読者です 読者をやめる 読者になる 読者になる

libxml2でスクレイピング

Python

川o・-・)<2nd life - ruby のスクレイピングツールキット scrAPIで、紹介されているscrAPIというツールを知りました。
CSSセレクタで要素を取得するというアイディアは面白いと思うのですが、やっぱりXPathを使った方が手っ取り早いし、あとあと応用が利きそうな気もします。


試しに、Pythonとlibxml2を使って書いてみます。libxml2のHTMLパーサーは、ブロークンなHTMLも解析してくれるし、エンコーディングも上手く扱ってくれるので非常に便利です。
例えば、すべてのリンクを取得したい場合はこんな感じです。

import libxml2

doc = libxml2.htmlReadFile(
        'http://www.hatena.ne.jp/',   # url
        None,                         # encoding
        libxml2.HTML_PARSE_RECOVER|
        libxml2.HTML_PARSE_NOERROR|
        libxml2.HTML_PARSE_NOWARNING  # options
      )
nodes = doc.xpathEval("//a[@href]")
links = list([x.prop('href') for x in nodes])
doc.freeDoc()

print links

はてなダイアリーキーワードページから、タイトル、ふりがな、URL、カテゴリを取得する場合、こんな感じで。

import libxml2

url = 'http://d.hatena.ne.jp/keyword/%A5%D9%A5%AC%A5%EB%A5%BF%C0%E7%C2%E6'
tree = libxml2.htmlReadFile(
            url,
            None,
            libxml2.HTML_PARSE_RECOVER|
            libxml2.HTML_PARSE_NOERROR|
            libxml2.HTML_PARSE_NOWARNING
       )

ctx = tree.xpathNewContext()
unmarshallers = (
    (
      "//span[@class='title']/a[1]",
      lambda x: (('title', x[0].content), ('url', x[0].prop('href')))
    ),
    (
      "//span[@class='furigana']",
      lambda x: (('furigana', x[0].content),)
    ),
    (
      "//ul[@class='list-circle']/li[1]/a",
      lambda x: (('category', x[0].content),)
    )
)
d = {}
for xpath, unmarshal in unmarshallers:
	d.update(unmarshal(ctx.xpathEvalExpression(xpath)))
print d

結果は、辞書型で取得できます。

{
  'title' : 'ベガルタ仙台',
  'url' : '/keyword/%a5%d9%a5%ac%a5%eb%a5%bf%c0%e7%c2%e6?kid=3522',
  'category' : 'スポーツ',
  'furigana' : 'べがるたせんだい'
}

libxml2バインディングを使って書いたので、id:secondlifeさんのRubyのコードと比べると、軽やかさが足りないですね。scrAPIのコードは、Rubyを知らない人間には魔法のように見えます!
Pythonでも、もう少し工夫すれば使いやすいライブラリを書けそうですが、HTMLから必要な情報を抜き出すだけならば、僕には、これで十分。