2
2

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.

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

Posted at

はじめに

組み込みエンジニアがGWにNode.js の勉強をした。
本を読んで基本的なことが理解できたので、何か実践的に役に立つことに使えないかと思い
全国の新型コロナウィルス感染者数の推移をグラフ化してみた。
以下がその結果である。ニュースでみるグラフと同じようなものが得られた。
image.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)
新型コロナウイルスに関連した患者等の発生について(4月30日公表分)

/* 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);
    }
}

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

/* 全角を半角に変換する */
function Zenkaku2hankaku(str) {
    return str.replace(/[0-9]/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-91-9]{1,2}[0-90-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は、"日付"と"合計の感染者数"と"一日あたりの増加人数"をデータベースに保存する。

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 ],

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を読み込んでくる。

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

最後に

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

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

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

参考にしたサイト

2
2
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
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?