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

orangain flavor

じっくりコトコト煮込んだみかん2。知らないことを知りたい。

Python 3をサポートしたScrapy 1.1が公開されました

python scraping

PythonスクレイピングフレームワークであるScrapyの新バージョン1.1がついに公開され*1Python 2.7に加えてPython 3.3以降がサポートされました🎉 。

Scrapy 1.1におけるPython 3のサポートは以下の制限があり、ベータサポートとされていますが、使えないのはどれもマイナーな機能なのでそれほど問題ないでしょう*2

  • Windows上のPython 3ではテストされていない。
  • メールの送信はサポートされていない。
  • FTPによるダウンロードはサポートされていない。
  • Telnetコンソールはサポートされていない。

実際、手元のいくつかの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という機能があり、インタラクティブXPathCSSセレクタで要素の取得を試せます。 Scrapy shellでPython 3のメリットを感じてみましょう。

近頃は良い気候でどこかに出かけたい気分なので、じゃらんの観光ガイドのページから、私の住んでいる兵庫県の観光スポットをスクレイピングしてみます。 兵庫県の「5月にオススメランキング」の第1位は姫路城です。

www.jalan.net

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サポートが実現したことになります。

github.com

私はここ1年半ほどウォッチしてきただけですが、以下の記事でその苦労の一端を窺うことができます。興味があれば読んでみてください。

blog.scrapinghub.com

まとめ

Scrapy 1.1とPython 3で楽しくクローリング・スクレイピングを行っていきましょう。

*1:例によってこの記事の執筆時点で公式サイトの表記は1.0のままですが、PyPI上には1.1.0が公開されています。

*2:Windowsユーザーの方はごめんなさい。

*3:Python 2におけるunicode型。

*4:従来のresponse.body_as_unicode()も使えます。