BeautifulSoup と戯れる
HTML 文章を情報源としてデータを構築するときのお供に、 BeautifulSoup 。やはり便利。
テキストに変換
HTML 文章を強引にプレーンテキストに直してみる。 soup2string 呼び出し可能オブジェクト。
>>> from BeautifulSoup import BeautifulSoup >>> html = '<html><body><p>aaa<br />bbb<a href="spam.html">ccc</a></p></body></html>' >>> soup = BeautifulSoup(html) >>> soup2string(soup.p) u'aaa\nbbbccc[spam.html]'
soup2string のコードは以下。今のところ a, br, p 以外のタグは剥がすのみ。body タグ全部を食わせるつもりならば h1, h2, h3 タグとか li タグとかも処理したほうがいいかも。
from string import Template from BeautifulSoup import NavigableString, Tag def a_handler(soup, result, template=Template(u'[${href}]')): u"""<a> タグ用 Soup2string ハンドラ""" if soup.name == 'a' and soup.get('href', None): result.append(template.substitute(href=soup['href'])) def br_handler(soup, result): u"""<br> タグ用 Soup2string ハンドラ""" if soup.name == 'br': result.append(u'\n') def p_handler(soup, result): u"""<p> タグ用 Soup2string ハンドラ""" if soup.name == 'p': result.append(u'\n') class Soup2string(object): u"""BeautifulSoup オブジェクトをプレーンテキストにする変換オブジェクト デフォルトでは <a>, <br>, <p> タグを次のように変換する。 その他のタグは単に剥がして中身だけにする。 <a herf="egg">spam</a>: u'spam[egg]' に変換する <br />: u'\\n'に変換する <p>spam</p>: u'spam\\n'に変換する タグの扱いを変更するには pre_handlers, post_handlers を取り替える。 """ def __init__(self, pre_handlers=(br_handler,), post_handlers=(a_handler, p_handler)): self.pre_handlers = list(pre_handlers) self.post_handlers = list(post_handlers) def __call__(self, soup): return self.convert(soup=soup) def convert(self, soup): u"""Soup オブジェクトをユニコード文字列にする""" return u''.join(self._convert(soup=soup, result=[])) def _convert(self, soup, result): for obj in soup.contents: if isinstance(obj, NavigableString): result.append(obj.string) elif isinstance(obj, Tag): for handler in self.pre_handlers: handler(obj, result) self._convert(obj, result) for handler in self.post_handlers: handler(obj, result) else: raise ValueError('not BeautifulSoup object') return result soup2string = Soup2string()
table 解析
table タグ関連を Python のリストのリストに変換。 parse_table 関数。
>>> from BeautifulSoup import BeautifulSoup >>> html = '''<html><body> ... <table> ... <tr> ... <td>a</td> ... <td>b</td> ... <td>c</td> ... </tr> ... <tr> ... <td colspan="2">d</td> ... <td rowspan="2">e</td> ... </tr> ... <tr> ... <td>f</td> ... <td>g</td> ... </tr> ... </table> ... </body></html>''' >>> soup = BeautifulSoup(html) >>> table = parse_table(soup.table) >>> for tr in table: ... for td in tr: ... print td, ... print ... <td>a</td> <td>b</td> <td>c</td> <td colspan="2">d</td> <td colspan="2">d</td> <td rowspan="2">e</td> <td>f</td> <td>g</td> <td rowspan="2">e</td>
先ほどの soup2string とあわせる。
>>> table = parse_table(soup.table, converter=soup2string) >>> for tr in table: ... for td in tr: ... print td, ... print ... a b c d d e f g e
parse_table のコードは以下。コードが雑すぎる…。手法や変数名など後で見直さないと。
def parse_table(soup, converter=lambda x: x): if soup.name == 'table': if soup.tbody: soup = soup.tbody elif soup.name == 'tbody': pass else: raise ValueError('not table') result = [] temp = {} i = 0 j = 0 for tr in soup.findAll(name='tr', recursive=False): j = 0 r = [] for td in tr.findAll(name=['th', 'td'], recursive=False): colspan = int(td.get('colspan', '1')) rowspan = int(td.get('rowspan', '1')) for _ in range(colspan): while (i, j) in temp: r.append(converter(temp[(i, j)])) j += 1 r.append(converter(td)) for k in range(rowspan - 1): temp[(i + k +1, j)] = td j += 1 else: while (i, j) in temp: r.append(converter(temp[(i, j)])) j += 1 result.append(r) i += 1 return result