千葉県コロナウィルスのページをGASでスクレイピングしてみた

## 動機
本当は東京都の[コロナウィルス](https://stopcovid19.metro.tokyo.lg.jp/)サイトを参考にして、千葉県のサイトを作りたかったんだけど...
あ...公開できるWebサーバーがない...

あんまり悩んでもしょうがないので、ここでは簡単にGASを使って千葉県の患者数情報を取得して、[DataStudio](https://datastudio.google.com/)でお手軽に公開する方法にしてみました

3/15時点では千葉県の派生サイトはなかったみたいですが、今現在(4/2)は以下のサイトができています
ちゃんとしたサイトがある以上お手軽のは不要だと思いますが、なんちゃってですぐにデータを皆で見たいときは割とDataStudioは使えると思うのでデータ取得部分のコードをQiitaに載せることにしました

||公開サイト|運営者|
|-----------|------------|------------|
|千葉県|[https://chiba-covid19.mypl.net/](https://chiba-covid19.mypl.net/)|[地域情報サイト「まいぷれ」](keisuke-kimura/covid19)|
|千葉県|[https://covid19.civictech.chiba.jp/](https://covid19.civictech.chiba.jp/)|[Civic Tech Zen Chiba](civictechzenchiba/covid19-)|


## ライブラリ
Webページから簡単にHTMLを抽出するのに「Parser」というライブラリを使います
GASプロジェクト画面のリソースからライブラリに以下を追加します

|ライブラリ|key|出典|
|-----------|------------|------------|
|Parser|M1lugvAXKKtUxn_vdAG9JZleS6DrsjUUV|[Easy data scraping with Google Apps Script in 5 minutes](https://www.kutil.org/2016/01/easy-data-scrapping-with-google-apps.html)|

## GASのコード
スプレッドシートのURLと欲しいWebページのURLを書き換えれば何となく他のサイトからもデータ取れると思います

```JavaScript:sample
function myFunction() {
  // データ書込用のスプレッドシートを開く
  var url = '自分のスプレッドシートのURL';
  var ss = SpreadsheetApp.openByUrl(url);
  
  // シートを指定して最終行を調べる
  var sheet = ss.getSheetByName('chiba');
  var lastSheetRow = sheet.getLastRow();

  // 千葉県感染者情報のページを開く
  var url = 'https://www.pref.chiba.lg.jp/shippei/press/2019/ncov-index.html'; 
  var content = UrlFetchApp.fetch(url).getContentText('UTF-8');
  
  // ライブラリにParserを追加しておく
  // Parser	: M1lugvAXKKtUxn_vdAG9JZleS6DrsjUUV
  // 感染者数のテーブルを取得する
  var dataTable = Parser.data(content)
    .from('<table width="100%" summary="" border="1" class="datatable">')
    .to('</table>')
    .iterate()

  // 取得したテーブルごとに処理を行う
  var outPutData = '';
  for(var i=0; i<dataTable.length; i++){
    // 取得したテーブルの各行を配列として取得する
    trItem = getDataRow(dataTable[i]);
    // 各行から必要な情報を取得していく
    outPutData = outPutData + getDataItem(trItem);
  }

  // スプレッドシートにデータを書込
  outPutDataAry = outPutData.split('\n');
  for(var l=outPutDataAry.length-1; l>=0; l--){
    dataValue = outPutDataAry[l].split(',')
    sheet.getRange(l+2,1).setValue(dataValue[0]);
    sheet.getRange(l+2,2).setValue(dataValue[1]);
    sheet.getRange(l+2,3).setValue(dataValue[2]);
    sheet.getRange(l+2,4).setValue(dataValue[3]);
    sheet.getRange(l+2,5).setValue(dataValue[4]);
    sheet.getRange(l+2,6).setValue(dataValue[5]);
    sheet.getRange(l+2,7).setValue(dataValue[6]);
  }
}


function getDataRow(dataTableItem) {
  // 取得したテーブルの各行を配列として取得する
  var trItem = Parser.data(dataTableItem)
    .from('<tr>')
    .to('</tr>')
    .iterate()
  return trItem;
}


function getDataItem(trItem) {
  // 各行から必要な情報を取得していく
  // 出力用の文字列
  var patient = '';
  var patientType = '';
  var patientCount = '';
  var area = '';
  var age = '';
  var sex = '';
  var fdate = '';
  var rdate = '';
  var outPutData = '';
  // テーブル内の患者情報の最大行数
  var nMax = trItem.length;
  
  for(var n=1; n<trItem.length; n++){
    // 各行内の情報を配列で取得
    var tdItem = Parser.data(trItem[n])
      .from('<td ')
      .to('</td>')
      .iterate()
    
    // 各情報をベタに正規表現で処理
    // 患者番号 or 無症状病原体保有者番号
    patient = tdItem[0].replace(/<\/p>|^.+?>|<.+?>|\s/g,'').replace(/,$/g,'');
    // 患者XX～患者YYみたいな表現の場合は正しく数をカウントできなくなるので別処理に回す
    if(patient.match(/～/)){
      var tmpRowItem = tmpRowFunc(tdItem, patient);
      var tmpRowItemAry = tmpRowItem.split(',');
      outPutData = outPutData + tmpRowItemAry;
    }else{
      patientType = patient.replace(/[0-9]/g,'');
      patientCount = patient.replace(/[^0-9]/g,'');
      var area_age_sex = tdItem[1].replace(/<\/p>|<br \/>|・/g,',').replace(/^.+?>|<.+?>|\s/g,'').replace(/,$|&nbsp;/g,'').split(',');
      area = area_age_sex[0];
      age = area_age_sex[1];
      sex = area_age_sex[2];
      var fdate_rdate = tdItem[2].replace(/<\/p>|<br \/>/g,',').replace(/^.+?>|<.+?>|\s/g,'').replace(/,$/g,'').split(',');
      // 無症状病原体保有者テーブルでは確定日のみなので発症日の情報がない
      if(fdate_rdate.length==2){
        if(fdate_rdate[0] != '不詳'){
          fdate = '2020年' + fdate_rdate[0];
        } else {
          fdate = '';
        }
        if(fdate_rdate[1] != '不詳'){
          rdate = '2020年' + fdate_rdate[1];
        } else {
          rdate = '';
        }
      }else{
        fdate = '';
        rdate = '2020年' + fdate_rdate;
      }
      outPutData = outPutData + patientType + ',' + patientCount + ',' + area + ',' + age + ',' + sex + ',' + fdate + ',' + rdate + '\n';
    }
  }
  return outPutData;
}


function tmpRowFunc(tdItem, patient){
  // patientから実際の患者数を算出する
  var patientAry = patient.replace(/患者|\(.*\)|無症状病原体保有者/g,'').split('～');
  var patientType = patient.replace(/[0-9]/g,'').split('～');
  var tmp = '';
  var area = tdItem[1].replace(/<\/p>|<br \/>|・/g,',').replace(/^.+?>|<.+?>|\s/g,'').replace(/,$/g,'');
  var age = '';
  var sex = '';
  var fdate = '';
  var rdate = tdItem[2].replace(/<\/p>|<br \/>/g,',').replace(/^.+?>|<.+?>|\s/g,'').replace(/,$|検査確定日：/g,'').split('～');
  var counterRange = Number(patientAry[1]) - Number(patientAry[0]);
  for(counter=counterRange; counter>=0; counter--){
    var patient_counter = Number(patientAry[0]) + counter; 
    tmp = tmp + patientType[0] + ',' + patient_counter + ',' + area + ',' + age + ',' + sex + ',' + fdate + ',' + '2020年' + rdate[0] + '\n';
  }
  return tmp;
}
```

## DataStudio
DataStudioを起動して上記のデータを書き込んだスプレッドシートをデータソースに指定すれば簡単に可視化できます
公開をすればWebサーバーなくてもなんちゃってなグラフが公開できちゃいます