長野県版 新型コロナウイルス感染症対策サイト データ更新を自動化した話

## TL;DR

[長野県版 非公式 新型コロナウイルス感染症対策サイト](https://nagano.stopcovid19.jp/)のデータ更新（Webスクレイピングしたり再エンコーディングしたりCSVからJSONへマッピングしたり）をGitHub Actionsを使って自動化しました。サイトのリポジトリにはデータファイルの管理をせずにアプリケーション関連のファイルのみを管理をするので開発もスッキリ。

開発したスクリプトはこちら：
- [wataruoguchi/covid19_nagano_csv_to_json](https://github.com/wataruoguchi/covid19_nagano_csv_to_json)
- [wataruoguchi/covid19_nagano_scraper](https://github.com/wataruoguchi/covid19_nagano_scraper)

これらのスクリプトで作られたJSONファイルを使っているリポジトリはこちら：
- [Stop-COVID19-Nagano/covid19](https://github.com/Stop-COVID19-Nagano/covid19)

## 自己紹介

昔は高専生でした。悪いことしてなくてもディスられる世の中、怖い・・・
https://twitter.com/IiToshihide/status/1246487047545556992

## 目次

```
0. 経緯
1. オープンデータ自動ダウンロード & CSV -> JSON 変換
2. ニュース一覧をスクレイピング & JSON 変換
3. 自動更新されたファイルでサイトを自動ビルド
4. まとめ
5. 謝辞
```

## 0. 経緯

東京都と[Code for Japan](https://www.code4japan.org/)が開発した[東京都 新型コロナウイルス感染症対策サイト](https://stopcovid19.metro.tokyo.lg.jp/)と、それをforkして各地域のサイトが開発されているのを見て、出身地の長野県版に協力したいと思いcontributeを始めました。

一つ目のスクリプトを書いた後で、長野県版の先行作業されていた有志の方たちがデータを手作業で更新していることを知りました。その方達を助けられるよう、自動化に観点を置いてコードを書いてきました。この知識が他の自治体やプロジェクトの一助になれば、と思います。

## 1. オープンデータ自動ダウンロード & CSV -> JSON 変換

Contributeするにあたり最初に確認したのは、オープンデータの有無でした。幸いにも、長野県のサイトでCSVファイルを見つけることができました。しかし、これらの公開されていたファイルには以下のようにいくつかの問題がありました。

1. 利用するのに難がある。ファイルタイトル（カラムヘッダではない）がついていたり合計行があったりする。
1. ファイルエンコードがSHIFT JIS。UNICODEが欲しい。
1. ファイル名が毎日変わる。ファイル名のsuffixに日付がつくので、毎日ファイル名が変わる。古い日付のファイルはサーバから削除される。ファイル名が変わることもある(`hasseijoukyou0402.csv` もあれば `hasseizyokyo0411.csv` もある)。

これらの問題を解決するために[WebスクレイピングしてCSVをJSONに変換するスクリプト](https://github.com/wataruoguchi/covid19_nagano_csv_to_json)を書きました。

1. 長野県のページをスクレイピングし、公開されているCSVファイルのURLを取得する。
1. ファイルをダウンロードし、エンコード変換したCSVファイルを保存する。
1. CSVから不要な行を削除し、JSONにマッピングし、生成されたJSONファイルを保存する。

たまに出現する全角数字(!)を半角数字に変換したり、複数のファイルから `data.json` を作成する処理も同スクリプトでやっています。

これを自動で毎時間実行するための GitHub Actions ワークフローを作成しました。

毎時間実行するためには、`scheduled event` というEvent Triggerを使います。

```yaml
...
on:
  schedule:
    - cron: "0 * * * *"
...
```

- [scheduled events](https://help.github.com/en/actions/reference/events-that-trigger-workflows#scheduled-events-schedule)
- [実際のファイルはこちら](https://github.com/wataruoguchi/covid19_nagano_csv_to_json/blob/master/.github/workflows/nodejs.yml#L7)

スクリプトを走らせて、新しいファイルが作成されたかを確認します。今回は `git status | grep modified` でチェックすれば十分でした。

新しいファイルが作成されていたらそれを `git add` & `git commit` して同リポジトリに保存します。

```yaml
...
      - name: Commit files
        run: |
          git config --local user.email "wataru.oguchi.0to1@gmail.com"
          git config --local user.name "Wataru Bot"
          git status | grep modified && git add src/.encoded src/.json && git commit -v -m "[Bot] GitHub Actions - auto run" || true
...
```

- [実際のファイルはこちら](https://github.com/wataruoguchi/covid19_nagano_csv_to_json/blob/master/.github/workflows/nodejs.yml#L29)

これによって、同リポジトリに常に最新のCSVファイル及びJSONファイル（`data.json`）が自動で保存されるようになりました。（`git push`もお忘れなく！）

## 2. ニュース一覧をスクレイピング & JSON 変換

グラフを表示するには `data.json` というファイルを参照している一方で、ニュース一覧には `news.json` というファイルを参照しています。県のサイトを見て手作業で更新されている様子でしたので、単純にWeb スクレイピングをし、一つ目のスクリプトと同様にJSONファイルに保存することとしました。[Web スクレイピングをするスクリプトはこちら](https://github.com/wataruoguchi/covid19_nagano_scraper)。

こちらも前述したスクリプトと同様、GitHub Actions ワークフローを作成し、毎時間実行しています。

## 3. 自動更新されたファイルでサイトを自動ビルド

ここまでで必要なJSONの生成は自動化することができました。

実際のページは静的サイトなので、データを更新するためにはビルドする必要があります。ここでのタスクは以下の通りです。

1. データが更新された時に再ビルドを行うため、データ生成スクリプト側で通知する。
2. 実際にページをビルドするプロジェクトで通知を受け取る。
3. 最新のデータをダウンロードして再ビルドを行う。

### 1. データ更新の通知を送信する

JSONデータを生成する `wataruoguchi/covid19_nagano_csv_to_json` や `wataruoguchi/covid19_nagano_scraper` 側のGitHub Actionsを使います。上記で紹介したGitHub Actions ワークフローのスニペットにGitHub APIへPOSTリクエストを送る `curl` コマンドを加えます。

```yaml
...
      - name: Commit files
        run: |
          git config --local user.email "wataru.oguchi.0to1@gmail.com"
          git config --local user.name "Wataru Bot"
          git status | grep modified && git add src/.encoded src/.json && git commit -v -m "[Bot] GitHub Actions - auto run" \
            && curl -X POST \
              -H "Authorization: Bearer ${{ secrets.ACCESS_TOKEN }}" \
              -H "Accept: application/vnd.github.everest-preview+json" \
              -H "Content-Type: application/json" \
              https://api.github.com/repos/Stop-COVID19-Nagano/covid19/dispatches --data '{"event_type": "build_application"}' \
            || true
...
```

変更点を抜粋したものが以下のスニペットです。

```sh

curl -X POST \
  -H "Authorization: Bearer ${{ secrets.ACCESS_TOKEN }}" \
  -H "Accept: application/vnd.github.everest-preview+json" \
  -H "Content-Type: application/json" \
  https://api.github.com/repos/Stop-COVID19-Nagano/covid19/dispatches \
  --data '{"event_type": "build_application"}'
```

- [実際のファイルはこちら](https://github.com/wataruoguchi/covid19_nagano_csv_to_json/blob/master/.github/workflows/nodejs.yml#L34)

[GitHub API v3 の dispatch event](https://developer.github.com/v3/repos/#create-a-repository-dispatch-event) を使い、Webサイトのリポジトリである `Stop-COVID19-Nagano/covid19` に dispatch イベント `{ "event_type" : "build_application" }` をPOSTしています。 `secrets.ACCESS_TOKEN` はrepo権限のみを持ったパーソナルアクセストークンを作成し、そのトークン値を` wataruoguchi/covid19_nagano_csv_to_json` リポジトリ側の `Secrets` に保存しています。前提として、イベントが送信される側のリポジトリの管理者権限を持っている必要があるかと思います。

- [パーソナルアクセストークンの作り方](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line)
- [Secretsへの保存方法](https://help.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets)

### 2. データ更新の通知を受け取る

データ更新の通知を受信するためにはWebサイトのリポジトリ `Stop-COVID19-Nagano/covid19` のGitHub Actions ワークフローを更新します。

イベントトリガーのところに、`repository_dispatch` とその `types` である `build_application` を追加します。

```
...
on:
  repository_dispatch:
    types: [build_application]
...
```

- [実際のファイルはこちら](https://github.com/Stop-COVID19-Nagano/covid19/blob/development/.github/workflows/deploy.yml#L4)

これだけの変更で dispatch event を受け取り次第、このworkflowが実行されるようになります。

注意すべき点は `repository_dispatch` は `master` リポジトリに無いと動かないということ。ドキュメントにはデフォルトブランチ（私たちの場合は`development`）でも動くと書いてあったのに動作は確認できませんでした。( 軽く2時間は溶けました :/ )

- [GitHub Actions - External events: repository_dispatch](https://help.github.com/en/actions/reference/events-that-trigger-workflows#external-events-repository_dispatch)

### 3. 最新のデータをダウンロードしてビルド

最新のデータは全てスクリプトのリポジトリにあるので、ビルドをする前にshellスクリプトで全てのJSONファイルをダウンロードします。

実際のファイルは[こちら (bin/cp_data.sh)](https://github.com/Stop-COVID19-Nagano/covid19/blob/development/bin/cp_data.sh)。

この `bin/cp_data.sh` を generate 時に実行するよう、`package.json` 内の `scripts` に変更を加えました。

```json
...
"scripts": {
    "cp:data": "./bin/cp_data.sh",
    "generate:deploy": "yarn cp:data && yarn generate:deploy:orig",
    "generate:deploy:orig": "nuxt-ts generate",
}
...
```

- [実際のファイルはこちら](https://github.com/Stop-COVID19-Nagano/covid19/blob/development/package.json)

`generate:deploy:orig` が元々 `generate:deploy` だったものです。新たにデータをダウンロードするスクリプト`./bin/cp_data.sh`を実行する`cp:data`を追加し、デフォルトで使われる`generate:deploy`でこれを実行するよう変更しました。

## 4. まとめ

以上の変更で、手動で行ってきた以下のタスクが全て自動化されました。

1. 県のサイトからCSVファイルを探してダウンロードする。
2. CSVファイルをダウンロードしてJSONへ変換する。
3. JSONが変わったときに、ページの再ビルドを行う。

同様に、自動化スクリプト側でデータ管理を行うことになり、Webサイトのリポジトリからデータ管理を外れたことで、以下の利益も生まれました。

1. 同リポジトリの変更がデータの変更タイミングに依存せず、アプリケーション変更に集中できる。
2. データに誤りがあったらデータ生成スクリプトの修正をすればいいだけなので、アプリケーション側でデプロイ等する必要がない。
3. 以前同リポジトリで行っていたHTTP GETでの最新JSON取得を廃止し、ビルドのタイミングで行うためレンダリングパフォーマンスが良い。


## 5. 謝辞

長野県版を立ち上げてくださった方、長野県庁の中の人と連絡を取ってくださった方、長野県のオープンデータを整備されている方、アプリのメンテナンス／改善をしてくださった方、みなさんの力でこのページが生まれて、県内に住む方々から良いフィードバックを得られています。ありがとうございます。

東京都のメインプロジェクトから派生して、長野県だけでなく全国各地で同じようにプロジェクトが生まれています。これらのプロジェクトに時間と能力を費やしている方、Code for JapanのSlack チャンネル #covid19, #stopcovid19jp の方にも知識の提供をいただいています。ありがとうございます。

身近にいる人の感染がわかってからでは身を守るのが手遅れになるかもしれません。

感染を遅らせることができれば、医療機関も一人ひとりの患者の治療に時間をとれます。外出を控え、自分だけでなく、自分の大切な人たち、そしてその人たちの大切な人たちを守ることに協力してください。