以下を云々してみたメモを。
scrapy は pip で導入済み。つうか Twisted が落ちてきたりしてるんですがどうなのか。あと、これも環境 Docker で作っておきたくはありますが、それは別途で。
と、思ったら
導入に失敗するな。Installation guild から見るか。
出力見てみるに ffi.h が無いと言われてるな libffi-dev を入れてみるか。
$ sudo apt-get install -fy libffi-dev
で、リトライ。コンパイルが始まっているみたいなので当たりかな。そして pip install は sudo 付けなきゃ、だった模様。
無事に導入できたみたいなので tutorial に着手します。
Creating Project
プロジェクトを以下で作成、とのこと。
$ scrapy startproject tutorial
以下な出力を確認。
You can start your first spider with:
cd tutorial
scrapy genspider example example.com
あと、find tutorial の出力が以下。
$ find tutorial/
tutorial/
tutorial/scrapy.cfg
tutorial/tutorial
tutorial/tutorial/__init__.py
tutorial/tutorial/pipelines.py
tutorial/tutorial/spiders
tutorial/tutorial/spiders/__init__.py
tutorial/tutorial/items.py
tutorial/tutorial/settings.py
git init しとくか。
$ cd tutorial
$ git init
最初に掃き出されるファイルについて箇条書きで纏めてあるので以下に控え。
- scrapy.cfg はプロジェクトの設定 (configuration) ファイル
- tutorial はプロジェクトの python module でここに実装を追加とのこと
- tutorial/items.py は project’s items file とある
- tutorial/pipelines.py は project’s pipelines file とある
- tutorial/settings.py は project’s setting file とある
- tutorial/spyders/ には別途 spiders を追加とのこと
Defining our Item
Item には scraped data が load されるとある。とりあえず branch して以下を盛り込んでみます。
import scrapy
class DmozItem(scrapy.Item):
title = scrapy.Field()
link = scrapy.Field()
desc = scrapy.Field()
TutorialItem というクラスがありましたが、そちらはそのままで上記を追加してます。いいのかな。
Our first spyder
前の branch を元にさらに branch してます。ええと、spider は user-written classes とありますね。定義されるのは
- list of URL to download
- how to follow links
- how to parse the contents of those pages to extact items
な initial list って理解で良いのかどうか。Spyder を作るには
- scrapy.Spider なサブクラス作る
- name, start_urls, parse という三つの属性定義
- name は Spyder の識別子で unique でないと駄目
- start_urls は spider が crowl する URL のリスト (トップのみ、で良いはず)
- parse は download された Reponse オブジェクトが渡される spider のメソド
ええと、parse についてもう少しフォローがある模様。
- parse は response を parse したり、scraped items を取り出したり、リンクをフォローしたりしなきゃ、なのかどうか
とりあえず最初の spider を書け、とのこと。あまりしなきゃいけないことができてない印象ですがそこはスルー。
tutorial/spiders/dmoz_spider.py に、とのこと。
import scapy
class DmozSpider(scrapy.Spider):
name = "dmoz"
allowed_domains = [ "dmoz.org" ]
start_urls = [
"http://www.dmoz.org/Computers/Programming/Languages/Python/Books/",
"http://www.dmoz.org/Computers/Programming/Languages/Python/Resources/"
]
def parse(self, response):
filename = response.url.split("/")[-2]
with open(filename, "wb") as f:
f.write(response.body)
Crawling
以下をプロジェクトの top level directory で、とのこと。
$ scrapy crawl dmoz
実行してみた。いくつかナチュラルをぶちカマしてましたが出力が以下。
2014-08-19 17:41:19+0900 [scrapy] INFO: Scrapy 0.24.4 started (bot: tutorial)
2014-08-19 17:41:19+0900 [scrapy] INFO: Optional features available: ssl, http11
2014-08-19 17:41:19+0900 [scrapy] INFO: Overridden settings: {'NEWSPIDER_MODULE': 'tutorial.spiders', 'SPIDER_MODULES': ['tutorial.spiders'], 'BOT_NAME': 'tutorial'}
2014-08-19 17:41:19+0900 [scrapy] INFO: Enabled extensions: LogStats, TelnetConsole, CloseSpider, WebService, CoreStats, SpiderState
2014-08-19 17:41:19+0900 [scrapy] INFO: Enabled downloader middlewares: HttpAuthMiddleware, DownloadTimeoutMiddleware, UserAgentMiddleware, RetryMiddleware, DefaultHeadersMiddleware, MetaRefreshMiddleware, HttpCompressionMiddleware, RedirectMiddleware, CookiesMiddleware, ChunkedTransferMiddleware, DownloaderStats
2014-08-19 17:41:19+0900 [scrapy] INFO: Enabled spider middlewares: HttpErrorMiddleware, OffsiteMiddleware, RefererMiddleware, UrlLengthMiddleware, DepthMiddleware
2014-08-19 17:41:19+0900 [scrapy] INFO: Enabled item pipelines:
2014-08-19 17:41:19+0900 [dmoz] INFO: Spider opened
2014-08-19 17:41:19+0900 [dmoz] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
2014-08-19 17:41:19+0900 [scrapy] DEBUG: Telnet console listening on 127.0.0.1:6023
2014-08-19 17:41:19+0900 [scrapy] DEBUG: Web service listening on 127.0.0.1:6080
2014-08-19 17:41:20+0900 [dmoz] DEBUG: Crawled (200) <GET http://www.dmoz.org/Computers/Programming/Languages/Python/Resources/> (referer: None)
2014-08-19 17:41:21+0900 [dmoz] DEBUG: Crawled (200) <GET http://www.dmoz.org/Computers/Programming/Languages/Python/Books/> (referer: None)
2014-08-19 17:41:21+0900 [dmoz] INFO: Closing spider (finished)
2014-08-19 17:41:21+0900 [dmoz] INFO: Dumping Scrapy stats:
{'downloader/request_bytes': 516,
'downloader/request_count': 2,
'downloader/request_method_count/GET': 2,
'downloader/response_bytes': 16515,
'downloader/response_count': 2,
'downloader/response_status_count/200': 2,
'finish_reason': 'finished',
'finish_time': datetime.datetime(2014, 8, 19, 8, 41, 21, 174204),
'log_count/DEBUG': 4,
'log_count/INFO': 7,
'response_received_count': 2,
'scheduler/dequeued': 2,
'scheduler/dequeued/memory': 2,
'scheduler/enqueued': 2,
'scheduler/enqueued/memory': 2,
'start_time': datetime.datetime(2014, 8, 19, 8, 41, 19, 860470)}
2014-08-19 17:41:21+0900 [dmoz] INFO: Spider closed (finished)
で、Books と Resources というファイルができてます。これは parse が吐き出したものですね。あと [dmoz] な出力にも着目せよ、という記述があります。
というか parse の実装見るに、これはコンテンツをそのまんま書き出せ、って話なんですね。
Extracting Items
XPath expression の例とその意味が列挙されている。ええと XPath で云々するには Selector というクラスが用意されている模様。
Selector が持ってる基本的なメソドが列挙されてます。
- xpath() : selectors の list が戻る (xpath な式を引数として取る)
- css() : selectors の list が戻る (CSS な式を引数として取る)
- extract() : 選択されたデータの unicode 文字列を戻す
- re() : 引数で渡された正規表現を apply して抽出された文字列のリストを戻す?
shell で試せる模様。最初に出力されるソレは略。
>>> response.xpath('//title')
[<Selector xpath='//title' data=u'<title>DMOZ - Computers: Programming: La'>]
>>> response.xpath('//title').extract()
[u'<title>DMOZ - Computers: Programming: Languages: Python: Books</title>']
>>> response.xpath('//title/text()')
[<Selector xpath='//title/text()' data=u'DMOZ - Computers: Programming: Languages'>]
>>> response.xpath('//title/text()').extract()
[u'DMOZ - Computers: Programming: Languages: Python: Books']
>>> response.xpath('//title/text()').re('(\w+):')
[u'Computers', u'Programming', u'Languages', u'Python']
あ、最後のソレはリストにできるのか。そして次の項の記述を見つつ
>>> response.xpath('//ul/li/a/@href').extract()
を確認してみたらリンク文字列なリストが取り出せてますね。ということで parse な実装を以下に修正。
def parse(self, response):
for sel in response.xpath('//ul/li'):
title = sel.xpath('a/text()').extract()
link = sel.xpath('a/@href').extract()
desc = sel.xpath('text()').extract()
print title, link, desc
最後は print でいいのかな。これで以下を実行。
$ scrapy crawl dmoz
確かに print されてますが、なんとなく微妙。
Using our item
あ、ここで Item になるのか。
def parse(self, response):
for sel in response.xpath('//ul/li'):
item = DmozItem()
item['title'] = sel.xpath('a/text()').extract()
item['link'] = sel.xpath('a/@href').extract()
item['desc'] = sel.xpath('text()').extract()
yield item
で、以下で json にして出力できる模様。
$ scrapy crawl dmoz -o items.json
あら、なんか出力が微妙だなぁ。link なナニが URL になってないものがあるんですが、って思ったらこれ、相対パスなリンクなのか。ということはこのあたりをフィルタするにはは正規表現マッチとかで判断できるのかどうか。
もう少し
パースの仕方 (?) は色々あるらしい。
- scrapy.Selector に response を食わせる方式
- SitemapSpider というものがあるらしい件
- 同様に CrawlSpider というものがあるらしい件
ちょっとドキュメント確認してみます。