はじめに
組み込みエンジニアが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例目)
新型コロナウイルスに関連した患者等の発生について(4月30日公表分)
/* 2-5月分の報道発表資料 */constmonthlyURL=["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"]constclient=require('cheerio-httpcli');varsearchClearlyResultsURL=function(url,results){constp=newPromise((resolve,reject)=>{client.fetch(url,{},function(err,$,res){$("li").each(function(idx){/* タイトルに"新型コロナウイルスに関連した患者"を含むURLを検索する */varisdata=$(this).text().indexOf('新型コロナウイルスに関連した患者');if(isdata!==-1){varanchor=$(this).find("a").eq(0);results.push({"title":$(this).text(),"href":anchor.attr("href"),});}});resolve(results);});});returnp;};getResultsURLs=(async()=>{varresultsURLs=[];for(letiinmonthlyURL){constresult=awaitsearchClearlyResultsURL(monthlyURL[i],resultsURLs);}}
次に、先ほど取得したリンクの中身である報道発表資料を解析して、感染者数と感染者数が発表された日付の部分だけを切り取る。
半角/全角/スペースの有無など、毎日書き方が微妙に違ったりするので、正規表現とか使って、無理やり切り出すようにした。
/* 全角を半角に変換する */functionZenkaku2hankaku(str){returnstr.replace(/[0-9]/g,function(s){returnString.fromCharCode(s.charCodeAt(0)-0xFEE0);});};/* 日付をYYYY/MM/DD のフォーマットにする */functiontoDate(str,delim){vararr=str.replace('','').split(delim)varm=("00"+arr[0]).slice(-2);vard=("00"+arr[1]).slice(-2);return'2020'+"/"+m+"/"+d;};functionapaptDate(str){returntoDate(Zenkaku2hankaku(str.replace('月','/').replace('日','')),'/');};varparseAvtiveNumber=function(url,results){constp=newPromise((resolve,reject)=>{client.fetch(url,{},function(err,$,res){vardate='';varactiveNum='';/* <div class="m-grid__col1"> のタグを抜き出す */$("div[class=m-grid__col1]").each(function(idx){/* 記事を一行ずつLoop を回す */varlines=$(this).text().split('\n');for(vari=0;i<lines.length;i++){/* 空行は無視する */if(lines[i]==''){continue;}/* 日付を取得 正規表現を駆使して抜き出す */varresult=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));}/* 感染者数を取得 */varisdata=lines[i].indexOf('国内感染者は');if(isdata!==-1){varactiveText=lines[i].slice(isdata).replace(/[,,]/,'');varresult=activeText.match(/\d{2,6}/);activeNum=result[0];if(date!=''&&activeNum!=''){/* 日付と感染者数を配列に保存する */results.push({"date":date,"num":activeNum});break;}}}});resolve(results);});});returnp;};/* 先ほど取得した報道資料のURLでLoopを回す */for(letiinresultsURLs){varurl='https://www.mhlw.go.jp'+resultsURLs[i].href;constresult=awaitparseAvtiveNumber(url,resultsDatas);}
Node.jsとmysqlを接続する。
毎回、厚生労働省のページから情報を取得すると時間がかかるので、一度取得したものはデータベースに保存する。
差分があった時ののみデータベースを更新することにして、ページ更新時のアクセススピードを早くする。
今回は、mysqlをデータベースとして用意して、APIはaddDataとgetDataを用意した。
保存するDataは、"日付"と"合計の感染者数"と"一日あたりの増加人数"をデータベースに保存する。
varmysql=require('mysql');/* 自分の環境に合わせてData Baseは修正する必要あり */vardbConfig={host:'127.0.0.1',user:'root',password:'',database:'mhlw_stf'};varconnection=mysql.createConnection(dbConfig);exports.addData=function(data){varquery='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(){returnnewPromise(resolve=>{varquery='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 ],
varexpress=require('express');varejs=require("ejs");varapp=express();constdataManager=require('./datamanager.js');app.engine('ejs',ejs.renderFile);// Google Chart API へ引き渡すデータapp.get('/',(req,res,next)=>{console.log(dataManager.getResultsData());letdata={items:dataManager.getResultsData()};returnres.render("./charts.ejs",data);})varserver=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);functiondrawChart(){vardata=google.visualization.arrayToDataTable(<%-JSON.stringify(items)%>);varoptions={title:'全国コロナ感染者数',hAxis:{title:'Date'},series:[{type:'line',targetAxisIndex:0},{type:'bars',targetAxisIndex:1}],};varchart=newgoogle.visualization.ComboChart(document.getElementById('chart_div'));chart.draw(data,options);}</script></head><body><divid="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
参考にしたサイト
https://habataki-blog.com/output-chart-in-20minuites/
https://www.yoheim.net/blog.php?q=20191101
https://www.youtube.com/watch?v=pnsieVYy72M&list=PLwM1-TnN_NN7-zdRV8YsGUB82VVhfYiWW