PythonのスクレイピングフレームワークであるScrapyの新バージョン1.1がついに公開され*1、Python 2.7に加えてPython 3.3以降がサポートされました🎉 。
Scrapy 1.1におけるPython 3のサポートは以下の制限があり、ベータサポートとされていますが、使えないのはどれもマイナーな機能なのでそれほど問題ないでしょう*2。
実際、手元のいくつかのSpiderで試した限りでは、Python 3で問題なく使用できています。
Scrapy 1.1の変更点
Scrapy 1.1の変更点は多くありますが、基本的な機能は変わっていません。Python 2.7+Scrapy 1.0で動いていたSpiderやプロジェクトは、それほど変更することなくPython 3+Scrapy 1.1で動くでしょう。
個人的に興味深い変更点としては以下の点が挙げられます。
- プロジェクト新規作成時に
ROBOTSTXT_OBEY
の設定が有効になるようになった。 - Python 3におけるstr型*3のレスポンスボディが、
response.text
で取得できるようになった*4。
変更点について詳しくはリリースノートを参照してください。
Release notes — Scrapy 1.1.0 documentation
Scrapy 1.1のインストール
Python 3.5の環境でインストールしてみましょう。
$ python3.5 -m venv venv $ . venv/bin/activate (venv) $ python -V Python 3.5.1
以下のようにpipでインストールできます。
(venv) $ pip install scrapy
Scrapy 1.1.0がインストールできていることがわかります。なおTwistedのバージョンは15.5以上が必要です。
(venv) $ pip freeze attrs==15.2.0 cffi==1.6.0 cryptography==1.3.2 cssselect==0.9.1 idna==2.1 lxml==3.6.0 parsel==1.0.2 pyasn1==0.1.9 pyasn1-modules==0.0.8 pycparser==2.14 PyDispatcher==2.0.5 pyOpenSSL==16.0.0 queuelib==1.4.2 Scrapy==1.1.0 service-identity==16.0.0 six==1.10.0 Twisted==16.1.1 w3lib==1.14.2 zope.interface==4.1.3
Python 3でScrapy shellを使う
さてScrapyがPython 3で動くようになって嬉しいのは、単にPython 2.7を使わずに済むようになることだけではありません。日本語などASCII以外のWebページをスクレイピングする時には、これまでUnicodeエスケープされていた文字列がそのまま表示されるようになるので、非常に便利です。
ScrapyにはScrapy shellという機能があり、インタラクティブにXPathやCSSセレクタで要素の取得を試せます。 Scrapy shellでPython 3のメリットを感じてみましょう。
近頃は良い気候でどこかに出かけたい気分なので、じゃらんの観光ガイドのページから、私の住んでいる兵庫県の観光スポットをスクレイピングしてみます。 兵庫県の「5月にオススメランキング」の第1位は姫路城です。
Scrapy shellは以下のようにscrapy shell
コマンドの引数にURLを指定して起動します。
$ scrapy shell http://www.jalan.net/kankou/spt_28201af2120008952/
起動すると指定したURLが取得され、以下のように入力待ちの状態になります。 Scrapy shell内で使用できる変数が表示されています。
[s] Available Scrapy objects: [s] crawler <scrapy.crawler.Crawler object at 0x107903ef0> [s] item {} [s] request <GET http://www.jalan.net/kankou/spt_28201af2120008952/> [s] response <200 http://www.jalan.net/kankou/spt_28201af2120008952/> [s] settings <scrapy.settings.Settings object at 0x10a851d68> [s] spider <DefaultSpider 'default' at 0x10ac28128> [s] Useful shortcuts: [s] shelp() Shell help (print this help) [s] fetch(req_or_url) Fetch request (or URL) and update local objects [s] view(response) View response in a browser >>>
以下のようにして、CSSセレクタやXPathで要素を取得できます。なお、これらのCSSセレクタやXPathはブラウザの開発者ツールを見ながら考えます。
# スポット名 >>> response.css('h1.detailTitle::text').extract_first() '姫路城' # スコア >>> response.css('.reviewPoint::text').extract_first() '4.3' # 住所 >>> response.xpath('//th[text()="所在地"]/following-sibling::td/text()').extract_first().strip() '〒670-0012\u3000\n\t\t\t\t\t\t\t\t\t兵庫県姫路市本町68'
Python 2.7で実行すると以下のようになるので、違いは明白です。Python 2.7でもprint文を使えば日本語をそのまま表示できますが、やや面倒です。ぜひPython 3を使いましょう。
# スポット名 >>> response.css('h1.detailTitle::text').extract_first() u'\u59eb\u8def\u57ce' # スコア >>> response.css('.reviewPoint::text').extract_first() u'4.3' # 住所 >>> response.xpath(u'//th[text()="所在地"]/following-sibling::td/text()').extract_first().strip() u'\u3012670-0012\u3000\n\t\t\t\t\t\t\t\t\t\u5175\u5eab\u770c\u59eb\u8def\u5e02\u672c\u753a68'
他にもPython 2.7ではSpider実行時のログに日本語が含まれていてもUnicodeエスケープされていましたが、Python 3ではそのまま表示されるので問題に気付きやすくなりました。Scrapyでログに出力されるItem内のUnicode文字列を読めるようにする - Qiita のような涙ぐましい努力はPython 3ではもう不要です。
Python 3サポートの軌跡
以下のIssueが立てられたのが2013年3月なので、実に3年以上の時を経てPython 3サポートが実現したことになります。
私はここ1年半ほどウォッチしてきただけですが、以下の記事でその苦労の一端を窺うことができます。興味があれば読んでみてください。