もっぱらHTMLからの情報取得はWeb::Scraperな訳ですが、GAE上でもスクレイピングできたら嬉しいのでBeautifulSoupを使ってみたのですが挫折したメモ書きです。
まずは手始めに以下のscraperでYahoo Japanのトップページから
タイトルとtopicsのURLを取得するperlスクリプトを移植してみる事にしました。
#!/usr/bin/perl use strict; use warnings; use LWP::Simple; use Web::Scraper; use YAML; my $content = get("http://yahoo.co.jp"); my $scraper = scraper { process '//title', 'title' => 'TEXT'; process '//a[@href =~ /.+topics.+/]', 'topics[]' => '@href'; }; my $res = $scraper->scrape($content); print Dump $res;
早速書いてみたのが以下のコードです。
#!/usr/bin/python # -*- conding: utf-8 -*- import re import urllib from BeautifulSoup import BeautifulSoup content = urllib.urlopen('http://yahoo.co.jp') bs = BeautifulSoup(content.read()) title = bs.find('title') topics = bs.findAll('a', href=re.compile('.+topics.+')) print title for item in topics: print item
scraperのように複数のデータを連想配列として一括で取得は出来ないですが,コードも短く非常にすっきりとしています。このサンプルではXPathは使っていませんが、BeautifulSoupのXPath拡張も存在していて、特に不便な点は感じられません。
そこでもう少し実践的なデータを取得してみようと思いMLBのスタッツ情報をスクレイピングしようとしたのですが、ここで問題が発生しました。
url = 'http://mlb.mlb.com/stats/historical/player_stats.jsp' content = urllib.urlopen(url) bs = BeautifulSoup(content.read()) tables = bs.findAll('table')
このコードを走らせると以下のようなエラーが発生します。
調べてみると、特定のjavascriptがhtml中に存在すると、パースに失敗してしまうようです。
Traceback (most recent call last):
File "./bs_test3.py", line 18, in <module>
bs = BeautifulSoup(content.read())
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'', at line 104, column 114
</module>もう少し調べてみると、これを回避する為にはhtml5libを使えば良いという情報があったので、実際に試してみました。以下がそのコードになります。
#!/usr/bin/python # -*- conding: utf-8 -*- import re import urllib from BeautifulSoup import BeautifulSoup from html5lib import HTMLParser from html5lib import treebuilders url = 'http://mlb.mlb.com/stats/historical/player_stats.jsp' res = urllib.urlopen(url) parser = HTMLParser(tree=treebuilders.getTreeBuilder("beautifulsoup")) bs = parser.parse(res.read()) tables = bs.findAll('table') for table in tables: print table
これで先ほどのコードもパースできます。import文が長くなってしまいましたが、
ソース自体は綺麗に保たれていて、万事解決かと思われましたがそうでもありませんでした。
では何が問題かというと、html5libを使った場合に解析対象のデータ(html)が
HTML5の形式に書き換えられてしまう事です。スクレイピングコードを書く時は多かれ少なかれ、HTMLソースを参照しながらの作業になります。それが書き換えられてしまうとなると、作業効率に少なくない影響が出そうで心理的に抵抗が大きい訳です。HTML5も全然理解してないですし。
html5libを使わずに先ほどのパースエラーが回避できないか調べてみましたが、見つけられなかったので、とりあえず、BeautifulSoupを使うのはペンディングする事にしました。
何かいい方法がないものかしらん。
関連する記事
タグ: beautifulsoup, python

