Node.js とGoogle chartを使って新型コロナウィルス感染者数の推移をグラフ化した。

# はじめに
組み込みエンジニアがGWにNode.js の勉強をした。
本を読んで基本的なことが理解できたので、何か実践的に役に立つことに使えないかと思い
全国の新型コロナウィルス感染者数の推移をグラフ化してみた。
以下がその結果である。ニュースでみるグラフと同じようなものが得られた。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535710/9ea0bb4b-309f-9d9f-15f2-44a33cf3431c.png)

# やったこと
1. 厚生労働省のホームページに掲載されている報道発表資料(https://www.mhlw.go.jp/stf/houdou/index.html) から、感染者数を取得する。
 
2. 取得した感染者数と一日の増加人数をデータベース(mySQL)に収納する。

3. Google Chart(https://developers.google.com/chart) を使って、感染者数の合計と増加人数をグラフ化する。

### Node.js の環境設定

~~~
$ npm init
$ npm install ejs --save
$ npm install express --save
~~~

ホームページから取得したData を加工するために、cherrio-httpcli をインストール

~~~
$ npm install cherrio-httpcli --save
~~~

データベースのためにmysqlもインストール

~~~
$ npm install mysql --save
~~~

定期的に厚生労働省のDataを取得するために、node-cronも追加する。

~~~
$ npm install node-cron --save
~~~

### cherrio-httpcliを使って、新型コロナウィルス感染者数を取得
まずは、感染者数を取得するために、厚生労働省のホームページで発表されている報道向け資料を参照することにする。
Dataの取得先を公的機関にして、信頼できる情報を取得することは重要だと思う。

報道資料のタイトルに以下のように複数のパータンがあるので、"新型コロナウイルスに関連した患者" と書かれているリンクを抜き出すようにした。

[新型コロナウイルスに関連した患者の発生について(209～217例目)]
(https://www.mhlw.go.jp/stf/newpage_09859.html)
[新型コロナウイルスに関連した患者等の発生について(４月30日公表分)](https://www.mhlw.go.jp/stf/newpage_11127.html)

~~~javascript
/* 2-5月分の報道発表資料 */
const monthlyURL = ["https://www.mhlw.go.jp/stf/houdou/houdou_list_202002.html",
                  "https://www.mhlw.go.jp/stf/houdou/houdou_list_202003.html",
                  "https://www.mhlw.go.jp/stf/houdou/houdou_list_202004.html",
                  "https://www.mhlw.go.jp/stf/houdou/houdou_list_202005.html"]

const client = require('cheerio-httpcli');

var searchClearlyResultsURL = function( url, results){
    const p = new Promise((resolve, reject) => {
        client.fetch(url, {}, function(err, $, res) {
            $("li").each( function(idx) {

                /* タイトルに"新型コロナウイルスに関連した患者"を含むURLを検索する */
                var isdata = $(this).text().indexOf('新型コロナウイルスに関連した患者');
                if(isdata !== -1) {
                    var anchor = $(this).find("a").eq(0);
                    results.push({
                        "title" : $(this).text(), 
                        "href"  : anchor.attr("href"), 
                    });
                }
            });
            resolve(results);
        });
    });
    return p;
};

getResultsURLs = (async () => {
    var resultsURLs = [];

    for(let i in monthlyURL) {
        const result = await searchClearlyResultsURL(monthlyURL[i], resultsURLs);
    }
}
~~~

次に、先ほど取得したリンクの中身である報道発表資料を解析して、感染者数と感染者数が発表された日付の部分だけを切り取る。
半角/全角/スペースの有無など、毎日書き方が微妙に違ったりするので、正規表現とか使って、無理やり切り出すようにした。

~~~javascript
/* 全角を半角に変換する */
function Zenkaku2hankaku(str) {
    return str.replace(/[０-９]/g, function(s) {
        return String.fromCharCode(s.charCodeAt(0) - 0xFEE0);
    });
};

/* 日付をYYYY/MM/DD のフォーマットにする */
function toDate (str, delim) {
    var arr = str.replace(' ','').split(delim)
    var m = ("00" + arr[0]).slice(-2);
    var d = ("00" + arr[1]).slice(-2);

    return '2020' + "/" + m + "/" + d;
};

function apaptDate(str) {
    return toDate(Zenkaku2hankaku(str.replace('月','/').replace('日','')), '/');
};

var parseAvtiveNumber = function(url, results){
    const p = new Promise((resolve, reject) => {
        client.fetch(url, {}, function(err, $, res) {
            var date = '';
            var activeNum = '';
            /* <div class="m-grid__col1"> のタグを抜き出す */
            $("div[class=m-grid__col1]").each( function(idx) {

                /* 記事を一行ずつLoop を回す */
                var lines = $(this).text().split( '\n' );
                for ( var i = 0; i < lines.length; i++ ) {
                    /* 空行は無視する */
                    if ( lines[i] == '' ) {
                        continue;
                    }
                    /* 日付を取得 正規表現を駆使して抜き出す */
                    var result = lines[i].match(/[1-9１-９]{1,2}月[0-9０-９]{1,2}\s?日[）、].*/);
                    if (result!=null){
                        date = apaptDate(result[0].substr(0, result[0].indexOf('日')+1));
                    }
                    /* 感染者数を取得 */
                    var isdata = lines[i].indexOf('国内感染者は');
                    if(isdata !== -1) {
                        var activeText = lines[i].slice(isdata).replace(/[,，]/, '');
                        var result = activeText.match(/\d{2,6}/);
                        activeNum = result[0];
                        if(date != '' && activeNum != '') {
                            /* 日付と感染者数を配列に保存する */
                            results.push({"date": date, 
                                     "num": activeNum});
                            break;
                        }
                    }
                }
            });
            resolve(results);
        });
    });
    return p;
};

/* 先ほど取得した報道資料のURLでLoopを回す */
for(let i in resultsURLs) {
    var url = 'https://www.mhlw.go.jp'+resultsURLs[i].href;
    const result = await parseAvtiveNumber(url, resultsDatas);
}
~~~

### Node.jsとmysqlを接続する。
毎回、厚生労働省のページから情報を取得すると時間がかかるので、一度取得したものはデータベースに保存する。
差分があった時ののみデータベースを更新することにして、ページ更新時のアクセススピードを早くする。

今回は、mysqlをデータベースとして用意して、APIはaddDataとgetDataを用意した。
保存するDataは、"***日付***"と"***合計の感染者数***"と"***一日あたりの増加人数***"をデータベースに保存する。

~~~javascript
var mysql = require('mysql');

/* 自分の環境に合わせてData Baseは修正する必要あり */
var dbConfig = {
  host: '127.0.0.1',
  user: 'root',
  password: '',
  database: 'mhlw_stf'
};

var connection = mysql.createConnection(dbConfig);

exports.addData = function(data) {

  var query = 'INSERT INTO mhlw_data (created_date, Total_num, Numbyday) VALUES ("' + data.date + '", "' + data.num + '", "' + data.numByday+ '")';

  connection.query(query, function(err, rows) {
  });
}

exports.getData = function() {

  return new Promise(resolve => {

    var query = 'SELECT * FROM mhlw_data';

    connection.query(query, function(err, rows){
      console.log(rows);
      resolve(rows)
    });
  });
}
~~~

### Google Chartを使ってグラフ化
DBから取得してきたDataを以下のように、ejsを使ってGoogle ChartにDataを代入する。
今回は、一日の増加数と合計値を同時に表示したかったので、***Combo Charts***を使うことにした。 
一日の増加数を縦棒グラフ、合計値を折れ線グラフにする。

Google Chart にはこんな感じの配列を渡す必要がある。
  [ '日付', '感染者数', '増加人数' ],
  [ '2020/02/20', 93, 9 ],
  [ '2020/02/21', 105, 12 ],
  [ '2020/02/22', 132, 27 ],
  [ '2020/02/23', 144, 12 ],
  [ '2020/02/24', 156, 12 ],

~~~javascript:app.js
var express = require('express');
var ejs = require("ejs");
var app = express();

const dataManager = require('./datamanager.js');

app.engine('ejs', ejs.renderFile);

// Google Chart API へ引き渡すデータ
app.get('/', (req,res,next) => {
    console.log(dataManager.getResultsData());

    let data = {
      items: dataManager.getResultsData()
    };
    return res.render("./charts.ejs", data);
  })

var server = app.listen(1234, function() {
    console.log('サーバを起動しました');
});
~~~

ejs側でGoogle Chart用のjavascriptを読み込んでくる。

~~~html:chart.ejs
<!DOCTYPE html>
<html>
  <head>
    <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
    <script type="text/javascript">
      google.charts.load('current', {'packages':['corechart']});
      google.charts.setOnLoadCallback(drawChart);

      function drawChart() {
        var data = google.visualization.arrayToDataTable(<%- JSON.stringify(items) %>);

        var options = {
          title: '全国コロナ感染者数',
          hAxis: {title: 'Date'},
          series: [
            { type: 'line', targetAxisIndex: 0 },
            { type: 'bars', targetAxisIndex: 1 }
          ],
        };

        var chart = new google.visualization.ComboChart(document.getElementById('chart_div'));
        chart.draw(data, options);
      }
    </script>
  </head>
  <body>
    <div id="chart_div" style="width: 900px; height: 500px"></div>
  </body>
</html>
~~~

最後に、以下のコマンドを実行して、

~~~
$ node app.js
~~~

ブラウザから、http://localhost:1234 にアクセスすると以下のグラフが取得できた。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535710/cb1c5504-1658-2c78-43b5-7045f2d45a51.png)


## 最後に
以上で、Node.js の全くの初心者でも、サーバー側の実装や、データベースの繋ぎこみ、グラフ化など簡単にできた。
今後は、厚生労働省の報道向け資料がupdate されるたびグラフも最新の情報が更新されることになる。

普段はCとかpythonを使っていて、javascriptは馴染みがなかったが、色々と勉強になった。
async/awaitだったり、funcitonの書き方に慣れない部分が多かったが、慣れてくると簡単に使えて便利。
これからも、もっと勉強してみようと思う。
今更ながら、javascriptはググると必要な情報がすぐに見つかるし、VSCodeでStep実行ができて、Debugもしやすいので、勉強しやすいと思う。

上で使ったコードは一部抜粋なので、完全なコードはこちらに置いておいた。
https://github.com/daiki0321/node-corona-active-count

# 参考にしたサイト

https://habataki-blog.com/output-chart-in-20minuites/

https://www.yoheim.net/blog.php?q=20191101

https://github.com/osamu38/node-express-curriculum/wiki/Node.js%E3%81%A8%E3%83%87%E3%83%BC%E3%82%BF%E3%83%99%E3%83%BC%E3%82%B9%E3%82%92%E6%8E%A5%E7%B6%9A%E3%81%97%E3%82%88%E3%81%86

https://www.youtube.com/watch?v=pnsieVYy72M&list=PLwM1-TnN_NN7-zdRV8YsGUB82VVhfYiWW