7
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

PythonでHTMLのテーブルを解析した

Last updated at Posted at 2018-04-11

動機

wikiのテーブルにまとめられたデータを読み込んで、正規化したり、コードにを生成したりしたかった。
URLコピペとかが理想だが、解析対象のTableの特定など困難なので、「検証」タブなどからtable部分のHTMLをコピペして、それを解析することにした。

Python苦手なのであしからず。

tdにネストしたタグ

HTMLparserをそのまま使うと、タグごとに handle_data() が呼ばれてちょっと不便なので、tdの中にネストしたタグがあったら全部くっつけて、tdが終わるタイミングで独自のコールバックを呼び出すことにした。

rowspan, colspan

HTMLのテーブルは、縦横につなげる事ができる。
今回の用途では、これらは同じ値が入っているものとして扱いたかったので、同じ値で埋めることにした。

そのために、解析中にrowspanやcolspanが1より大きいものがあれば、仮想的な欄を生成してバッファに保持していき、必要なタイミングで取り出してコールバックを呼ぶことで対応した。

成果物

※何も考えずにline, rowで作ったら、HTMLではrow, columnで、かなり混乱を生むコードになってしまった

# coding: utf-8
from html.parser import HTMLParser

class Error(Exception):
    pass

class HTMLTableParser(HTMLParser):
    """Class for getting data from HTML tableself. Please override `handle_td` to parse."""
    def __init__(self):
        super().__init__()
        self.is_inside_td = False
        self.line = 0
        self.row = 0
        self.buffer = ''
        self.current_rowspan = 1
        self.current_colspan = 1
        self.virtual_rows = []

    def before_handle_td(self):
        for i in range(0, self.current_rowspan):
            # Append virtual_rows except (0, 0)
            if i > 0:
                self.virtual_rows.append((self.line+i, self.row, self.buffer))
            for j in range(1, self.current_colspan):
                self.virtual_rows.append((self.line+i, self.row+j, self.buffer))
        self.current_rowspan = 1
        self.current_colspan = 1
        matchVirtualRow = next(filter(lambda x: x[0] == self.line and x[1] == self.row, self.virtual_rows), None)
        if matchVirtualRow is not None:
            self.handle_td(matchVirtualRow[0], matchVirtualRow[1], matchVirtualRow[2])
            self.virtual_rows.remove(matchVirtualRow)
            self.row += 1
            self.before_handle_td()

    def handle_td(self, line, row, buffer):
        """Please override this function to handle table cell data"""
        print(self.line, self.row, self.buffer)

    def handle_starttag(self, tag, attrs):
        if tag == 'td':
            if self.is_inside_td:
                print("[td] tag must not be nested.")
                raise Error()
            self.is_inside_td = True
            self.current_rowspan = next(map(lambda x: int(x[1]), filter(lambda x:x[0] == 'rowspan', attrs)), 1)
            self.current_colspan = next(map(lambda x: int(x[1]), filter(lambda x:x[0] == 'colspan', attrs)), 1)
        elif tag == 'br':
            if self.is_inside_td:
                self.buffer += '\n'
        elif tag == 'ul':
            if self.is_inside_td:
                self.buffer += '('

    def handle_endtag(self, tag):
        if tag == 'td':
            if not(self.is_inside_td):
                print("[td] tag must not be nested.")
                raise Error()
            self.before_handle_td()
            self.handle_td(self.line, self.row, self.buffer)
            self.is_inside_td = False
            self.buffer = ''
            self.row += 1
        elif tag == 'tr':
            # process trailing virtual rows
            for virtual_row in sorted(filter(lambda x: x[0] == self.line, self.virtual_rows), key=lambda x:x[1]):
                self.handle_td(virtual_row[0], virtual_row[1], virtual_row[2])
                self.virtual_rows.remove(virtual_row)
            self.line += 1
            self.row = 0
        elif tag == 'p':
            if self.is_inside_td:
                self.buffer += '\n'
        elif tag == 'li':
            if self.is_inside_td:
                self.buffer += ', '
        elif tag == 'ul':
            if self.is_inside_td:
                self.buffer += ')'

    def handle_data(self, data):
        if self.is_inside_td:
            self.buffer += data

使い方

継承して、handle_tdを実装して、使うだけです!
以下は、第3カラムの内容を取得して、uniqしてアルファベット順に並べたデータを取得する例です。

class Column2Parser(HTMLTableParser):
    def __init__(self):
        super().__init__()
        self.values = []

    def handle_td(self, line, row, buffer):
        if row == 2:
            self.values.append(buffer)
            
def valueList():
    parser = Column2Parser()
    with open('table.html') as f:
        parser.feed(f.read())
    values_uniq = sorted(set(parser.values), key=lambda v: v.upper())
    print(values_uniq)
    
valueList()

他の言語のメタプロ

Python、型がなくてツライけど、気軽にメタプロ(他の言語のコードを吐き出す)できるから楽しい。
KVOなコードは自動生成していきたい。

7
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?