[GAS]GASでスクレイピングしてLINEグループとChatworkグループへ通知する

# この記事でやること
GASでスクレイピングした結果をLINEとChatworkグループへ通知する。

スクレイピングする情報は、
静岡県の新型コロナウイルス感染症対策WEBサイトから"県内の最新のコロナウイルス感染者情報"と、"感染症症状を自覚した際の対応ガイドライン"を毎日朝1回取得する。

# なぜ作った
下記の練習が目的です。
それと折角なので社員の感染症対策の意識向上にもなるかなーと。

- GASのParserライブラリを使ったスクレイピング
- GASのChatwork Bot
- GASのLINE Bot

# 必要なもの
- Googleアカウント
- Chatworkアカウント
    - Chatwork API トークン（取得方法は後述）
- LINEアカウント
    - Line Notifyのトークン（取得方法は後述）
- GASでスプレッドシートに簡単な書き込みやったことあるくらいの知識
- HTMLのタグを何個か言えるくらいのHTMLの知識


# 実装
まずは新型コロナウイルス感染症対策サイトをスクレイピングするスクリプトを作ります。

## 対象サイト
下記のサイトから情報を取得します。
> 静岡県 新型コロナウイルス感染症対策サイト
> https://stopcovid19.code4numazu.org/

下図①～④の情報を取得したいと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/342467/3c3c15ba-19ec-e83e-5d68-3432856d88b8.png)



## スクレイピングライブラリParser
GASでスクレイピングするには、便利なParserというライブラリがあるので使わせて頂きます。

> Parserライブラリ 作者の解説
> https://www.kutil.org/2016/01/easy-data-scrapping-with-google-apps.html
> Parserライブラリキー
> M1lugvAXKKtUxn_vdAG9JZleS6DrsjUUV

Parserライブラリを使ったスクレイピングはざっくり説明すると下記の手順で行います。

1. `UrlFetchApp.fetch()`でターゲットのWebサイトのhtmlを取得する
1. 取得したhtmlから取得したい情報を下記のいづれかで取得する
    - Parser.data(html).from().to().iterate(); //単一情報を取得
    - Parser.data(html).from().to().build(); //複数情報を配列として取得
1. 取得した情報を必要に応じて、正規表現や`replace`を使って加工する

## Parserライブラリをスクリプトに読み込む
今回はスタンドアローンGASを使うので、Googleドライブを開き、新規GASファイル作成します。
作成したGASファイルを開き、”リソース”→”ライブラリ”をクリック→Parserライブラリキー（上記）を追加→okをクリックします。
これでParserライブラリが使えるようになります。


## スクレイピングスクリプトを記述
スクリプトを書いていきたいと思います。
いきなりLINEなどへ配信する前に、まずはスクレイピングの練習としてスプレッドシートに出力するプログラムを作成しました。Parserライブラリを読み込んだGASファイルに下記のように記述しました。

```Javascript:GASでスクレイピング
function GetWebInfo_covid19_ByParser(){  
  var url = "https://stopcovid19.code4numazu.org/" //ターゲットとなるWebサイトのURL
  var options = {
    'method' : 'GET',
    'muteHttpExceptions' : true,
  }
  var content = UrlFetchApp.fetch(url, options).getContentText('UTF-8');
  var parser1 = Parser.data(content).from('<span class="DataView-DataInfo-summary">').to('</small>').iterate();
  var parser2 = Parser.data(content).from('<small class="DataView-DataInfo-date">').to('</small>').iterate();

  //取得した情報を加工する。
  for (var i = 0;  i < parser1.length; i++){
    parser1[i] = parser1[i].replace('<small class="DataView-DataInfo-summary-unit">',"");//不要なタグ削除
    parser1[i] = parser1[i].replace('\n',""); //不要な改行コード削除
    parser1[i] = parser1[i].replace('\n',""); //不要な改行コードもう一個削除
  }

  //正しく情報が取得できたのか、スプレッドシートに書き込んでみる。
  var stid = "スプレッドシートのシートID";
  var st = SpreadsheetApp.openById(stid).getSheetByName("test");
  for (i = 0 ; i < parser1.length ; i ++){
    st.getRange(i+1,1).setValue(parser1[i]);
    st.getRange(i+1,2).setValue(parser2[i]);
  }
}
```

