note.nkmk.me

Scrapyのクローリング処理を外部のスクリプトから実行する

Date: 2018-07-29 / tags: Python, 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_URLを指定することでファイル出力を指定できる。

-oオプションと同様に拡張子jsoncsvに従ってそのフォーマットのファイルが出力される。

FEED_URLsetting.pyで指定しておくことも可能。常に同じ設定で問題なければsetting.pyに書いておいたほうがスクリプト内で指定する必要がないので楽。

ローカルファイルへの保存だけでなくftpサーバーに保存したりAmazon S3に保存したりすることもできる。

FEED_URIには変数を使うことも可能。デフォルトで以下の2つが使える。

  • %(time)s: タイムスタンプ
  • %(name)s: スパイダー名

そのほかスパイダーのクラスの属性値も使用できる。変数を使わずパスを指定すると結果が一つのファイルに追記されていってしまうが、変数を使うと結果を別々のファイルに出力して保存できるので便利。

サンプルコードではrankfilename%(filename)s%(rank)sとして使っている。

filenameについてはSearchArgsSpiderクラスの定義では一切登場していないが、process.crawl()のキーワード引数として指定したキーワードと値はクラスの属性とその値として格納されるという仕組みを利用している。

例の場合、引数querySearchArgsSpiderクラスの__init__()の中でself.queryに格納していないのでクラスの属性として定義されていない。このためFEED_URIの変数としては使えない。FEED_URIの変数として使いたい場合は、__init__()self.queryに格納しておく。

引数についての詳細は以下の記事を参照。

スポンサーリンク
シェア
このエントリーをはてなブックマークに追加

関連カテゴリー

関連記事