はじめに
組み込みエンジニアがGWにNode.js の勉強をした。
本を読んで基本的なことが理解できたので、何か実践的に役に立つことに使えないかと思い
全国の新型コロナウィルス感染者数の推移をグラフ化してみた。
以下がその結果である。ニュースでみるグラフと同じようなものが得られた。
やったこと
-
厚生労働省のホームページに掲載されている報道発表資料(https://www.mhlw.go.jp/stf/houdou/index.html) から、感染者数を取得する。
-
取得した感染者数と一日の増加人数をデータベース(mySQL)に収納する。
-
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 ],
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を読み込んでくる。
<!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 にアクセスすると以下のグラフが取得できた。
最後に
以上で、Node.js の全くの初心者でも、サーバー側の実装や、データベースの繋ぎこみ、グラフ化など簡単にできた。
今後は、厚生労働省の報道向け資料がupdate されるたびグラフも最新の情報が更新されることになる。
普段はCとかpythonを使っていて、javascriptは馴染みがなかったが、色々と勉強になった。
async/awaitだったり、funcitonの書き方に慣れない部分が多かったが、慣れてくると簡単に使えて便利。
これからも、もっと勉強してみようと思う。
今更ながら、javascriptはググると必要な情報がすぐに見つかるし、VSCodeでStep実行ができて、Debugもしやすいので、勉強しやすいと思う。
上で使ったコードは一部抜粋なので、完全なコードはこちらに置いておいた。
https://github.com/daiki0321/node-corona-active-count
参考にしたサイト