Scrapyのクローリング処理を外部のスクリプトから実行する
scrapy crawl
コマンドで実行できるScrapyのクローリング処理を外部のスクリプトから制御できると何かと便利。特に引数を指定して処理を制御できるスパイダーを定義しておくと、異なる引数でのクローリング処理を自動で実行したりできる。
公式ドキュメントの説明は以下。
ここではサンプルコードとともに以下の内容について説明する。
- Scrapyを外部から実行する二通りの方法
- 新規にスパイダーを定義
- 既存のScrapyプロジェクトを利用
- 既存のScrapyプロジェクトを利用するサンプルコード
- 実行するスパイダースクリプト
- 複数のスパイダーを同時に実行
- 複数のスパイダーを順番に実行
- 出力ファイル名を動的に指定
プロジェクトの作成など基本的な部分は以下の記事を参照。
プロジェクトのリポジトリは以下。
Scrapyを外部から実行する二通りの方法
Scrapyのクローリング処理を外部のスクリプトから実行するには以下の二通りの方法がある。
新規にスパイダーを定義
スクリプト内でスパイダーのクラスを定義する。
CrawlerProcess()
に設定を指定してインスタンスprocess
を作成し、process.crawl()
に定義したスパイダーのクラスと指定する。
公式ドキュメントのサンプルコードは以下の通り。
import scrapy
from scrapy.crawler import CrawlerProcess
class MySpider(scrapy.Spider):
# Your spider definition
...
process = CrawlerProcess({
'USER_AGENT': 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)'
})
process.crawl(MySpider)
process.start() # the script will block here until the crawling is finished
既存のScrapyプロジェクトを利用
既存のScrapyプロジェクトのスパイダーを呼び出して実行することも可能。
get_project_settings()
でプロジェクトのsettings.py
の内容を読み込み、process.crawl()
ではスパイダーの名前を指定する。
公式ドキュメントのサンプルコードは以下の通り。
from scrapy.crawler import CrawlerProcess
from scrapy.utils.project import get_project_settings
process = CrawlerProcess(get_project_settings())
# 'followall' is the name of one of the spiders of the project.
process.crawl('followall', domain='scrapinghub.com')
process.start() # the script will block here until the crawling is finished
この場合、スクリプトを実行するカレントディレクトリがプロジェクトディレクトリ(scrapy.cfg
があるディレクトリ)でなければならないので注意。
Pythonにおけるカレントディレクトリについては以下の記事を参照。
特に制約がなければプロジェクトディレクトリ(scrapy.cfg
があるディレクトリ)にスクリプトファイルを置いてそのディレクトリから実行すれば問題ない。
以下、既存のScrapyプロジェクトを利用する場合についてサンプルコードを挙げて説明する。
既存のScrapyプロジェクトを利用するサンプルコード
実行するスパイダースクリプト
外部のスクリプトから呼び出すスパイダーは以下の通り。
# -*- coding: utf-8 -*-
import scrapy
class SearchArgsSpider(scrapy.Spider):
name = 'search_args'
allowed_domains = ['yahoo.co.jp']
def __init__(self, query='', rank=0, *args, **kwargs):
super(SearchArgsSpider, self).__init__(*args, **kwargs)
self.start_urls = ['https://search.yahoo.co.jp/search?p=' + query]
self.rank = int(rank)
def parse(self, response):
w = response.css('div#WS2m div.w')[self.rank]
d = {}
d['rank'] = self.rank
d['comment'] = self.comment
d['title'] = w.css('h3 a').xpath('string()').extract_first()
d['url'] = 'https://' + w.css('div.a span.u').xpath('string()').extract_first()
yield d
scrapy crawl
のコマンドライン引数を受け取るスパイダーになっている。詳細は以下の記事を参照。
以下、このスパイダーを実行するスクリプトの例を示す。
上述のように、スクリプトを実行するカレントディレクトリがプロジェクトディレクトリ(scrapy.cfg
があるディレクトリ)でなければならないので注意。
複数のスパイダーを同時に実行
複数のスパイダーを同時に実行するスクリプトのサンプルを示す。
from scrapy.crawler import CrawlerProcess
from scrapy.utils.project import get_project_settings
settings = get_project_settings()
settings.set('FEED_URI', 'results/search_%(filename)s_%(rank)s.csv')
process = CrawlerProcess(settings)
process.crawl('search_args', query='python', rank=0, comment='test', filename='python')
process.crawl('search_args', query='ruby', rank=0, comment='test', filename='ruby')
process.start()
ほぼ上述の公式のサンプルコードのままだが、外部ファイルへの出力のためにFEED_URI
設定を指定している。これについては後述。
scrapy crawl
のコマンドライン引数で指定する値はprocess.crawl()
のキーワード引数として指定する。
process.crawl()
を複数回呼んでいるが、これはもちろんforループを使ってもOK。
複数のスパイダーを順番に実行
実際に実行してみると分かるが、上の例は複数のスパイダーが同時に実行される。
特に多数のスパイダーを実行したい場合は対象のサーバーへの影響を考慮すると同時に実行されるよりも順番に一つずつ実行されるほうが望ましい。
以下のように書く。@defer.inlineCallbacks
でデコレータを指定して関数を定義し実行する。
from twisted.internet import reactor, defer
from scrapy.crawler import CrawlerRunner
from scrapy.utils.log import configure_logging
from scrapy.utils.project import get_project_settings
settings = get_project_settings()
settings.set('FEED_URI', 'results/search_%(filename)s_%(rank)s.csv')
configure_logging()
runner = CrawlerRunner(settings)
@defer.inlineCallbacks
def crawl():
yield runner.crawl('search_args', query='python', rank=0, comment='test', filename='python')
yield runner.crawl('search_args', query='ruby', rank=0, comment='test', filename='ruby')
reactor.stop()
crawl()
reactor.run()
この場合もforループで回したい場合はyield runner.crawl()
を繰り返せばOK。
出力ファイル名を動的に指定
scrapy crawl
コマンドでクローリングを実行する場合は-o
オプションで結果をファイルに出力できる。
スクリプトから実行する場合はFEED_URI
を指定することでファイル出力を指定できる。
-o
オプションと同様に拡張子json
やcsv
に従ってそのフォーマットのファイルが出力される。
FEED_URI
はsetting.py
で指定しておくことも可能。常に同じ設定で問題なければsetting.py
に書いておいたほうがスクリプト内で指定する必要がないので楽。
ローカルファイルへの保存だけでなくftpサーバーに保存したりAmazon S3に保存したりすることもできる。
- Feed exports Storage backends — Scrapy 1.5.1 documentation
- ftp
- 例:
ftp://user:pass@ftp.example.com/path/to/export.csv
- 例:
- Amazon S3
- 例:
s3://mybucket/path/to/export.csv
- 別途アクセスキーの設定が必要
- 例:
- ftp
FEED_URI
には変数を使うことも可能。デフォルトで以下の2つが使える。
%(time)s
: タイムスタンプ%(name)s
: スパイダー名
そのほかスパイダーのクラスの属性値も使用できる。変数を使わずパスを指定すると結果が一つのファイルに追記されていってしまうが、変数を使うと結果を別々のファイルに出力して保存できるので便利。
サンプルコードではrank
とfilename
を%(filename)s
と%(rank)s
として使っている。
filename
についてはSearchArgsSpider
クラスの定義では一切登場していないが、process.crawl()
のキーワード引数として指定したキーワードと値はクラスの属性とその値として格納されるという仕組みを利用している。
例の場合、引数query
はSearchArgsSpider
クラスの__init__()
の中でself.query
に格納していないのでクラスの属性として定義されていない。このためFEED_URI
の変数としては使えない。FEED_URI
の変数として使いたい場合は、__init__()
でself.query
に格納しておく。
引数についての詳細は以下の記事を参照。