Scrapyでリンクを辿りながら欲しい情報をクロールする
Scrapy
Python製Crawlerライブラリのひとつで、ブログ記事をクロールするために現在利用を検討している。
初手で日本語の入門記事を参考にして何度か痛い目を見ているので、おとなしく本家のScrapy Tutorialを読んだ。この記事では、公式のDocumentを参考に勉強したことをまとめる。
Scrapyでできること
- 基本:websiteの内容を抽出&保存する
- HTML/XMLソースからCSSやXPathを使って簡単にデータを選択抽出するための補完
- インタラクティブなShellコンソールも利用可能(簡単にscrapingを試せる)
- ローカルやFTP等の環境にJSON等の形で簡単にファイル生成するための補完
- 文字コード関係の補完
- signalsとAPIを使った独自機能のプラグインが可能??
- 様々な拡張ビルドインとミドルウェア
- cookiesとsessionの操作
- cachingなどのHTTP機能
- user-agentになりすます
- robots.txtに関する機能(クローラに対する指令書)
- クロール深度制限
- scrapyプロセスの監視&操作のためのTelnet console
- サイトマップなどからサイトをクロールするための再利用可能なスパイダー
- 画像自動ダウンロード機能
- caching DNS resolver
(webの知識が浅いので見当違いな訳をしているかも)
始め方
Scrapyのインストール
$ pip install scrapy
欲しいデータの手がかり表現を見つける
クロール対象のソースファイルを見て、HTMLタグなどの手がかり表現を見つける。
例えば、以下のような構造を持つファイルを対象とするとき、
<div class="quote"> <span class="text">“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”</span> <span> by <small class="author">Albert Einstein</small> <a href="/author/Albert-Einstein">(about)</a> </span> <div class="tags"> Tags: <a class="tag" href="/tag/change/page/1/">change</a> <a class="tag" href="/tag/deep-thoughts/page/1/">deep-thoughts</a> <a class="tag" href="/tag/thinking/page/1/">thinking</a> <a class="tag" href="/tag/world/page/1/">world</a> </div> </div> <ul class="pager"> <li class="next"> <a href="/page/2/">Next <span aria-hidden="true">→</span></a> </li> </ul>
上記の例からテキスト・著者名・タグ・次ページへのリンクを抽出したい場合なら、
- 抽出範囲:< div class="" > と < /div > で囲まれた範囲
- テキスト:< span class="text" > ココ < /span >
- 著者:< small class="author" > ココ < /small >
- タグ:< div class="tag" > < a class="tag" href="~~~" > ココ < /a > < /div >
- リンク: < li class="next" > < a href="ココ" > Next < span aria-hidden="true" > → < /span > < /a >
に目星をつける。
対話形式でクロールしてみる
ちゃんと取り出せるかをSpiderのShellConsorlで確認する。
$ scrapy shell <クロール対象ドメイン url>
SpiderShellConsorlを起動。>>> response.css('<css/xpath表現>')
SelectorList
オブジェクトを返す。'div.quote'
例) < div class="quote" > ... < /div > の範囲を抽出。'span.text::text'
例) < span class="text" > ABCDE < /span > のABCDEを抽出。
>>> response.css('<css/xpath表現>').extract()
dataが入ったlist
オブジェクトを返す。>>> response.re(r'<正規表現>')
SelectorList
オブジェクトを返す。
欲しい情報を取って来れそうであることが確認できたら、クロールプログラムを作る。
新規プロジェクトの作成
Scrapy projectの基本構造は以下の通り。
scrapy.cfg #デプロイ設定ファイル myproject/ #プロジェクトディレクトリ __init__.py items.py middlewares.py pipelines.py settings.py spiders/ #Spiderディレクトリ __init__.py spider1.py spider2.py
$ scrapy startproject <プロジェクト名>
上記構造を持ったファイル群が作られる。
新規Spider(クローラ)の作成
$ scrapy genspider <spider名> <クロール対象ドメイン url>
Spiderの雛形ソースファイルが生成される。構造図で言うとspider1.py
など。-t <template>
をspider名の前に入れることでテンプレートを指定できる。$ scrapy genspider -l
spiderファイルのテンプレート一覧を取得。
Itemファイルの編集
一般的にスクレイピングでは、web pagesなどのごちゃっとしたデータから構造化されたデータを抽出したい。SpidersはPythonのdict型でデータを抽出できるが、dict型の構造化はあまり厳密なものではない。そこで、データ出力フォーマットを定義するItem Classが提供されている。
# items.pyの例 import scrapy class QuotesItem(scrapy.Item): text = scrapy.Field() author = scrapy.Field() tags = scrapy.Field()
Spiderファイルの編集
import scrapy from myproject.items import QuotesItem class QuotesSpider(scrapy.Spider): name = 'author' #<spider名> start_urls = ['<クロール対象ドメイン url>'] #AuthorSpiderクラスが呼ばれる時にまず呼ばれる。 def parse(self, response): #データ抽出 for quote in response.css('div.quote'): qitems = QuotesItem() qitems['text'] = quote.css('span.text::text').extract_first() qitems['author'] = quote.css('span small::text').extract_first() qitems['tags'] = quote.css('div.tags a.tag::text').extract_first() yield qitems yield response.follow(response.css('li.next a::attr(href)')[0], callback=self.parse)
Spiderファイルの補足説明
start_urls
が呼ばれると、裏側で以下のような処理が実行される。
def start_requests(self): for url in start_urls: yield scrapy.Request(url=url, callback=self.parse)
従って、parse関数が自動的に呼び出される。
また、リンクを辿って次のページに行く以下の部分は、
yield response.follow(response.css('li.next a::attr(href)')[0], callback=self.parse)
これは複数の処理をまとめて行なっている。以下のように書き下すことができる。
next_page = response.css('li.next a::attr(href)').extract_first() if next_page is not None: next_page = response.urljoin(next_page) yield scrapy.Request(next_page, callback=self.parse)
従ってresponse.follow()
はresponse.css('li.next a::attr(href)')[0]
が返すSelectorList
オブジェクトの第一要素から
data
を抜き出して、- ドメインのurlと結合し、
scrapy.Request
オブジェクトを返す
という3つの操作をしている。 (この場合要素は一つしかあり得ないが、要素数1であってもリスト型で返ってくるので第一要素の指定が必要。)
クロールする
$ scrapy crawl <spider名>
クロール開始$ scrapy list
spider一覧を取得。
感想
まとめ終えてからQiitaでscrapyと検索したら、出るわ出るわ。 もっと詳しい記事がたくさん。。。 なんと言うことでしょう。。。
はい。今日も勉強おつかーれさんでした。