BeautifulSoupでパースエラーが出て困る件

先日BeautifulSoupを使おうとして挫折した訳ですが、そうは言ってもGAEではlxmlが使えない以上、GAE上でスクレイピングする場合は使うしかないです。htmlのパーサーなんて書く気にならないのであります。書く力が無いのであります。

しかし先日も書いたように使ってみると、割と頻繁にパースエラーが発生して処理できなくなります。例えば(http://mlb.mlb.com/stats/historical/player_stats.jsp)を実際にパースしてみると以下の様なエラーが発生します。

>>> import urllib
>>> from BeautifulSoup import BeautifulSoup
>>> source = urllib.urlopen('http://mlb.mlb.com/stats/historical/player_stats.jsp').read()
>>> soup = BeautifulSoup(source)
Traceback (most recent call last):
  File "< stdin >", line 1, in < module >
  File "/Users/matsumototaichi/Documents/test/python_test/beautifulsoap_test/BeautifulSoup.py", line 1499, in __init__
    BeautifulStoneSoup.__init__(self, *args, **kwargs)
  File "/Users/matsumototaichi/Documents/test/python_test/beautifulsoap_test/BeautifulSoup.py", line 1230, in __init__
    self._feed(isHTML=isHTML)
  File "/Users/matsumototaichi/Documents/test/python_test/beautifulsoap_test/BeautifulSoup.py", line 1263, in _feed
    self.builder.feed(markup)
  File "/System/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/HTMLParser.py", line 108, in feed
    self.goahead(0)
  File "/System/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/HTMLParser.py", line 150, in goahead
    k = self.parse_endtag(i)
  File "/System/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/HTMLParser.py", line 314, in parse_endtag
    self.error("bad end tag: %r" % (rawdata[i:j],))
  File "/System/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/HTMLParser.py", line 115, in error
    raise HTMLParseError(message, self.getpos())
HTMLParser.HTMLParseError: bad end tag: u'< / scr "+" ipt > ', at line 104, column 114

原因は< / scr "+" ipt > の様な形式のscriptタグをパースできない事が理由で、対策としてはhtml5libを使うと良いようです。確かに先ほどのURLをhtml5libを利用してパースすると上手くパースできます。

>>> parser = HTMLParser(tree=treebuilders.getTreeBuilder("beautifulsoup"))
>>> soup = parser.parse(source)
>>> soup.find('title')
Historical Player Stats | MLB.com: Stats

ところがこれで万事解決ではありません。何故かというとhtml5libを使うと、逆にパースできなくなるページがあるからです。例えば(‘http://blog.livedoor.jp/goldennews/archives/51338054.html’)をhtml5libでパースしてみると以下の様なエラーが発生します。(ただしこちらは普通のBeautifulSoupでパースしてもエラーは発生しません)

>>> source = urllib.urlopen('http://blog.livedoor.jp/goldennews/archives/51338054.html').read()
>>> parser.parse(source)
Traceback (most recent call last):
  File "< stdin >", line 1, in < module >
  File "/Users/matsumototaichi/Documents/test/python_test/beautifulsoap_test/html5lib/html5parser.py", line 155, in parse
    self._parse(stream, innerHTML=False, encoding=encoding)
  File "/Users/matsumototaichi/Documents/test/python_test/beautifulsoap_test/html5lib/html5parser.py", line 132, in _parse
    method(token["name"])
  File "/Users/matsumototaichi/Documents/test/python_test/beautifulsoap_test/html5lib/html5parser.py", line 329, in processEndTag
    self.endTagHandler[name](name)
  File "/Users/matsumototaichi/Documents/test/python_test/beautifulsoap_test/html5lib/html5parser.py", line 1160, in endTagFormatting
    furthestBlock.parent.removeChild(furthestBlock)
  File "/Users/matsumototaichi/Documents/test/python_test/beautifulsoap_test/html5lib/treebuilders/soup.py", line 92, in removeChild
    del node.parent.element.contents[index]
TypeError: list indices must be integers

バグトラッカーへの記述によると、内部でhtmlのノードツリーを書き換える際に、ノード間のリンクが壊れているらしいです。解ってるなら直してくれと思いながらも、何とかうまく解決する方法は無いのかと、色々と実験してみましたが上手く解決する方法は見つかりませんでした。

仕方がないので、その場しのぎですがパースエラーをキャッチして、両方のパーサを併用する事にしました。

def get_soup(url):
  source = urllib.urlopen(url).read()
  try:
    # まずBeautifulSoupでパースして、
    soup = BeautifulSoup(source)
  except:
    # エラーが発生したらhtml5libでパースする
    parser = HTMLParser(tree=treebuilders.getTreeBuilder("beautifulsoup"))
    soup = parser.parse(source)
  return soup

更にエラーが発生する場合は、また別のパーサでパースする訳ですが、汚いですよね。もっといい方法があったら教えて下さい。

2 thoughts on “BeautifulSoupでパースエラーが出て困る件”

  1. ずいぶんと前の投稿にコメントしてよいものやらわかりませんが、
    参考にさせていただきました。
    source = urllib.urlopen(url).read()
    ここで<script~を削除してから
    soup = BeautifulSoup(source)
    とするとうまくいくことが多いように思います。

  2. te2rojp 様
    コメントありがとうございます。返信が遅くなってしまいました。

    確かにがパースできないので削除すると動くのですが、
    一々削除するのは抵抗がありますし、他のパースエラーが出た際に
    都度対応するのも面倒です。そこで記事のような方策を取っていたのですが、
    先日リリースされたBS3.2系列でうまくパースできるようになっているようです。
    別のパースエラーがでない限りは特にhtml5libを使う必要はなさそうですね。

Leave a Reply

Your email address will not be published. Required fields are marked *