Scrapyのスケジューラscrapy-doを使ってみた

Scrapyを時刻起動させられるデーモン、[scrapy-do](https://scrapy-do.readthedocs.io/en/latest/index.html#)を試してみたので、記録しておきます。
ネット上に情報が少ないこともあり、少しでも貢献できれば幸いです。

## 環境情報

Python 3.7.7
Scrapy 2.1.0
scrapy-do 0.4.0

macOS Catalina 10.15.4

## scrapy-doについて
scrapy-doはLukasz Janyst氏が開発するScrapyの実行用デーモンです。
Scrapyのジョブを管理するコマンドライン、REST API、WebGUIが備わっています。
(公式ドキュメントは[こちら](https://scrapy-do.readthedocs.io/en/latest/))

Scrapyの実行デーモンとしては、[scrapyd](https://github.com/scrapy/scrapyd)が有名ですが、scrapy-doはscrapydにインスパイアされて開発したとのことです。

どちらも常駐プロセスとしてScrapyを実行しますが、scrapy-doは起動タイミングや実行間隔を持つ点がscrapydと大きく異なります。

scrapydでスケジュール管理をする時はcronやその他スケジューラを併用することになりますが、scrapy-doはジョブの登録時に「毎日t時に起動」のような指定が可能です。

## 早速使ってみる
### 前提事項
[Scrapyチュートリアル](https://doc-ja-scrapy.readthedocs.io/ja/latest/intro/tutorial.html)の`quotes_spider`の一部を題材にします。

### インストール
pipでscrapy-doをインストールします。

```bash

pip install scrapy-do
```

### 起動
任意のディレクトリで起動します。

```
scrapy-do -n scrapy-do
```

起動したディレクトリに `projects`というディレクトリが作成されます。
実行ログやスケジュール情報が置かれる場所です。

```
projects
├── spider-data
├── schedule.db
├── metadata.pkl
└── log-dir
```

### 実行環境設定

ジョブ実行の並列数やプロジェクトの保存先など、設定全般は設定ファイルで指定します。
詳細は公式ドキュメント[Server Configuration](https://scrapy-do.readthedocs.io/en/latest/server-configuration.html)を参照。

```bash
scrapy-do scrapy-do --config /path/to/config/file.conf
```

### プロジェクトのpush(コマンド)
Scrapyプロジェクトをpushします。
ここでいうプロジェクトとは、`scrapy startproject tutorial`の`tutorial`を意味しています。

pushの方法はcliとRESTの2通り。

まずはcliでpushしてみます。
デフォルトでカレントディレクトリのプロジェクトをpushしますが、`--project-path`でpush対象のパスを指定することも可能です。

```:push対象のディレクトリに入る
cd tutorial
```

```:pushする
scrapy-do-cl push-project
+------------+
| tutorial   |
|------------|
| quotes     |
+------------+
```

pushすると、プロジェクト内にあるスパイダーを見つけて表示してくれます。

さきほどのprojectsディレクトリを見てみると、pushされたtutorialプロジェクトが追加されているのがわかります。

```
projects
├── [  64]  log-dir
├── [ 140]  metadata.pkl
├── [ 20K]  schedule.db
├── [  64]  spider-data
└── [9.1K]  tutorial.zip
```

なお、scrapy-doではスパイダーを**更新した時はプロジェクトの再push**が必要です。
再pushと初回pushで実行方法に違いはありません。

### プロジェクトのpush(REST)
一応RESTでpushする場合も試してみます。

RESTでpushするときは、push対象のプロジェクトをzip圧縮してパラメータで指定します。

```bash

zip -r tutorial_archive.zip tutorial/
```

```bash

curl -s http://localhost:7654/push-project.json \
     -F archive=@tutorial_archive.zip | jq -r
{
  "status": "ok",
  "name": "tutorial",
  "spiders": [
    "quotes"
  ]
}
```

サーバ側のコンソールにはプロジェクトの受付ログが出力されています。

```
2020-05-24T10:12:14+0900 [scrapy_do.controller.Controller#info] Pushing new project
2020-05-24T10:12:15+0900 [scrapy_do.controller.Controller#info] Added project "tutorial" with spiders ['quotes']
2020-05-24T10:12:15+0900 [twisted.python.log#info] "127.0.0.1" - - [24/May/2020:01:12:14 +0000] "POST /push-project.json HTTP/1.1" 200 60 "-" "python-requests/2.23.0"
```

### ジョブのスケジューリング
ジョブの実行をスケジューリングしてみます。

特徴的なのが`--when`で指定する起動タイミング。
結構オシャレな指定方法になってます。

詳しくは公式ドキュメントの[Scheduling Specs](https://scrapy-do.readthedocs.io/en/latest/basic-concepts.html#scheduling-specs)を参照

- 'every monday at 12:30'
- 'every 2 to 3 hours'
- 'every 6 minutes'
- 'every hour at 00:15'

```bash:3分間隔の指定

scrapy-do-cl schedule-job --project tutorial \
    --spider quotes --when 'every 3 minutes'
+--------------------------------------+
| identifier                           |
|--------------------------------------|
| 3280bfb3-01df-49a5-aa53-805a5723df93 |
+--------------------------------------+
```

即時実行させたい時は `--when 'now'`と指定します。

### ジョブのキャンセル
登録したジョブをキャンセルしたい時は、cancel-jobコマンド or APIでジョブIDを指定します。

```bash
scrapy-do-cl cancel-job --job-id 3280bfb3-01df-49a5-aa53-805a5723df93
Canceled.
```

### ジョブの結果確認
ジョブの実行状況は、確認コマンド or APIで確認できます。

ステータス`ACTIVE`で実行中ジョブ、`COMPLETED`で終了したジョブを確認できます。

```bash
curl -s "http://localhost:7654/list-jobs.json?status=ACTIVE" | jq -r
```

```json
{
  "status": "ok",
  "jobs": [
    {
      "identifier": "ddd118dd-15f0-4448-8408-967fc806a859",
      "status": "SUCCESSFUL",
      "actor": "USER",
      "schedule": "now",
      "project": "tutorial",
      "spider": "quotes",
      "description": "",
      "timestamp": "2020-05-24 14:14:34.990209",
      "duration": 3,
      "payload": "{\"testkey\": \"hogehoge\"}"
    },
...
```

若干(というか結構)気になったのが、`--when 'every 3 minutes'`で指定したジョブは、実際に実行される時に別ジョブIDが発行されて、結果を見ると`"schedule": "now"`で実行されたことになっている点。

時刻が来ると`--when 'now'`で即時ジョブを登録するようになっているみたいです。

### Spiderに引数を渡す
スケジューリングの際に`payload`オプションを指定すると、Spiderに引数を渡せます。

指定できるのはvalidなJSON形式に限られており、Spider側からは`payload`という引数名で受け取ることができます。

```bash
curl -s http://localhost:7654/schedule-job.json \
       -F project=tutorial \
       -F spider=quotes \
       -F "when=now" \
       -F payload='{"hoge": "foobar"}' | jq -r
```

引数はJSON形式のstringで渡ってくるので、astなどで辞書型に変換して使います。

### Web GUI
ここまで書いてきませんでしたが、`localhost:7654`でアクセスできる管理画面も提供されています。

<img width="1164" alt="scrapy-do1.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239330/e0eb74e8-3a24-5d2b-4e4f-aaf84a8d47a0.jpeg">

ただ私の環境が悪いのか、ジョブを登録した後に画面を開くと、JavaScriptのシンタックスエラーが発生して見えなくなってしまいました。

## さいごに
scrapy-doを軽く動かしてみました。
まだバージョンは0.4.0ですが、scrapydで悩ましかった時刻起動がカバーされているのは非常にありがたく、今後に期待したいと思います。