Python, Scrapyの使い方(Webクローリング、スクレイピング)
PythonのWebクローリングとスクレイピングのフレームワークであるScrapyの使い方をサンプルコードとともに説明する。
以下の内容について説明する。具体例はYahoo! Japanを対象としている。
- クローリングとスクレイピング
- ScrapyとBeautifulSoupの違い
- Scrapyのインストール
- Scrapyの基本的な使い方
- 単独のページをスクレイピングする例
- リンクを指定してクローリング、スクレイピングする例
- リンクを抽出してクローリング、スクレイピングする例
- リンクを抽出して再帰的にクローリング、スクレイピングする例
Scrapyのバージョンは1.5.0
。バージョン1.1
からPython3にも対応している。
レポジトリは以下。
クローリングとスクレイピング
クローリングは「Webページのリンクをたどって巡回し、それぞれのページをダウンロードすること」で、クローリングのためのプログラムをクローラーやボット、スパイダーなどと呼ぶ。
スクレイピングは「ダウンロードしたWebページ(htmlファイルなど)を解析して必要な情報を抜き出すこと」。
ScrapyとBeautifulSoupの違い
BeautifulSoupはスクレイピングのためのライブラリで、ダウンロードしたhtmlファイルなどから必要な部分を抽出することができる。スクレイピング以外の処理、例えばhtmlファイルをダウンロードしたりリンクを辿ったりする処理は自分で用意する必要がある。
Scrapyはスクレイピングだけでなくクローリングも行う。
Webページのリンクを再帰的にたどって巡回(クローリング)し、各ページに対して所定の処理を行って必要な情報を抽出(スクレイピング)することができる。JSONやXML、CSVなどのファイル出力も簡単。
複数ページを対象とするのならScrapyのほうが便利。
Scrapyのインストール
Scrapyのインストールの公式説明ページは以下。
他のライブラリと同様にpip
(環境によってはpip3
)でインストールできる。
$ pip install scrapy
AnacondaやMinicondaを使っている場合は、conda
でインストールできる。
$ conda install -c conda-forge scrapy
特にWIndowsでゼロから環境構築する場合はAnacondaのインストーラーを使うのが楽だと思う。
Scrapyの基本的な使い方
公式のチュートリアルで使われているサイトを例にScrapyの基本的な使い方を説明する。
以下のような流れ。
scrapy startproject
でプロジェクト作成scrapy genspider
でスパイダーの雛形作成- itemsを定義しスパイダーに処理を追加
settings.py
に各種設定を記述scrapy crawl
で実行、ファイル出力
scrapy shell
によるデバッグについても述べる。
scrapy startprojectでプロジェクト生成
scrapy startproject
コマンドでScrapyプロジェクトを作成する。
[project_dir]
に<project_name>
というプロジェクトを生成するコマンドは以下の通り。[project_dir]
を省略するとカレントディレクトリにプロジェクトが生成される。
$ scrapy startproject <project_name> [project_dir]
ここでは、任意のディレクトリでtutorial
という名前のプロジェクトを作成する。
$ scrapy startproject tutorial
以下のような構造のディレクトリとファイルが生成される。tutorial
というプロジェクトのディレクトリの中に同名(tutorial
)のサブディレクトリが生成される。
tutorial
├── scrapy.cfg
└── tutorial
├── __init__.py
├── __pycache__
├── items.py
├── middlewares.py
├── pipelines.py
├── settings.py
└── spiders
├── __init__.py
└── __pycache__
scrapy genspiderでスパイダーの雛形作成
以降のコマンドは生成されたプロジェクトディレクトリで実行するので、scrapy startproject
から続けて実行する場合はcd
コマンドで移動する。
$ cd tutorial
scrapy genspider
コマンドでスパイダーの雛形を作成する。スパイダーはボットやクローラーとも呼ばれるクローリングのためのプログラム。
<name>
という名前で<domain>
を対象ドメインとするスパイダーを生成するコマンドは以下の通り。-t
オプションでテンプレートを指定できる。
$ scrapy genspider [-t template] <name> <domain>
ここでは上述の名言サイトquotes.toscrape.com
を対象とするquotes
という名前のスパイダーを生成する。-t
は省略してデフォルトのまま。テンプレートを指定する例は後述。
$ scrapy genspider quotes quotes.toscrape.com
tutorial/tutorial/spiders/quotes.py
が生成される。内容は以下の通り。
スパイダーの名称name
や対象ドメインallowed_domains
、クロールのスタート地点となるURLstart_urls
などの変数に適当な値が設定されている。
# -*- coding: utf-8 -*-
import scrapy
class QuotesSpider(scrapy.Spider):
name = 'quotes'
allowed_domains = ['quotes.toscrape.com']
start_urls = ['http://quotes.toscrape.com/']
def parse(self, response):
pass
start_urls
のURLからクロールが始まる。start_urls
はリストになっているので、複数のURLを指定することも可能。
取得したHTMLソースがparse()
メソッドの第二引数response
にscrapy.http.response.html.HtmlResponse
オブジェクトとして渡される。
このparse()
メソッドに処理を追加していく。
genspider
は雛形を生成するだけ。自分でゼロからスクリプトを作成してspiders
ディレクトリにおいてもOK。
itemsを定義しスパイダーに処理を追加
items定義
サイトから収集したい情報をtutorial/tutorial/items.py
に記述する。
プロジェクトを作成した時点でitems.py
には雛形が記述されている。
# -*- coding: utf-8 -*-
# Define here the models for your scraped items
#
# See documentation in:
# https://doc.scrapy.org/en/latest/topics/items.html
import scrapy
class TutorialItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
pass
収集したい項目を<name>=scrapy.Field()
で追加すればOK。
class TutorialItem(scrapy.Item):
text = scrapy.Field()
author = scrapy.Field()
tags = scrapy.Field()
スパイダーに処理を追加
genspider
で作成したスパイダーquotes.py
を以下のように更新する。
# -*- coding: utf-8 -*-
import scrapy
from tutorial.items import TutorialItem
class QuotesSpider(scrapy.Spider):
name = 'quotes'
allowed_domains = ['quotes.toscrape.com']
start_urls = ['http://quotes.toscrape.com/']
def parse(self, response):
for quote in response.css('div.quote'):
item = TutorialItem()
item['author'] = quote.css('small.author::text').extract_first()
item['text'] = quote.css('span.text::text').extract_first()
item['tags'] = quote.css('div.tags a.tag::text').extract()
yield item
items.py
で定義したクラスTutorialItem
をインポートして使えるようにし、parse()
に処理を追加する。
response
のメソッドcss()
にCSSセレクタを指定することでDOM要素を絞り込んで取得できる。
::text
でテキスト部分を選択し、さらにextract_first()
メソッドで文字列として取り出している。extract()
は選択されたオブジェクトをリストとして取り出すメソッド(要素が一個でもリスト)。tags
は複数個存在するのでextract()
を使う。
TutorialItem
のインスタンスitem
を作成し、抽出した値を格納していく。items.py
で定義したクラスは基本的に辞書(dict
オブジェクト)のように扱える。
CSSセレクタではなくXPathで要素を絞り込むxpath()
メソッドもある。後述のscrapy shell
を使うとどのようなCSSセレクタやXPathを指定すればよいかを試行錯誤しながら確認できる。
settings.pyに各種設定を記述
settings.py
に各種設定を記述する。プロジェクト作成時点で雛形が記述されているが、ほとんどの設定項目がコメントアウトされている。
設定項目についてのドキュメントは以下。
注意しておいたほうがいい項目は以下の通り。
USER_AGENT
- ユーザーエージェントによってレスポンスが異なるサイトの場合はここで適当な値を設定しておく。
- 次に例として示すYahoo! Japanでは設定の必要あり。
CONCURRENT_REQUESTS
- 同時リクエスト数。
- デフォルトは
16
。 - 対象サイトへの負荷が気になる場合は小さい値にしておく。
DOWNLOAD_DELAY
- リクエストごとの遅延秒数。
- デフォルトは
0
。 - 対象サイトへの負荷が気になる場合は
3
秒程度にしておく。
DEPTH_LIMIT
- リンクを辿る階層の数。例えば
DEPTH_LIMIT = 1
とするとスタートページからリンクを一回しかクロールしない。DEPTH_LIMIT = 2
とするとスタートページからリンクされているページ、および、そのページからリンクされているページまでクロールする。 - デフォルトは
0
で制限なし。 settings.py
の雛形にはないので指定する場合は追記する。- リンクを再帰的に辿るかどうかはスパイダーのスクリプトの記述で決まる。このチュートリアルの例ではスタートページを処理するのみで、リンクからクロールすることはない。後述の再帰的に辿る場合は
DEPTH_LIMIT
を指定しておかないと延々とリンクを辿っていく可能性があるので注意。
- リンクを辿る階層の数。例えば
scrapy crawlで実行、ファイル出力
scrapy crawl
コマンドでクローリング、スクレイピングを実行する。
<spider_name>
という名前のスパイダーを実行するコマンドは以下の通り。
$ scrapy crawl <spider_name>
<spider_name>
はスパイダーのファイル名ではなくスパイダークラスのname
で定義された名前。genspider
で生成した場合はファイル名とname
は同じになるが、自分でゼロから作成した場合は注意。
ここではquotes.py
のname
であるquotes
を指定する。
$ scrapy crawl quotes
エラーがなければスパイダーが起動しログが表示される。ログを表示させたくない場合は--nolog
オプションをつける。
収集した結果をファイルに保存したい場合は-o
オプションをつける。-o <file_name>
とすると、拡張子のフォーマットに自動的に変換され保存される。
.json
.xml
.csv
.jl
または.jsonl
⇒ JSON Lines.pickle
が自動的に認識される。
以下のように-t
でフォーマットを指定した上で-o stdout:
とすると結果が標準出力される。
$ scrapy crawl quotes -t csv -o stdout: --nolog
チュートリアルの結果は以下の通り。
CSVの先頭部分を示す。
author,tags,text
Albert Einstein,"change,deep-thoughts,thinking,world",“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”
J.K. Rowling,"abilities,choices","“It is our choices, Harry, that show what we truly are, far more than our abilities.”"
Albert Einstein,"inspirational,life,live,miracle,miracles",“There are only two ways to live your life. One is as though nothing is a miracle. The other is as though everything is a miracle.”
Jane Austen,"aliteracy,books,classic,humor","“The person, be it gentleman or lady, who has not pleasure in a good novel, must be intolerably stupid.”"
scrapy shellによるデバッグ
scrapy shell
コマンドでresponse
に対するcss()
やxpath()
メソッドによるスクレイピング結果を確認できる。
URLを指定して実行する。
$ scrapy shell <url>
インタラクティブシェルが起動するので、以下のようにparse()
の第二引数response
と同じオブジェクトresponse
を使ってコードを記述してその返り値を確認できる。
$ scrapy shell http://quotes.toscrape.com
In [1]: response.css('div.quote small.author::text').extract_first()
Out[1]: 'Albert Einstein'
終了するときはexit
またはquit
と入力する。
以下のように<user_agent>
にユーザーエージェントの文字列を指定できる。
$ scrapy shell -s USER_AGENT='<user_agent>' <url>
なお、CSSセレクタについては以下のMDNのドキュメントが詳しい。
とりあえず、
- クラスは
.classname
で指定 - IDは
#idname
で指定 A B
(A[スペース]B
)はA
要素の中にあるすべてのB
要素
ぐらいは覚えておくとよい。
Google Chromeの場合、抽出したい対象の部分を選択して、右クリック→検証でその部分のソースが開発者ツールに表示される。下側にhtml
要素から順番に要素名が書いてあるのでそれを参考にscrapy shell
で試してみるいいと思う。
単独のページをスクレイピングする例
以降はYahoo! JAPANを例とする。サイトの構成が変わるとサンプルコードが動かなくなる可能性があるので注意。2018年6月24日時点で動作確認をしている。
Yahoo! JapanはユーザーエージェントによってHTMLソースを切り替えている。
settings.py
のUSER_AGENT
を設定しておかないとブラウザで見ているソースが取得できない。以下の例ではGoogle Chromeのユーザーエージェントを指定している。
USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36'
ユーザーエージェントの文字列は以下のサイトなどで確認できる。確認したいブラウザでアクセスする。
まずは単独のページをスクレイピングする例を示す。scrapy startproject
でプロジェクトを作成。
$ scrapy startproject yahoo_japan
トップページのニュースのヘッドラインを抽出する。
scrapy genspider
でtopics
という名前のスパイダーを作成。
$ cd yahoo_japan
$ scrapy genspider topics yahoo.co.jp
items.py
を更新。
import scrapy
class YahooJapanItem(scrapy.Item):
headline = scrapy.Field()
url = scrapy.Field()
スパイダーのスクリプトtopics.py
を更新。上のチュートリアルの例とほぼ同じ。::text
ではなく::attr(属性名)
で属性の値を取得できる。リンクのURLを取得したい場合は::attr(href)
とする。
import scrapy
from yahoo_japan.items import YahooJapanItem
class TopicsSpider(scrapy.Spider):
name = 'topics'
allowed_domains = ['yahoo.co.jp']
start_urls = ['http://yahoo.co.jp/']
def parse(self, response):
for topic in response.css('div#topicsfb ul.emphasis li'):
item = YahooJapanItem()
item['headline'] = topic.css('a::text').extract_first()
item['url'] = topic.css('a::attr(href)').extract_first()
yield item
実行はscrapy crawl
コマンド。
$ scrapy crawl topics -o results/topics.csv
出力されたCSVの先頭部分を示す。
headline,url
蒸し暑さ到来か 月曜から注意,https://rdsig.yahoo.co.jp/_ylt=A2RiWbfSKy9bUAEAWo6JBtF7/RV=2/RE=1529904466/RH=cmRzaWcueWFob28uY28uanA-/RB=VavfPUkulHthHXVoX6gs1JDQneo-/RU=aHR0cHM6Ly9uZXdzLnlhaG9vLmNvLmpwL3BpY2t1cC82Mjg3NDQyAA--/RK=0/RS=_9gfqbCvAHIP84UmhBqIced1Oiw-
男性死亡 成田で逃走の男発見,https://rdsig.yahoo.co.jp/_ylt=A2RiWbfSKy9bUAEAW46JBtF7/RV=2/RE=1529904466/RH=cmRzaWcueWFob28uY28uanA-/RB=VavfPUkulHthHXVoX6gs1JDQneo-/RU=aHR0cHM6Ly9uZXdzLnlhaG9vLmNvLmpwL3BpY2t1cC82Mjg3NDQzAA--/RK=0/RS=4feMOELOwKrak.x0d.8l9ckBwM4-
カネミ油症「許して」母悔恨,https://rdsig.yahoo.co.jp/_ylt=A2RiWbfSKy9bUAEAXI6JBtF7/RV=2/RE=1529904466/RH=cmRzaWcueWFob28uY28uanA-/RB=VavfPUkulHthHXVoX6gs1JDQneo-/RU=aHR0cHM6Ly9uZXdzLnlhaG9vLmNvLmpwL3BpY2t1cC82Mjg3NDM3AA--/RK=0/RS=2fT.gQYqZNV7sKZQIA8jCfGb1N4-
菊池桃子氏にまたストーカー,https://rdsig.yahoo.co.jp/_ylt=A2RiWbfSKy9bUAEAXY6JBtF7/RV=2/RE=1529904466/RH=cmRzaWcueWFob28uY28uanA-/RB=VavfPUkulHthHXVoX6gs1JDQneo-/RU=aHR0cHM6Ly9uZXdzLnlhaG9vLmNvLmpwL3BpY2t1cC82Mjg3NDQwAA--/RK=0/RS=gQ1c8t0S_2wcaL1vCJF7GwnCHCU-
リンクを指定してクローリング、スクレイピングする例
コード中でリンクのURLを指定してリンク先の情報を取得する例として、トップページのニュースのヘッドラインのリンクを辿ってニュースタイトル(省略されていない見出し)を取得する。
scrapy genspider
でtopics_detail
という名前のスパイダーを作成。プロジェクトディレクトリで以下のコマンドを実行する。
$ scrapy genspider topics_detail yahoo.co.jp
items.py
にクラスを追加。
class YahooJapanDetailItem(scrapy.Item):
headline = scrapy.Field()
url = scrapy.Field()
title = scrapy.Field()
スパイダーのスクリプトtopics_detail.py
を更新。
import scrapy
from yahoo_japan.items import YahooJapanDetailItem
class TopicsDetailSpider(scrapy.Spider):
name = 'topics_detail'
allowed_domains = ['yahoo.co.jp']
start_urls = ['http://yahoo.co.jp/']
def parse(self, response):
for topic in response.css('div#topicsfb ul.emphasis li'):
item = YahooJapanDetailItem()
item['headline'] = topic.css('a::text').extract_first()
yield scrapy.Request(topic.css('a::attr(href)').extract_first(),
callback=self.parse_detail,
meta={'item': item})
def parse_detail(self, response):
item = response.meta['item']
item['url'] = response.url
item['title'] = response.css('div.topicsDetail h2 a::text').extract_first()
yield item
リンク先を読み込んで処理するにはscrapy.Request()
を使う。引数として。
- ヘッドラインのリンクのURL(読み込みたいページのURL)
callback
: リンク先のページに適用する関数meta
:response
オブジェクトに渡すオブジェクト(辞書形式で指定)- ここでは取得した情報を格納するオブジェクトを指定
を渡す。
meta
でオブジェクトを渡すあたりは分かりにくいので注意。公式ドキュメントの説明ページは以下。
また、トップページのヘッドラインのリンクのドメインはrdsig.yahoo.co.jp
でnews.yahoo.co.jp
にリダイレクトされる。allowed_domains
をwww.yahoo.co.jp
としている場合は別ドメインとみなされアクセスできない(Filtered offsite request
となる)。yahoo.co.jp
としておけばいずれのドメインも許可されるのでOK。
実行はscrapy crawl
コマンド。
$ scrapy crawl topics_detail -o results/topics.csv
出力されたCSVの先頭部分を示す。
headline,title,url
蒸し暑さ到来か 月曜から注意,月曜以降は「蒸し蒸し猛暑」到来か 梅雨明け前に暑さに注意,https://news.yahoo.co.jp/pickup/6287442
菊池桃子氏にまたストーカー,また菊池桃子さんにストーカー 元タクシー運転手を逮捕,https://news.yahoo.co.jp/pickup/6287440
松本人志 連れ去り企画を謝罪,松本人志、「水曜日のダウンタウン」芸人連れ去り企画問題を謝罪,https://news.yahoo.co.jp/pickup/6287438
セネガル戦 勝てばGL突破も,日本代表、セネガルに勝利でGL突破決定も…決勝T進出条件は?,https://news.yahoo.co.jp/pickup/6287441
リンクを抽出してクローリング、スクレイピングする例
上の例はリンクのURLをコードの中で取得しそれを指定してリンク先のページを読み込んだが、Scrapyではページ中のリンクを抽出して自動的にクロールする処理も可能。
トップページではなくYahoo!ニュースの各カテゴリページ(経済、スポーツなど)のヘッドラインを抽出する例を示す。
なお、この例のようにクロールするURLが固定されている場合はstart_urls
に対象URL(各カテゴリページのURL)をリストで指定しても所望の動作が可能。
以下のサンプルコードでは説明のためにリンクを自動的にクロールする処理を行っている。
items.py
にクラスを追加。
class YahooJapanNewsItem(scrapy.Item):
url = scrapy.Field()
headline = scrapy.Field()
category = scrapy.Field()
scrapy genspider
でnews_crawl
という名前のスパイダーを作成。プロジェクトディレクトリで以下のコマンドを実行する。-t crawl
とすることでページ内リンクをクロールするスパイダーの雛形が生成される。
$ scrapy genspider -t crawl news_crawl yahoo.co.jp
生成される雛形news_crawl.py
は以下の通り。-t crawl
によりデフォルトとは異なる雛形が生成される。
# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
class NewsCrawlSpider(CrawlSpider):
name = 'news_crawl'
allowed_domains = ['yahoo.co.jp']
start_urls = ['http://yahoo.co.jp/']
rules = (
Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True),
)
def parse_item(self, response):
i = {}
#i['domain_id'] = response.xpath('//input[@id="sid"]/@value').extract()
#i['name'] = response.xpath('//div[@id="name"]').extract()
#i['description'] = response.xpath('//div[@id="description"]').extract()
return i
これを以下のように更新する。
# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from yahoo_japan.items import YahooJapanNewsItem
class NewsCrawlSpider(CrawlSpider):
name = 'news_crawl'
allowed_domains = ['yahoo.co.jp']
start_urls = ['http://news.yahoo.co.jp/']
rules = (
Rule(
LinkExtractor(restrict_css='ul#gnSec'),
callback='parse_item'
),
)
def parse_item(self, response):
category = response.css('ul#gnSec li.current a::text').extract_first()
for topic in response.css('div#epTabTop ul.topics h1.ttl, p.ttl'):
item = YahooJapanNewsItem()
item['headline'] = topic.css('a::text').extract_first()
item['url'] = topic.css('a::attr(href)').extract_first()
item['category'] = category
yield item
rules
のRule
でクローリングのルールを指定する。
どのリンクをクロールするかのルールはRule
の第一引数LinkExtractor()
で指定する。
LinkExtractor()
の引数restrict_css
にCSSセレクタを指定すると選択された要素内のリンクのみがクロール対象となる。ここではYahoo!ニュース上部の各カテゴリページへのリンク部分を選択している。
LinkExtractor()
にはクロールするリンクのURLを正規表現で指定する引数allow
や範囲をXPathで指定する引数restrict_xpaths
などもある。いずれも複数値をリストで指定可能。
Rule
の引数callback
にリンク先を処理する関数名を指定する。
Rule
の引数follow
には、リンク先のページに含まれているリンクを再帰的にクロールするかをTrue
, False
で指定する。引数callback
に関数を指定した場合はデフォルトでfollow=False
となる(それ以上リンクを辿らない)。この例ではデフォルトでいいので引数follow
は指定していない。
引数callback
を指定しない場合はデフォルトでfollow=True
となる。次の例で示す。
なお、この例のようにrules
でRule
を指定した場合はRule
に合致したリンクがcallback
の関数で処理されるのみで、スタートページ(start_urls
のURLのページ)自体は処理対象とならない。スタートページにも何らかの処理を適用したい場合は、parse_start_url()
をオーバーライドする。
例えば以下のように記述する。
def parse_start_url(self, response):
return self.parse_item(response)
この例ではparse_start_url()
をオーバーライドしなくてもスタートページのヘッドラインが抽出されるが、それはスタートページに自身へのリンクが存在しているから。
また、rules
で指定したクローリングでは同一URLのページはデフォルトでスキップされるようになっている。各カテゴリページには各カテゴリページへのリンクがあるが、一度クロールしたページに再びクロールすることはない。
そのほか以下の点に注意。
start_urls
を雛形の値から変更している。rules
はタプルなので要素のRule
が一つの場合は末尾にコンマ,
が必要。Rule
の引数follow
などの設定を誤ると延々とリンクを辿ってしまう可能性があるので、まずはsettings.py
のDEPTH_LIMIT
を1
か2
くらいにしておいたほうが安全。
実行はscrapy crawl
コマンド。
$ scrapy crawl news_crawl -o results/topics.csv
結果は以下の通り。
出力されたCSVの先頭部分を示す。
category,headline,url
主要,蒸し暑さ到来か 月曜から注意,https://news.yahoo.co.jp/pickup/6287442
主要,男性死亡 成田で逃走の男発見,https://news.yahoo.co.jp/pickup/6287443
主要,カネミ油症「許して」母悔恨,https://news.yahoo.co.jp/pickup/6287437
主要,菊池桃子氏にまたストーカー,https://news.yahoo.co.jp/pickup/6287440
リンクを抽出して再帰的にクローリング、スクレイピングする例
さらに再帰的にリンクをクロールする例を示す。
Yahoo!ニュースの各カテゴリページのヘッドラインのリンク先から情報を取得する。
items.py
にクラスを追加。
class YahooJapanNewsDetailItem(scrapy.Item):
url = scrapy.Field()
title = scrapy.Field()
category = scrapy.Field()
scrapy genspider
でnews_crawl_detail
という名前のスパイダーを作成。
$ scrapy genspider -t crawl news_crawl_detail yahoo.co.jp
生成されたnews_crawl_detail.py
を以下のように更新する。
# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from yahoo_japan.items import YahooJapanNewsDetailItem
class NewsCrawlDetailSpider(CrawlSpider):
name = 'news_crawl_detail'
allowed_domains = ['yahoo.co.jp']
start_urls = ['http://news.yahoo.co.jp/']
rules = (
Rule(
LinkExtractor(restrict_css='ul#gnSec'),
),
Rule(
LinkExtractor(restrict_css='div#epTabTop ul.topics h1.ttl, p.ttl'),
callback='parse_item'
)
)
def parse_item(self, response):
item = YahooJapanNewsDetailItem()
item['url'] = response.url
item['title'] = response.css('div.topicsDetail h2 a::text').extract_first()
item['category'] = response.css('ul#gnSec li.current a::text').extract_first()
return item
rules
に2つのRule
を指定する。
1つ目は上の例と同じく上部の各カテゴリページへのリンク部分。これに対しては引数callback
を指定していないため、リンク先のカテゴリページに対しては関数で処理しない(情報を抽出しない)。引数callback
を指定していないと引数follow
がデフォルトでTrue
となるため、カテゴリページ内のリンクも再帰的にクローリング対象となる。
2つ目のRule
の第一引数に各カテゴリページのヘッドライン(個別ニュースページへのリンク)を対象とするCSSセレクタを指定し、引数callback
にリンク先(個別ニュースページ)を処理する関数を指定している。引数follow
はcallback
を指定した場合のデフォルトFalse
なので、それ以上はクロールしない。
実行はscrapy crawl
コマンド。
$ scrapy crawl news_crawl_detail -o results/topics.csv
出力されたCSVの先頭部分を示す。
category,title,url
国内,月曜以降は「蒸し蒸し猛暑」到来か 梅雨明け前に暑さに注意,https://news.yahoo.co.jp/pickup/6287442
地域,青森女性殺人 犯人どこに、動機は… 未解決のまま、過ぎていく時間 住民、不安な日々,https://news.yahoo.co.jp/pickup/6287446
国際,サウジアラビアで女性の運転解禁,https://news.yahoo.co.jp/pickup/6287444
地域,毒入り油で手料理 母悔恨「知らなかった」「ごめんね許して」 家族全員発症 カネミ油症50年の証言,https://news.yahoo.co.jp/pickup/6287437
この例の場合、
- スタートページ(Yahoo!ニュース(主要))→ カテゴリページ(経済など)→ 個別ニュースページ
- スタートページ(Yahoo!ニュース(主要))→ 個別ニュースページ
のいずれかのリンクを辿る。
settings.py
でDEPTH_LIMIT = 1
とすると下のルートでしかクローリングされないので、主要カテゴリのヘッドラインからリンクされている個別ニュースページの情報しか抽出されない。
また、上述のようにrules
で指定したクローリングでは同一URLのページはデフォルトでスキップされるようになっている。この例ではヘッドラインのリンク先(個別ニュースページ)へのアクセスもrules
で指定しているので、「主要」カテゴリと各カテゴリに同じニュースへのリンクがある場合、どちらか片方はスキップされる。
このため、上の例(「主要」カテゴリと各カテゴリが重複してリストアップされる)とは結果の項目数が異なる。