## スプレッドシートへの出力結果
こんな感じで欲しいところが取得できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/342467/e841ba82-2693-62b2-e53f-a5276b509c71.png)


## Parserの使い方補足

### `from()`と`to()`に渡す値は？
fromとtoに渡す値はざっくりいうと、htmlの、`このhtmlタグ（from）から このhtmlタグまで（to）`の間の情報が欲しいです。という感じで指定します。

欲しい情報がどのhtmlタグの中にあるかを調べるには、ブラウザの開発モードの機能を使って調べます。
Google Chromeの場合はターゲットとなるWebサイトを開きF12キーを押すと開発モードになり、下図のような画面が表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/342467/80a59f26-5713-2ef4-ba28-397f20a75302.png)

上図のように操作してみましょう。
どのhtmlタグの間に欲しい情報があるかがわかります。今回取得したい情報のhtml構成は下図のようになっていることがわかりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/342467/0cc718af-909e-06da-ff90-2ce19c0a191c.png)

上図を見てみると、
目的の”57”という値は、図のFromからtoに挟まれていますので、下記のように記述しました。

```Javascript:GAS
Parser.data(content).from('<span class="DataView-DataInfo-summary">').to('</small>').iterate();
//今見ると下記の方が良い気がしますが、何かエラーがでて上記にしたと思います。
//Parser.data(content).from('<span class="DataView-DataInfo-summary">').to('<small class="DataView-DataInfo-summary-unit">').iterate();
```


上記ルールに合致するものが複数あるようでしたので`Build`ではなく、`iterate`として配列として結果を取得します。
また、これだと下記のように余分な文字列と改行コードも含まれてしまうので、`replace`で不要な文字列を削除（空白に置換）しています。

```:取得結果
    57
    <small class="DataView-DataInfo-summary-unit">人
```

## スクレイピングするスクリプトを関数化
スクレイピングするスクリプトは外部関数から呼ぶ予定なので`return`を使って下記のように関数として書き換えました。

```Javascript:GASでスクレイピング
function GetWebInfo_covid19_ByParser(){  
  var url = "https://stopcovid19.code4numazu.org/" 
  var options = {
    'method' : 'GET',
    'muteHttpExceptions' : true,
  }
  
  var content = UrlFetchApp.fetch(url, options).getContentText('UTF-8');
  var parser1 = Parser.data(content).from('<span class="DataView-DataInfo-summary">').to('</small>').iterate();
  var parser2 = Parser.data(content).from('<small class="DataView-DataInfo-date">').to('</small>').iterate();   
      
    for (var i = 0;  i < parser1.length; i++){
    parser1[i] = parser1[i].replace('<small class="DataView-DataInfo-summary-unit">',"");
    parser1[i] = parser1[i].replace('\n',"");
    parser1[i] = parser1[i].replace('\n',"");
  }
  var shizuokaCovid = {};
  shizuokaCovid["total"] = parser1[0];
  shizuokaCovid["today"] = parser1[1];
  shizuokaCovid["totaldescription"] = parser2[0];
  shizuokaCovid["todaydescription"] = parser2[1];
  return shizuokaCovid;  
}

```

# LINEへ通知するスクリプト作成

## LINE Notifyの登録とトークン発行
下記へアクセスします。
> LINE Notify
> https://notify-bot.line.me/my/

## LINE通知するトークルームを選択
トークルームはこのページでは新規作成できません。新規ルームで通知する場合は事前にLINEアプリでルームを作成しておく必要があります。
Notifyアカウントのトークルームへの参加はあとからでも構いません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/342467/914a49ce-61ff-3f21-1b92-f3c41935c1b6.png)

## LINEトークンをコピー
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/342467/84637eb2-cc4a-fabc-eab2-9064a8c145e1.png)
後ほど必要になるのでコピーしておきます。

