銀月の符号

Python 使い見習いの日記・雑記

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