Scrapy でクローリング、スクレイピングする
寒くなってきましたね。23日から年末年始休暇で12連休なのに何も予定がないので近所のドトールで黙々とブログ書いています。
さて、今回は先日、クローリング、スクレイピングを実装する機会があったので、その時に利用した Scrapy に関して調べた備忘録です。
Scrapy
Scrapy は Python 実装によるクローリング、スクレイピングを簡単に構築するためのフレームワークです。
HTTP リクエストや、スケジューラがフレームワークとして用意されており、利用者はフレームワークのルールに則ることでコード量少なく、クローリング、スクレイピングを実装することができます。利用者はどのページにアクセスするのか、アクセスしたページから何をどのように抽出するかを記述するだけで欲しい結果を取得することができます。
以前、requests、BeautifulSoupで実装したことがありますが、Scrapy 知れば良かった。。
今回は簡単に本ブログから記事タイトルを取得してみましょう。
対象読者
今回の目的
Scrapy を利用して本ブログから記事タイトルを抽出、出力する
環境
初期設定
Scrapy インストール
今回は pip にて Scrapy をインストールしました。
$ pip install scrapy : Successfully installed cryptography-1.7.1 idna-2.2 lxml-3.7.1 scrapy-1.3.0 setuptools-32.3.0 zope.interface-4.3.3
Scrapy 初期設定
Scrapy はサブコマンドで設定、実行することができます。初期設定はscrapy startprojectコマンドを実行すると、対話形式で設定値を入力することで最低限必要なファイル群を生成することができます。
$ scrapy startproject first_scrapy
New Scrapy project 'first_scrapy', using template directory 'XXXXXXXXX/.pyenv/versions/3.5.1/envs/scrapy/lib/python3.5/site-packages/scrapy/templates/project', created in:
XXXXXXXXXXX/scrapy/first_scrapy
You can start your first spider with:
cd first_scrapy
scrapy genspider example example.com
$ cd first_scrapy
$ tree .
.
├── first_scrapy
│ ├── __init__.py
│ ├── __pycache__
│ │ ├── __init__.cpython-35.pyc
│ │ └── settings.cpython-35.pyc
│ ├── items.py
│ ├── middlewares.py
│ ├── pipelines.py
│ ├── settings.py
│ └── spiders
│ ├── __init__.py
│ └── __pycache__
│ └── __init__.cpython-35.pyc
└── scrapy.cfg
4 directories, 10 files
実装
Spider 実装
Spider はどのURLへアクセスするか、アクセスした結果をどのように扱うか、つまりやりたいことを実装します。最低限 Spider を実装することでクローリング、スクレイピングできます。Spider はscrapy genspiderコマンドによりテンプレートから生成できます。
$ scrapy genspider tech sfujimoto.hatenablog.com Created spider 'tech' using template 'basic' in module: first_scrapy.spiders.tech
テンプレートからどのようなコードができたのか見てみましょう。
$ cat first_scrapy/spiders/tech.py
# -*- coding: utf-8 -*-
import scrapy
class TechSpider(scrapy.Spider):
name = "tech"
allowed_domains = ["sfujimoto.hatenablog.com"]
start_urls = ['http://sfujimoto.hatenablog.com/']
def parse(self, response):
pass
allowed_domainsはクローリング先としてアクセスしてもよい FQDN を設定します。誤ったサイトへアクセスが多発したら、サイトへ迷惑をかけることになりますが、ここで指定した FQDN 以外への URL アクセスを防止することができます。クローリングはマナーが大事です。start_urlsは最初にアクセスする URL を指定します。例えば、アクセス先にページングがあり、複数ページから取得する場合、start_urlsは最初のページだけ取得し、次のページの URL を抽出することで次のページの URL にリクエストを送信することも可能です。
Scrapy を実行すると、start_urlsへHTTPリクエストした結果がresponseに渡され、parseメソッドが処理されます。なので、今回の例であれば、parseメソッドにタイトルを抽出し、出力する処理を書くだけです。
XPATH
parse メソッドを実装する前に HTML をスクレイピングする XPATH について説明します。Scrapy はいくつかのスクレイピング方法があるのですが、今回は XPATH を利用しました。XPATH は XML からパスを指定することでタグや、属性、値を抽出する記法です。
例
例えば、以下のような XML から色々と抽出するパスの書き方を説明します。
<A>
<B id="1" class="test">
<C>TEXT</C>
</B>
<B id="2">
<D>MESSAGE</D>
</B>
</A>
絶対パスでTEXTを抽出する
/A/B[1]/C/text()
短縮系でTEXTを抽出する
//C/text()
※ 全てのCタグを抽出するので、Cタグが複数ある場合は同じ結果にはなりません。
属性でフィルタして、TEXTを抽出する
//B[@id="1"]/C/text()
属性でアンド条件でフィルタして、TEXTを抽出する
//B[@id="1" and @class="test"]/C/text()
簡単な例ですが、様々な抽出方法があります。
タイトルのルール(XPATH)を見つける
抽出したい文字や、URL の XPATH のパスは HTML を読み解いて、パスを探すしかありません。。私はWebブラウザに Firefox を利用していて、Firebug、FirePath を利用することで自動で XPATH を生成することができました。
ちなみに今回利用する XPATH は以下となります。
//article/div/header/h1/a/text()
実装
それでは XPATH がわかったところで、抽出したタイトルを出力する実装をしましょう。
# -*- coding: utf-8 -*-
import scrapy
class TechSpider(scrapy.Spider):
name = "tech"
allowed_domains = ["sfujimoto.hatenablog.com"]
start_urls = ['http://sfujimoto.hatenablog.com/']
def parse(self, response):
titles = response.xpath('//article/div/header/h1/a/text()')
for title in titles:
print(title)
下3行を追加しただけです。簡単!
xpath の結果は extract() で展開することができます。それを print で標準出力しているだけです。
動作確認
実装が終わったら、Scrapy を実行して動作確認します。scrapy runspiderで Spider を実行することができます。
$ scrapy runspider --nolog first_scrapy/spiders/tech.py Elasticsearch の概要を調べてみた AWS CloudFormation AWS S3 AWS ElasticCache AWS ELB 「Amazon Web Services徹底活用ガイド」を読みました 脱vSphereClient (Apple信者向け)
記事タイトルが出力されました!
まとめ
いかがでしたでしょうか。たった 3行の実装で簡単にクローリング、スクレイピングを実装することができました。今回は非常に簡単な例ですが、Scrapy は複雑な要件にも対応しており多機能です。次回はもっと深掘りしたいと思います。