3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-02-27

GoogleAppScriptとスプレッドシートを使って北海道内の最新のコロナ感染者状況を通知するSlackBotを作りました。
北海道内感染者状況はこちら

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

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

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

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要素とした配列になります。

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に追加し、シートの最終行にも挿入しています。

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

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

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通知の中身はこのようになってます。

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

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);
}
3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?