CloudflareWorkers+D1+cheerioでスクレイピングを定期実行してみる

## やりたいこと
Cloudflare WorkersとD1を使って、定期的にサイトをスクレイピングするWorkerを作ってみます。

## Workers環境を作成する。

### Workerをセットアップします

下記のnpm createを叩き、Workersを作成します。

```sh
$ $ npm create cloudflare@latest
```

### D1をセットアップする

下記のコマンドで、d1データベースを作成します。

```sh
$ npx wrangler d1 create <DATABASE_NAME>
```

`wrangler.toml`で、d1をバインディングします。

```toml:wrangler.toml
[[d1_databases]]
binding = "DB" # available in your Worker on env.DB
database_name = "<DATABASE_NAME>"
database_id = "<unique-ID-for-your-database>"
```

### D1へテーブルを作成する

サンプルなのでSQLは適当です。
各々必要なカラムを持ったテーブルを作ってください。

```sql
CREATE TABLE IF NOT EXISTS Test (Title TEXT);
```

schemaファイルをローカルの環境で叩きます。

```sh
npx wrangler d1 execute <DB_NAME> --local --file='./schema.sql'
```

WorkersとD1のチュートリアルを一度走っておくと分かりやすいです。

https://qiita.com/RuruCun/items/017aa47eabab6bfef67f

https://qiita.com/RuruCun/items/93c2a47122fb76c74723


## Cronトリガーを設定します。


### `wrangler.toml`を編集します。

`triggers`を追加し、UTC時間を配列の中に入れておきます。

```toml:wrangler.toml
[triggers]
# Schedule cron triggers:
# - At every 3rd minute
# - At 3PM on first day of the month
# - At 11:59PM on the last weekday of the month
crons = [ "*/3 * * * *", "0 15 1 * *", "59 23 LW * *" ]
```

下記の形式で入力ができます。
ChatGPTにお願いするか、ブラウザのGUIから入力するのが正確で楽でした。

| フィールド | 値 | 文字 |
| -- | -- | -- |
| 分 | 0-59 | * , - / |
| 時 | 0-23 | * , - / |
| 月の日 | 1-31 | * , - / L W |
| 月 | 1-12、大文字小文字を区別しない3文字の略語（例: “JAN”, “aug”など） | * , - / |
| 曜日 | 1-7、大文字小文字を区別しない3文字の略語（例: “MON”, “fri”など） | * , - / L # |


`src/index.ts`を編集して、スクレイピング処理を追加します。


### ブラウザから編集する方法

Cloudflareダッシュボードの、「Workers/概要」から、作ったプロダクトへアクセスします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3589058/558d37e3-ee0a-3d52-d1fd-999c0be8553b.png)

タブメニューの「トリガー」へ移動し、下の方にCronトリガーの項目があるので、ここで追加と削除が行なえます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3589058/4bc2c0ce-0d65-5d54-d769-3e5e0e47b17a.png)


## Cronの削除方法
Cronsトリガーをwrangler.tomlから削除する際は、丸ごと消してはCronトリガーが削除されません。
`crons=[]`と空の配列を渡す必要があります。

## スクレイピングする処理を追加する

### スクレピングライブラリを追加する

軽量で高速なスクレイピングライブラリの`cherrio`をインストールします。

https://github.com/cheeriojs/cheerio

```sh
$ npm install cheerio
```


### `src/index.js`へ処理を追加する

`expord default`内へ、`async scheduled`を追加します。
これで、この関数内の処理がcronトリガーで叩かれるようになります。

```js
export default {
  async scheduled(event, env, ctx) {
    //　ここに処理を追加していく。
  },
};
```

### スクレイピングの処理を追加する

cheerioでスクレピングする処理を追加します。


```js
export default {
  async scheduled(event, env, ctx) {
		const url = 'https://example.com/';
		const response = await fetch(url); 
        const data = await response.text();
        const $ = cheerio.load(data);
        const h1Text = $('h1').text();
        console.log(h1Text); 
};
```

### 処理をテストする

`wrangler dev`時に`--test-scheduled`を渡してあげてから、curlで叩くとテストができます。
h1のテキスト `Example Domain`がターミナルに表示されると思います。

```sh
$ wrangler dev --test-scheduled
$ curl "http://localhost:8787/__scheduled?cron=*+*+*+*+*"
```

## D1へ追加する

### D1へINSERTする

チュートリアルでやったように、INSERT文でスクレピングしたテキストをD1へ追加します。

```js
export default {
  async scheduled(event, env, ctx) {
    const url = 'https://example.com/';
    const response = await fetch(url); 
    const data = await response.text();
    const $ = cheerio.load(data);
    const h1Text = $('h1').text();
    console.log(h1Text); 
    
    await env.DB.prepare(`
    insert into Test (title) values (?)
    `).bind(h1Text).run()
};
```

### 確認する

ローカルのD1データベースは、`.wrangler/state/v3/d1/miniflare-D1DatabaseObject`内のsqliteファイルに入っているので、こちらを確認することで確認できます。

自分は、VSCodeのSQLiteViewerを使って確認しています。

## デプロイする

動作を確認したらデプロイします。

```sh
$ npx wrangler d1 execute <DB_NAME> --local --file='./schema.sql'
$ npx wrangler deploy
```

Cloudflare dashboardに移動し、無事デプロイされているか、cronで設定された時間になったら、無事Workerが動いてD1にデータが追加されているかを確認して終了です。

この記事では、スクレピング部分を適当に済ませていますが、ここを目的のサイトに対応させることで、勝手にデータを拾って保存できる環境を高速かつ低コストで実装できるので、個人開発で情報を取り扱う場合にはとても便利に感じました。

### 参考

https://developers.cloudflare.com/workers/configuration/cron-triggers/

https://developers.cloudflare.com/workers/runtime-apis/handlers/scheduled/