北海道が提供してる道内コロナ感染者状況をスクレイピングして最新感染状況を通知するSlackBotをGASで作った

GoogleAppScriptとスプレッドシートを使って北海道内の最新のコロナ感染者状況を通知するSlackBotを作りました。
[北海道内感染者状況はこちら](http://www.pref.hokkaido.lg.jp/hf/kth/kak/hasseijoukyou.htm)

特に説明する事もないのでコード貼り付けていって解説していきます。

```javascript
let notify = [];
const SHEET = SpreadsheetApp.getActiveSheet();
const DATA = SHEET.getDataRange().getValues();
const MAX_PERSON_ID = DATA.map(person => person[0]).reduce((a,b) => a>b?a:b);
```
1行目は単純にSlackに通知するデータを後々格納する為の配列です。今は関係ありません。
サイト上でテーブルで扱われているので、こちらも同じ感じで扱うためスプレッドシートを用います。
上記2,3行でシートと、シートに存在するデータを取得しています。
シートでは以下の画像のようにデータを持っています。初期状態は1行目のみでした。
![スクリーンショット 2020-02-27 17.07.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/249725/799dc59e-27ee-f845-7787-de7bd1263864.png)

4行目でスプレッドシートに登録されてるIDカラムの最大値を取得します。
これと後々取得するデータのIDを比較する事で最新か通知済みかを判定させます。

```javascript
const ENDPOINT = "http://www.pref.hokkaido.lg.jp/hf/kth/kak/hasseijoukyou.htm";
const CONTENT = UrlFetchApp.fetch(ENDPOINT).getContentText('UTF-8');
const INFECT_DATA = CONTENT.replace(/&nbsp;/g,'').replace(/\r?\n/g, '').match(/<tr>.*?<\/tr>/g);
```
この3行でエンドポイントを指定し、データを取得し、必要な部分のみを抽出しています。
3行目`INFECT_DATA`定数で行っているreplace処理は、1つ目は取得したHTMLデータの中にnon breaking spaceがあり邪魔だったので除外しています。2つ目は改行を消してます。その後に、テーブル内のtr要素のみを取り出しています。
これでINFECT_DATAはテーブルに存在するデータを1レコードを1要素とした配列になります。

```javascript
for(let infectPerson of INFECT_DATA) {
  const INFO = infectPerson.match(/<td>.*?<\/td>/g);
  const ID = INFO[0].replace(/<.*?>/g, '').replace(/./g, s => String.fromCharCode(s.charCodeAt(0) - 0xfee0));
  const DATE = INFO[1].replace(/<.*?>/g, '');
  const AGE = INFO[2].replace(/<.*?>/g, '');
  const SEX = INFO[3].replace(/<.*?>/g, '');
  const RESIDENCE = INFO[4].replace(/<.*?>/g, '');
  if (ID > MAX_PERSON_ID) {
    const mes = "\n" + DATE + "  |  " + ID + "例目" + "  |  年齢: " + AGE + "  |  性別: " + SEX + "  |  居住地: " + RESIDENCE;
    notify.push(mes);
    SHEET.appendRow([ID, DATE, AGE, SEX, RESIDENCE]);
  }
}
```
INFOは各レコードから更にtd要素で分割された要素の配列です。
ID、日付、年齢、性別、居住地をそれぞれ宣言していますが、各td要素の中にもbrやa要素などが存在していたのでそれらを除去しています。
IDに関しては全角数値で取得してたので半角になおしています。
先程取得したMAX_PERSON_IDとIDを比較し、true(未通知の最新データ)であればメッセージを作ってnotifyに追加し、シートの最終行にも挿入しています。

```javascript
if (notify.length > 0) {
  slack(notify);
}
```

通知部分です。こんな書き方にしてるのは、後々LineBotや他のSNS等への通知を簡単に追加したかったからです。
通知フラグとか持たせてT/F判定よりは、notifyが0じゃなければ通知内容があると思ったので長さで判定しています。

```javascript
function slack(notify) {
  const ENDPOINT = "https://hooks.slack.com/services/TEAMID/BOT/TOKEN";
  const TEXT = "北海道内の最新コロナ感染状況です。" + notify.join("");
  const BODY = {
    "text": TEXT
  };
  const PAYLOAD = JSON.stringify(BODY);
  const OPTION = {
    "method": "post",
    "contentType": "application/json",
    "payload": PAYLOAD
  };
    
  UrlFetchApp.fetch(ENDPOINT, OPTION);
}
```
slack通知の中身はこのようになってます。

これで全てです。コード全体は以下のようになっています。

```javascript
function myFunction() {
  let notify = [];
  const SHEET = SpreadsheetApp.getActiveSheet();
  const DATA = SHEET.getDataRange().getValues();
  const MAX_PERSON_ID = DATA.map(person => person[0]).reduce((a,b) => a>b?a:b);
  const ENDPOINT = "http://www.pref.hokkaido.lg.jp/hf/kth/kak/hasseijoukyou.htm";
  const CONTENT = UrlFetchApp.fetch(ENDPOINT).getContentText('UTF-8');
  const INFECT_DATA = CONTENT.replace(/&nbsp;/g,'').replace(/\r?\n/g, '').match(/<tr>.*?<\/tr>/g);
  for(let infectPerson of INFECT_DATA) {
    const INFO = infectPerson.match(/<td>.*?<\/td>/g);
    const ID = INFO[0].replace(/<.*?>/g, '').replace(/./g, s => String.fromCharCode(s.charCodeAt(0) - 0xfee0));
    const DATE = INFO[1].replace(/<.*?>/g, '');
    const AGE = INFO[2].replace(/<.*?>/g, '');
    const SEX = INFO[3].replace(/<.*?>/g, '');
    const RESIDENCE = INFO[4].replace(/<.*?>/g, '');
    if (ID > MAX_PERSON_ID) {
      const mes = "\n" + DATE + "  |  " + ID + "例目" + "  |  年齢: " + AGE + "  |  性別: " + SEX + "  |  居住地: " + RESIDENCE;
      notify.push(mes);
      SHEET.appendRow([ID, DATE, AGE, SEX, RESIDENCEP]);
    }
  }
  if (notify.length > 0) {
    slack(notify);
  }
}

function slack(notify) {
  const ENDPOINT = "https://hooks.slack.com/services/TEAMID/BOT/TOKEN";
  const TEXT = "北海道内の最新コロナ感染状況です。" + notify.join("");
  const BODY = {
    "text": TEXT
  };
  const PAYLOAD = JSON.stringify(BODY);
  const OPTION = {
    "method": "post",
    "contentType": "application/json",
    "payload": PAYLOAD
  };
    
  UrlFetchApp.fetch(ENDPOINT, OPTION);
}
```