work.log

元エンジニアの備忘録的ブログ

Scrapydを使ってScrapy製のクローラーをデーモン化し定期実行する

投稿:2019-03-15 13:21  更新:

Scrapyで作成したクローラー (スパイダー) をScrapydというクローラー管理APIを使って制御するメモです。

クローラーをバッググラウンドで動かしたい場合、cronにコマンドを登録して定期実行というのがお手軽な方法ですが、他のアプリケーションからスパイダーを起動させたり、重複起動しないように制御したい場合はちょっと大変です。

しかしScrapydを使えば、手製のアプリケーションからAPIを叩いてリアルタイムにクローラーを走らせる事も可能だし、複数あるクローラーのスケジューリングもScrapydがやってくれるので便利です。

「CentOS7 + Python 3.7.2 (pyenv) + Scrapy 1.6.0 + scrapyd 1.2.0」という環境でクローラーをデーモン化するまでをやってみます。

Scrapydのインストールと設定

Scrapydを使うために scrapyd, scrapyd-client というPythonモジュールをインストールします。

# pip install scrapyd scrapyd-client

Scrapydの設定ファイルはLinuxの場合、下記の順番でファイルを探すらしいです。

  1. /etc/scrapyd/scrapyd.conf
  2. /etc/scrapyd/conf.d/*
  3. ./scrapyd.conf
  4. ~/.scrapyd.conf

今回はScrapyのプロジェクトディレクトリ内にScrapyd用のディレクトリを作成しそこで管理する事にしました。

$ mkdir /home/miura/ScrapyProject/scrapyd
$ vi /home/miura/ScrapyProject/scrapyd/scrapyd.conf

----- scrapyd.conf -----
[scrapyd]
eggs_dir    = /home/miura/ScrapyProject/scrapyd/eggs
logs_dir    = /home/miura/ScrapyProject/scrapyd/logs
items_dir   =
jobs_to_keep = 5
dbs_dir     = /home/miura/ScrapyProject/scrapyd/dbs
max_proc    = 1
max_proc_per_cpu = 4
finished_to_keep = 100
poll_interval = 5.0
bind_address = 127.0.0.1
http_port   = 6800
debug       = on
runner      = scrapyd.runner
application = scrapyd.app.application
launcher    = scrapyd.launcher.Launcher
webroot     = scrapyd.website.Root

[services]
schedule.json     = scrapyd.webservice.Schedule
cancel.json       = scrapyd.webservice.Cancel
addversion.json   = scrapyd.webservice.AddVersion
listprojects.json = scrapyd.webservice.ListProjects
listversions.json = scrapyd.webservice.ListVersions
listspiders.json  = scrapyd.webservice.ListSpiders
delproject.json   = scrapyd.webservice.DeleteProject
delversion.json   = scrapyd.webservice.DeleteVersion
listjobs.json     = scrapyd.webservice.ListJobs
daemonstatus.json = scrapyd.webservice.DaemonStatus

max_proc はデフォルトだと 0 で無制限にクローラーを同時実行してしまうので、サーバスペックと相談していくつ同時に動かすかを設定する。

適当なディレクトリで scrapyd コマンドを実行すればScrapydが起動して 127.0.0.1:6800 でLitenしますが、設定ファイルを読み込まないで起動させるとdbsディレクトリとかをカレントディレクトリに勝手に作るので注意。

この例で手動実行したい場合は “cd /home/miura/ScrapyProject/scrapyd ; scrapyd” すれば scrapyd.conf を読み込んで起動します。

Scrapydのサービス登録と起動

ScrapydのデーモンはCentOS7のsystemdで管理します。

# vi /etc/systemd/system/scrapyd.service

----- scrapyd.service -----
[Unit]
Description=Scrapyd daemon
After=network.target

[Service]
WorkingDirectory=/home/miura/ScrapyProject/scrapyd
ExecStart=/bin/bash -c '/usr/local/pyenv/versions/3.7.2/bin/python3.7 /usr/local/pyenv/versions/3.7.2/bin/scrapyd'
Restart=always
User=miura
Group=miura

[Install]
WantedBy=multi-user.target

WorkingDirectoryは scrapyd.conf のあるディレクトリ、ExecStartにはpython3とscrapydのパスを書きますがここは適時変更する事。

クローラーを特定のユーザー権限で動かしたい場合は User, Group も設定しておく。

ここまで書いたらサービス登録してScrapydをバッググラウンドで起動させる。

# systemctl enable scrapyd
# systemctl start scrapyd

psしてプロセスがいたら成功です。

# ps auxw | grep scrapyd
miura     30887  0.0  1.9 285892 40364 ?        Ss   11:33   0:02 /usr/local/pyenv/versions/3.7.2/bin/python3.7 /usr/local/pyenv/versions/3.7.2/bin/scrapyd

一応、停止と再起動はこのように。

# systemctl stop scrapyd
# systemctl restart scrapyd

Scrapydの管理画面にアクセスする

Scrapydは簡単な管理画面を持っていて、各クローラーのステータスや動作ログが確認できます。

デフォルトでは http://127.0.0.1:6800 で管理画面にアクセスできます。

Scrapydにアクセスした時のトップページ

Scrapydにジョブを登録してクローラーを走らせる

Scrapydにクローラーのジョブを管理させるにはまず scrapyd-deploy コマンドでスパイダーをデプロイします。

$ cd /home/miura/ScrapyProject
$ scrapyd-deploy -p ScrapyProject
Packing version 1552571177
Deploying to project "ScrapyProject" in http://localhost:6800/addversion.json
Server response (200):
{"node_name": "myhostname", "status": "ok", "project": "ScrapyProject", "version": "1552571177", "spiders": 3}

次に curl コマンドを使ってAPIにジョブを登録します。

$ curl http://localhost:6800/schedule.json -d project=ScrapyProject -d spider=mySpider
{"node_name": "myhostname", "status": "ok", "jobid": "c2624744465f11e9ae3b9ca3ba021a29"}

上記で登録したプロジェクト名、スパイダー名と違いますが、登録後はScrapydの管理画面にこのように表示されます。

Scrapydにジョブを登録した時の管理画面

空いていれば登録したジョブは即座に実行されて、他のジョブを実行中の場合は後に登録したクローラーは待機します。

後はcronとかでAPIにジョブを定期的に登録してあげればScrapydがよしなにクローラーを実行してくれます。

max_proc_per_cpu と jobs_to_keep の設定も大事だと思いますが、設定を変えても何処に効いているのかがイマイチわかりませんでした。

何はともあれクローラーの多重起動をこれで制御できるようになったのでScrapyがますます便利になりました。

Scrapyd利用時のエラー集

Scrapydを動かした時に発生したエラーをここにまとめておきます。

TypeError: __init__() got an unexpected keyword argument ‘_job’

エラー内容
TypeError: __init__() got an unexpected keyword argument ‘_job’

■ 対処方法

Scrapydはジョブの実行時に _job の引数をスパイダーに渡すようで、中で __init__() を利用している場合は可変長引数 (*args, **kwargs) を付ける。

■ NG

class MySpider(scrapy.Spider):
        name = 'myspider'

        def __init__(self):

■ OK

class MySpider(scrapy.Spider):
        name = 'myspider'

        def __init__(self, *args, **kwargs):

Scrapydはこのようにクローラーを実行するので、_job にはジョブを識別できるIDが渡されるようです。

/usr/local/pyenv/versions/3.7.2/bin/python3.7 -m scrapyd.runner crawl MySpider -a _job=bb1d1820484811e9a41c9ca3ba021a29

Connection to the other side was lost in a non-clean fashion: Connection lost.

エラー内容
twisted.python.failure.Failure twisted.internet.error.ConnectionLost: Connection to the other side was lost in a non-clean fashion: Connection lost.

ScrapydのAPIからRunning中のクローラーのキャンセルリクエストを送ると、少し待ってログにこのエラーが出てクローラーがハングする。

$ curl http://localhost:6800/cancel.json -d project=ScrapyProject -d job=5915184246c911e996c69ca3ba021a29

正確にはクローラーは異常終了して、クローラーが呼び出したSeleniumのプロセスだったり、Scrapyd側のジョブステータスはその状態で止まりゾンビ化します。

クローラーがまだPendingであれば問題なくキャンセルできるので、もしかするとRunning中のクローラーへ使うものでは無い?

Seleniumとか他のプロセスが原因で、Scrapydからのクローズ処理が正常に走らないのかも知れない。(引き続き調べる)

とりあえずScrapydをrestartすればゾンビも消えて元には戻る。

よく読まれている記事

  • 本日
  • 週間
  • 月間