もくじ
クローリング・スクレイピングフレームワークのScrapyを使ってみる
Scrapyとは、クローリング・スクレイピングのPythonのフレームワークです。
ぶっちゃけPythonそんな好みじゃないので、はじめはRubyでやろうと思ったんですが、Rubyのクローリング・スクレイピングフレームワークが数年前に開発が止まっちゃってたり、なんだかんだScrapyが強力かつ使いやすいっぽいとの各所での評判により使ってみることにしました。
ちなみに、Scrapyはしばらくの間Python2系でしか動かなかったみたいですが、2016年にリリースされたScrapy ver1.1からPython3に対応しました。
この記事、というかこのブログでは基本的にはPython3系を前提にコードを書いていく予定です。
クローラー、クローリングって何?
そもそもクローリングとは、ざっくり言うと自動でwebを巡回してwebサイトなどからデータを収集する行為のことです。
そのクローリングをしてくれるプログラムのことをクローラー、クローラbot、もしくは単にbotなんかとも呼ばれます。
また、Web上のリンクを次々とたどっていく様子からか、スパイダーと呼ばれることもあり、今回使用するScrapyでは、クローリングの処理に用いる部分は”Spider”と名付けられています。
スクレイピングってなに?
スクレイピングとは、前述のクローリングで収集したデータから、必要な情報を抜き出す行為のことです。
クローリングとスクレイピングは同じ意味合いで使われることも多く、混同されやすいのですが、当ブログ内では
- クローリング → 巡回・データ収集
- スクレイピング → 元データからの情報抽出・整形処理
という意味合いで呼び分けていきます。
本記事で使用した環境
MacOS Mojave
Python 3.7.3
Pip 19.1.1
*anaconda等のディストリビューションは使用していません。なのでanaconda環境の方は少し手順やコマンドが異なるかもしれません。
Scrapyのインストール
Scrapyのインストールにはpipを使うとラクチンです。
$ pip install scrapy
が、できれば仮想環境を作成してプロジェクトごとに切り分けておくことが推奨されています。
仮想環境の作成手順は下記に。めんどくさかったら飛ばしてもまぁok。
Installation — virtualenv 16.6.1 documentation
インストールに成功していれば、Scrapyコマンドが使えるようになります。
正しくインストールできているか、次のコマンドでバージョンを確認してみましょう。
Scrapy 1.4.0
インストールに失敗したら
なお、環境によっては上記手順のインストールに失敗することがある。その場合下記などみて見てください。
Scrapyを使ったクローリング:pip install scrapyに失敗した時の解決方法(Twistedのインストール手順)
Win + anacondaでやってる人は下記ブログに手順がまとめられています。
Pythonでスクレイピングしようと思ってScrapy入れようとしたらエラーになった話 – 自動化厨のプログラミングメモブログ│CODE-LIFE
(というか、当サイトのやり方で失敗した方が調べてまとめてくれてるようです。ありがとうございます。すみません、、、)
実際にクローラーを作ってみる
今回は、Scrapyのアーキテクチャとか実行順序、データの流れなどの詳しい部分は一旦省略します。
まずはとりあえず動くものを作り、ざっくりどんなものか感じをつかんでからの方がとっつきやすいと思うので。
Spiderの作成
Scrapyでクローリング・スクレイピングをする際には、今回作成するSpiderというクラスを主に利用します。
Spiderには対象となるWebページをクローリングする際の設定やスクレイピングの処理を書いていきます。
今回のサンプルでは、Scrapy公式サイトにあるサンプルをもとに、何やってるのか適宜日本語でコメントをつけていきます。
このサンプルでは、ScrapyをメンテナンスしているScrapinghubのブログから、すべての記事タイトルを抜き出して一覧にするSpiderを作成します。
サンプルコード(samplespider.py)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import scrapy class BlogSpider(scrapy.Spider): name = 'blogspider' # Spiderの名前。 # クロールを開始するURLをリストで指定する。タプルでもok。 start_urls = ['https://blog.scrapinghub.com'] # start_urlで指定したページを取得後に呼ばれるメソッド。spider.Responseオブジェクトを受け取る。 def parse(self, response): """ 記事一覧ページから、各記事のタイトルを抜き出す """ # 記事タイトル(h2)を抜き出す for title in response.css('h2.entry-title'): yield {'title': title.css('a ::text').extract_first()} # 次のページのリンクを抜き出してたどる for next_page in response.css('div.prev-post > a'): yield response.follow(next_page, self.parse) |
Scrapyのgenspiderコマンドを使えば、Spiderのひな形を生成することができます。
このひな形を使えば、効率よくSpiderを作っていくことができます。
$ scrapy genspider samplespider blog.scrapinghub.com
genspiderコマンドでは、第1引数にSpiderの名前、第2引数にクロール対象のドメインを指定して実行してやると、下記のようにそれぞれの値がnameとallowed_domainsに自動で設定されます。
1 2 3 4 5 6 7 8 9 10 11 |
# -*- coding: utf-8 -*- import scrapy class SamplespiderSpider(scrapy.Spider): name = 'samplespider' allowed_domains = ['blog.scrapinghub.com'] start_urls = ['http://blog.scrapinghub.com/'] def parse(self, response): pass |
ちなみに、クラス名には”Spider”が自動でつきますので、この例のようにSpiderの名前を”samplespider”にしてしまうと、生成されるSpiderのクラス名が”SamplespiderSpider”と変な感じになってしまうのでご注意ください。
runspiderコマンドでSpiderを実行
では、作成したSpiderを以下のコマンドで実行してみましょう。
以下のように、scrapy runspider
コマンドでSpiderを実行できます。
$ scrapy runspider samplespider.py -o sampleitems.jl
-o FILENAME
オプションで、抽出したデータ(ScrapyではItemと呼ぶことが多いので、今後はItemで)の保存先を指定できます。
拡張子の”.jl”はJSON Lines形式のことで、各行にJSONオブジェクトを追記していくイメージです。
その場合、最新のScrapyにアップデートしてやりましょう。
ちなみに、アップデートのやり方は以下のコマンド。
$ pip install -upgrade scrapy
実行結果の確認
{“title”: “Do Androids Dream of Electric Sheep?”}
{“title”: “Deploy your Scrapy Spiders from GitHub”}
{“title”: “Looking Back at 2016”}
{“title”: “How to Increase Sales with Online Reputation Management”}
{“title”: “How to Build your own Price Monitoring Tool”}
…(中略)…
{“title”: “Scrapy 0.14 released”}
{“title”: “Dirbot \u2013 a new example Scrapy project”}
{“title”: “Introducing w3lib and scrapely”}
{“title”: “Scrapy 0.12 released”}
{“title”: “Spoofing your Scrapy bot IP using tsocks”}
{“title”: “Hello, world”}
こんな感じで、記事タイトルが最後のページまで全て抜き出せていたら成功です。
サンプルの解説
それでは、サンプル(samplespider.py)の挙動を詳しく見ていきます。
start_urlに指定したページを取得
まず、scrapy runspiderでsamplespider.pyが実行されると、start_urlに指定したページを取得します。
ページが取得できたら、その結果をscrapy.Responseオブジェクトに格納し、parse(self,response)メソッドを呼び出します。
parseメソッドの実行
ページ内にある記事タイトルの取得
parseメソッド内では、まず記事タイトルを抜き出すための処理を行います。
response.css(‘h2.entry-title’)メソッドで、指定したcssセレクタにマッチする要素を取得し、SelectorListオブジェクトとして返します。
そしてfor文内でSelectorListのそれぞれの要素(title)に対しtitle.css(‘a ::text’).extract_first()を実行し、記事タイトルの文字列を取得しyieldで都度結果を返しています。
html要素のテキストを取得するには、疑似クラス”::text”を使ってテキストノードを取得してから、”extract()”メソッドを実行します。
複数のノードからテキストを取得するにはextract()を使用する。結果は文字列のリスト。
1つのノードからテキストを取得するにはextract_first()を使用する。結果は文字列。
次のページへのリンクを取得
現在のページにある記事タイトルをすべて取得したら、次のページに移動するためにリンクをたどります。
次ページへのリンクは、response.css(‘div.prev-post > a’)で取得し、next_pageに格納します。
そして、取得したリンクを自身のparseメソッドに渡し、次ページへのリンクがなくなる=最後のページになるまで再帰的にparseを実行し続けることで、最後までページをたどってすべての記事タイトルを取得することができます。
感想とか
始めにちょっとしたお作法とかを覚える必要はありますが、Scrapyを使うと短いコードで簡単にスクレイピングができるので便利です。
今回のサンプルみたいに、ブログの記事タイトルを抜き出すくらいじゃたいした役には立ちませんが、例えばGoogle検索結果の上位ページからテキストを抜き出し、形態素解析にかけたりなんやかやしてSEOの参考にしたりとか、特定のページを定点観測してゴニョったりとか、AIの学習用データをWebから自動で集めたりとか、いろいろ応用が利く分野だと思います。
ただ何も考えずにクローリングしまくると、相手先に迷惑をかけてしまうことになるので注意が必要です。
クローリングの実行には最低でも1秒以上の間隔をあけるなど、最低限のマナーを守るようにしましょう。
Scrapyにはクロール間隔の調整とか、robots.txtの指示に従うようにするとか、クロール先に迷惑をかけないための設定も用意されているので、また次回以降でまとめたいと思います。
参考にしたもの
Scrapy
https://scrapy.org/
How do I update Scrapy from the Terminal? – StackOverFlow
https://stackoverflow.com/questions/21258961/how-do-i-update-scrapy-from-the-terminal