世田谷区新型コロナウイルスWebページのスクレイピングを GitHub Actions で自動化した話

これは [CivicTechテック好き Advent Calendar 2020](https://qiita.com/advent-calendar/2020/civictech_tech) の23日目の記事です。

# はじめに

CivicTech 的な活動の中で、行政やライフライン企業の Web ページから情報を定期的にスクレイピングしないといけないシーンは結構あると思います。
個人的には、2019年9月の [令和元年房総半島台風](https://ja.wikipedia.org/wiki/%E4%BB%A4%E5%92%8C%E5%85%83%E5%B9%B4%E6%88%BF%E7%B7%8F%E5%8D%8A%E5%B3%B6%E5%8F%B0%E9%A2%A8) の際に停電情報をスクレイピングして GitHub 上のレポジトリにストックして可視化するような試行を行った経験があります。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82494/a04e627c-c189-2fdf-6ab4-4c36ee9913c4.png)

Repo: <https://github.com/frogcat/teideninfo>
Demo:  <https://frogcat.github.io/teideninfo/>


自宅でサーバを立てて常時稼働、 crontab を使って作業を定期的に実行するというのが割と伝統的なやり方だと思うのですが、これだけのために自宅サーバを維持管理するのは面倒です。かといってクラウド上に常時稼働のサーバを立てると費用が気になります。

ここでは GitHub の CI/CD サービスである [GitHub Actions](https://github.co.jp/features/actions) を使って、定期的なスクレイピング＆PUSH を自動化する手法を紹介します。

# シナリオ

[区内の新型コロナウイルス感染症の検査陽性者数等について(世田谷区)](https://www.city.setagaya.lg.jp/mokuji/kusei/001/001/004/d00185364.html) では、平日の夕方〜夜にかけて情報が更新されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82494/62c3bcd3-2b4a-b3b6-103a-d728f6b99354.png)

同ページ内でこのように、検査数や陽性者数などが報告されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82494/8546643a-ea66-9f2c-f4fe-6bd090e18d91.png)


この情報をスクレイピングしてこんな風に GitHub にストックしていきましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82494/36c49680-913e-604a-05b5-16907d89c3c2.png)


# できあがり

こちらのレポジトリに一式置いています。
<https://github.com/frogcat/covid19-setagaya>

実際に日々更新されていくデータはこちら。
<https://github.com/frogcat/covid19-setagaya/blob/master/docs/data.csv>



# 解説

## GitHub Actions

いろいろな説明ができるのですが、この記事の用途においては

**任意の GitHub レポジトリに `.github/workflows/main.yml` のような YAML ファイルを置いておくと、YAML ファイルの中で指定したトリガーによって、YAML ファイルの中に記述した任意の処理を実行してくれる**

との理解があれば十分です。

実際に運用している `main.yml` は以下のようなものです。

```yml
name: main

on:
  schedule:
    - cron: '0 12 * * MON,TUE,WED,THU,FRI'
  push:
    paths-ignore:
    - 'docs/**'

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    - uses: actions/setup-node@v1
      with:
        node-version: 14.x
    - run: npm ci
    - run: curl https://www.city.setagaya.lg.jp/mokuji/kusei/001/001/004/d00185364.html > docs/data.html
    - run: npm run build
    - run: npm run test
    - run: git config --local user.email "frogcat@users.noreply.github.com"
    - run: git config --local user.name "frogcat-bot"
    - run: git add docs
    - run: git commit -m "Update data.html and data.csv"
    - uses: ad-m/github-push-action@v0.6.0
      with:
        github_token: ${{ secrets.GITHUB_TOKEN }}
```

ざっくりと

- 月〜金の 12:00 (日本時間 21:00) またはレポジトリに push がおきたときに実行されるようだ
- ubuntu の最新版の上でレポジトリのチェックアウト、node.js のセットアップ、依存ライブラリのインストールが行われるようだ
- `curl ... > docs.data/html` で Web から HTML を持ってきているようだ
- `npm run build` でスクレイピングがされるようだ
- `npm run test` でスクレイピング結果がテストされるようだ
- 以後の `git` コマンド群でレポジトリへの PUSH が行われるようだ

といった雰囲気を掴んでいただければこの記事の目的は果たせたかと思います。

## スクレイピング

スクレイピング(HTML からの CSV生成) については node.js で書いています。
上の `npm run build` の中で、 `node main.js data.html data.csv` が呼び出されるという仕組みです。

中身は（スクレイピングにありがちな）見るに耐えない内容なので割愛します。

## エラーハンドリング

YAML に書いた手順は順次実行され、ひとつでも失敗するとそこで終了してエラーメールが送られてきます。

ありがちなエラーは **スクリプトは実行したが、まだデータが更新されていなかった** というケースです。通常は設定のとおり毎日21:00に実行されるのですが（しかし 21:30 ごろに実行されることもあって時間的な正確さは微妙）、そのときに世田谷区サイトでまだ当日の更新が終わっていないということもあります（ご苦労様です）。

そんな場合のエラー例は以下のように確認することができます。この例では何も更新がないのに `git commit` しようとしてエラーになったよ、というものですね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82494/d1e33d96-4b9f-8aee-580e-f5477e84b26d.png)


あと、crontab 的な設定では「日本の祝日は除く」みたいなことは当然できません。
世田谷区の場合月曜〜金曜の祝日はデータは更新されないのですがスクリプトは走るので、
だいたいエラーメールが飛んできます。


## フォーマット変更

世田谷区の場合、以下の２つのメンテナンスが必要でした。

- 月初に過去の月の表が削除される場合がある (都度生成ではなくて、追記の対応が必要)
- ラベルが変更される（陽性者数 → 感染者数、など）

フォーマット変更を検出するために、 `npm run test` でラベルや件数をチェックして、もしも異常があればメールが飛んでくるようにしています。

# さいごに

上記の Actions の実行ログですがレポジトリのタブの **Actions** を叩くと誰でも見ることができます。過去に 111 回実行されて、だいたい一回あたり 30秒くらいかかっているようですね。

<https://github.com/frogcat/covid19-setagaya/actions>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82494/0515820e-54e3-dfaa-7f05-81bd9fcd875d.png)

気になる費用ですが、 GitHub Actions はスクリプトの実行時間が課金対象になります。ただし、通常のユーザであれば **2,000分/月** の無料枠が付与されています。

上の事例では、一回30秒のスクリプトを月30回実行することになるので、0.5(分) x 30 = 15分 のリソースを消費したことになります。2,000分の無料枠の 15分ですので、 1% 以下程度です。この程度の処理頻度であれば特に意識することなく無料で運用できそうです。
毎時更新はたぶんさばけると思いますが、毎分は無理ですね。そういう高頻度の場合には AzureFunction や AWS Lambda なんかも選択肢に入ってくるのかもしれません。



なお、このように Actions に更新させるとパンチカードがやけに勤勉なかんじになるので注意しましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82494/2345919e-a765-a6fb-c3e7-b509a852f32a.png)