## LINE通知するトークルームにNotifyを招待
ここはLINEアプリでの操作です。
LINEグループのトークルームを開きます。トークルームの左上の設定ボタンをクリックします。

招待をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/342467/d2f7b3a3-589a-0cfe-156d-23c07f722b35.png)

Line Notifyのチェックボックスにチェックをいれ招待をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/342467/2c91a9c7-4dac-6cd1-7889-b5c98055fa7a.png)

トークルームを確認すると参加が確認出来ます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/342467/6865f44d-69c8-536f-1b07-5bdb1118443e.png)

## LINE通知するGASスクリプト作成

```Javascript:GASでLINE通知
function postLINE(message){
var token = "先ほど取得したLINEトークン";
  var options =
   {
     "method"  : "post",
     "payload" : "message=" + message,
     "headers" : {"Authorization" : "Bearer "+ token}
   }; 
    //エンドポイントへPost
   UrlFetchApp.fetch("https://notify-api.line.me/api/notify",options);
}
```
# Chatworkへ通知するスクリプト作成

## ChatworkのWeb版へログインします。
API取得はアプリケーション版Chatwork上からは取得できない（と思う）のでWeb版にログインします。
また、Chatworkには、LINE Notifyのような通知用botアカウントは存在しない（と思う）ので、自身のアカウントから通知するか、別途bot用アカウントを取得します。
わたしは、社内共有アカウントがありますので、そこから通知することにします。
ここでは通知するアカウントからログインしてください。

## Chatwork APIトークンを発行
下記の公式ドキュメントを参考にトークンを取得してコピーします。
 
> Chatwork公式 APIトークンを発行する
> https://help.chatwork.com/hc/ja/articles/115000172402-API%E3%83%88%E3%83%BC%E3%82%AF%E3%83%B3%E3%82%92%E7%99%BA%E8%A1%8C%E3%81%99%E3%82%8B

## 通知用アカウントを通知するトークルームに参加
通知用アカウントを通知するトークルームに参加させます。

## Chatworkへ通知するGASスクリプト作成

```Javascript:GASでChatwork通知
function postChatwork(message) { 
    var chatworktoken = "先ほど取得したChatworkトークン";
    var payload = {
        'body' : message
    };
    var headers = {
        'X-ChatWorkToken' : chatworktoken
    };
    var options = {
        'method' : 'post',
        'payload' : payload,
        'headers' : headers
    };
    //エンドポイントへPost
    UrlFetchApp.fetch('https://api.chatwork.com/v2/rooms/' + ROOM_ID + '/messages', options);
}
```

# スクレイピングして通知するスクリプトを記述します。
いままで作ったスクリプトを呼び出して、実際に通知を行うスクリプトを下記のように作成します。
messageの生成の部分、汚くて見づらいですね・・・・。後程修正しようと思います。

```Javascript:GASでスクレイピングして通知するスクリプト

function scrapingAndPost(){
  covid = GetWebInfocovid19.GetWebInfo_covid19_ByParser()
  covid_taisaku_url = "新型コロナウイルス感染症対策ガイドラインのURL" 

  //通知するメッセージを作成する
  message += "\n" + "●静岡県の新型コロナウイルス感染症 \n" +　
"本日確認された陽性患者数【" + covid["today"] + "】" + "\n" + covid["todaydescription"] + 
"(累計" + covid["total"] + ")" + "\n" + covid_taisaku_url

  //先ほど作成した通知スクリプトにPOSTメッセージを渡して通知する
  postLINE(message); 
  postChatwork(message); 
}
```

# スクレイピングして通知するスクリプトにトリガーを設定する。
定期実行するためにトリガーを作成します。（トリガーの作成方法は後程追加します。）

# 結果
↓こんな感じで通知ができました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/342467/2cf11732-c554-9403-a4c1-c3dd909efcc0.png)


# スクレイピングする時の注意
スクレイピングは動産不法侵入、著作権法などに侵害する可能性があります。
取得頻度、取得する情報とその用途、には十分注意しましょう。

また、定期実行を行う際は、、アクセス先のサーバへ負荷がかからないように節度を持って実行しましょう。