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

YAMAGUCHI::weblog

土足で窓から失礼いたします。今日からあなたの息子になります。 当年とって92歳、下町の発明王、エジソンです。

5分でWebスクレイピングをする

はじめに

あけましておめでとうございます。今年もPython界の江古田ちゃんとして頑張っていく所存です。さて id:nishiohirokazu が5分でPythonは便利だと思える記事を元旦から書いていました。
「ほえー、さすが西尾さんや」って思ってたら、西尾さんが「おい山口、5分でPython便利だなーって思える記事書けや」っていう無言の圧力をかけてきたので*1 *2なんとなく書きました。
「5分で」っていうのが読者が読む時間なのか、筆者が書く時間なのかがわからなかったので前者ということにしました。5分で記事とコード両方書くとか無理や。

こんなことないですか

「Webでスクレイピングしたいよー、てへへ。だけど文字コードとかがページごとにバラバラでマジしんどいっす。しかもタグ抜き出すのとかめちゃめんどいっす><」ってことはあったりしませんか?
リンクのタイトルとかも綺麗に抜き出したいなーっていうときにはページの文字コードが問題になってきますよね。でもいちいちどのページがどのエンコードとか調べるのめんどくさいですよね。
最近はPythonでは便利なライブラリが増えたので、簡単にできちゃいます。

やってみよう

用意するもの

とりあえずeasy_insallでもpipでもいいけど、このライブラリをインストールしましょう。コマンドで一発です。うちの環境ではvirtualenv下でpip使ってるのでそのまま転載。

$ pip install chardet
$ pip install pyquery

ただしPyQueryにはlxmlが必要で、lxmlにはlibxml2とlibxsltが必要です。これ用意する時点でちょっと面倒ですね。ブログ書き始めた時点で気づいたけど、まあそれくらいはググってもらえばいっぱい手順がでてくるはず。

Webのスクレイピングをする大まかな手順
  1. 欲しいものを考える
  2. スクレイピングしたいサイトを用意する
  3. 各サイトをデコードしてUnicodeに統一する
  4. 各サイトからほしい部分をぶっこぬく
  5. 整形して保存したりする

ここの「デコードして」っていう部分と「ぶっこぬく」っていう部分がなかなか面倒なのでライブラリ使いましょうっつー話。

こんな感じで書けばいいと思う
import urllib
import chardet
from pyquery import PyQuery as pq

# テスト用URL
# METAタグでcharset指定してなくてもちゃんと文字コード判別できるかもテスト
urls = [
    'http://python-history-jp.blogspot.com/',               # UTF-8
    'http://iblinux.rios.co.jp/PyJdoc/lib-j/',              # EUC-JP
    'http://osksn2.hep.sci.osaka-u.ac.jp/~taku/osx/python/encoding.html', # ISO-2022-JP
    'http://www.atmarkit.co.jp/news/200812/04/python.html', # Shift_JIS
    'http://pyunit.sourceforge.net/pyunit_ja.html',         # EUC-JP, w/o META
    'http://www.f7.ems.okayama-u.ac.jp/~yan/python/',       # ISO-2022-JP, w/o META
    'http://weyk.com/weyk/etc/OpenRPG.html', # Shift_JIS, w/o META
    ]


if __name__ == '__main__':
    
    # 各サイトの文字コードを判別
    detected = []
    for url in urls:
        data = ''.join(urllib.urlopen(url).readlines())
        guess = chardet.detect(data)
        result = dict(url=url,data=data,**guess)
        detected.append(result)

    # 各サイトごとにaタグの文字とリンク先を引っ張ってくる
    for p in detected:
        print '%s -> %s (%s)' % (p['url'], p['encoding'], p['confidence'])
        unicoded = p['data'].decode(p['encoding'])  # デコード
        d = pq(unicoded)
        for link in d.find('a'):  # 見つけたaタグごとにタイトルとURLを抜き出す
            link_title = pq(link).text()
            link_url = pq(link).attr.href
            print '    %s => %s' % (link_title, link_url)

まあ例外処理もしてないのでちゃんと書くならもっと時間かかりますが、ちょっと書くだけならこんなもんでしょう。
さらにリンク先のコンテンツを並列で取りに行ったりとか、そういうことするならeventlet使うのがいいって id:mopemope が言ってました!

おわりに

「別にこの話題はPythonじゃなくてもPerlでもRubyでもPHPでもなんでもそういうライブラリ使えばできるじゃん?」っていう話ですが、まあPythonでは割と簡単にできるよ、という証拠として書いときました。
なにげにテスト用のURLを探すのに苦労しました。これだけで15分くらい使ってるはず。

*1:被害妄想の誇大妄想です

*2:西尾さんはこんなこと言いません。いい人です。