※この記事は、私がGitHubに公開しているnode.js製ツール「福井県オープンデータ ごみ収集日一覧CSVをJSONに変換するツール」のREADMEを転載したものです。
解説は「恋に落ちるコード.js」の絵子と樹里です。
福井県オープンデータ ごみ収集日一覧CSVをJSONに変換するツール
「福井県オープンデータライブラリ」で公開されている、県内17市町の「ごみ収集日一覧」データの形式を、CSV(Shift-JIS)からJSONに変換します。
Description
樹里「というわけで、福井県内自治体のごみ収集日一覧JSONデータを作成するアプリを作ってみた」
絵子「ほう。なんでまた突然」
樹里「それがな、久しぶりに福井県オープンデータに公開されてるデータを使って、アプリでも作ってみようとしたんだがな」
絵子「福井県は昔からオープンデータの公開に熱心だよね」
樹里「で、ごみ収集日一覧のデータを見てみたんだが、なんとShift-JISのCSVなんだよ。この令和の時代に」
絵子「うーん。確かにそのままだと扱いにくいよね」
樹里「Shift-JISのCSVなんだよ。この令和の時代に」
絵子「2回言わなくていいよ。あと太字にしなくてもいいよ」
樹里「これではどうしようもないので、まずはちゃんとしたUTF-8のJSONに変換するツールから作ってみたわけだ」
絵子「なるほど。経緯はよくわかった」
Usage
樹里「使い方は簡単。Node.js製のアプリなので……」
git clone https://github.com/8amjp/fukui-opendata-gomisyusyubi-json.git
cd fukui-opendata-gomisyusyubi-json
npm install
樹里「上記のコマンドでインストールして、」
node index.js
樹里「と実行すれば、dist
ディレクトリにJSONデータが出力される」
絵子「あら本当に簡単」
樹里「じゃあ、アプリがどういう動きをするのか解説しよう」
index.js
樹里「まずは、メインとなるコードだ」
/*
「福井県オープンデータライブラリ」の「ごみ収集日一覧」ページで公開されている
収集日のCSVデータ(Shift-JIS)をJSONに変換します。
*/constscraper=require('./lib/scraper');constgenerator=require('./lib/generator');constpage='https://www.pref.fukui.lg.jp/doc/toukei-jouhou/opendata/list_ct_gomisyusyubi.html';// 「ごみ収集日一覧」ページのURL(async()=>{// ページ内の17市町のCSVデータのURLを取得constresources=awaitscraper.scrape(page)// すべてのCSVを取得してJSONに変換して出力awaitPromise.all(resources.map(resource=>generator.generate(resource)))console.log('できたよ!')})();
絵子「scraper
とgenerator
っていうのが、樹里が作ったモジュール?」
樹里「そう。scraper
で「ごみ収集日一覧」ページからCSVへのリンクを取得している。で、generator
でCSVを取得してJSONに変換している」
絵子「なるほど」
樹里「では、各モジュールの動きをみてみよう」
lib/scraper.js
樹里「次に、ページをスクレイピングして、CSVへのリンクを取得するscraper
モジュールだ」
/*
指定されたページ内の、CSVへのリンクをを取得します。
*/constfetch=require('node-fetch');constcheerio=require('cheerio');consturl=require('url');module.exports.scrape=async(page)=>{// 指定されたページのHTMLを取得するconstresponse=awaitfetch(page)constbody=awaitresponse.text()// cheerioでページをスクレイピングconst$=awaitcheerio.load(body)// 末尾が'.csv'のリンクをすべて取得constrelativePaths=await$('a[href$=".csv"]').map((i,el)=>$(el).attr('href')).get()// 絶対パスに変換constabsolutePaths=awaitrelativePaths.map(path=>url.resolve(page,path))returnabsolutePaths}
樹里「スクレイピングにはcheerioというライブラリを使用している」
絵子「知ってる。jQueryっぽく操作できるやつだよね」
樹里「そう。そのcheerioで、属性セレクターを使って、href
属性 が ".csv" で終わるa
要素、すなわちCSVへのリンクを取得してだな、その配列を返している」
絵子「なるほど」
lib/generator.js
樹里「最後に、データの変換を行うgenerator
モジュールだ」
/*
指定されたURLのCSVを取得し、文字コードをShift-JISからUTF-8にに変換して出力します。
*/constpath=require('path');constfs=require('fs-extra');constfetch=require('node-fetch');constparse=require('csv-parse/lib/sync');module.exports.generate=async(resource)=>{// ファイル名を生成constfile=path.basename(resource,'.csv')+'.json'// 指定されたURLのCSVを取得constresponse=awaitfetch(resource)constbuffer=awaitresponse.arrayBuffer()// CSVの文字コードをShift-JISからUTF-8にに変換constdecoder=newTextDecoder("Shift_JIS")constcsv=decoder.decode(buffer)// CSVをJSONに変換constjson=awaitparse(csv,{columns:true,trim:true})// JSONを出力constresult=awaitfs.outputJson(path.join('dist',file),json,{spaces:4})returnresult};
樹里「まず、node-fetchでCSVを取得して、Shift-JISからUTF-8に変換する」
絵子「へー、TextDecoderっていうので文字コードを変換できるんだね」
樹里「で、csvというライブラリのcsv-parse
という機能を使って、CSVをJSONに変換しているわけだ」
絵子「便利なライブラリだねー」
樹里「さて、処理の結果、このようなJSONが出力される」
[{"行":"あ","音":"あ","町名":"在田町","読み":"あいだ","燃える":"火・金","燃えない":"2・4木","プラスチック製容器包装":"月","カン":"1・3水","ビン":"4水","ペットボトル":"2水","ダンボール":"3水","蛍光灯":"4木","キーワード":"清水","備考":"清水南"},//以下略
絵子「これ、キーが日本語になってるけど問題ないの?」
樹里「ああ、仕様に則った正しいJSONだぞ」
絵子「へー、そうなんだ」
樹里「なにより、Shift-JISのCSVよりははるかに扱いやすい」
絵子「よっぽどキライなんだね」
樹里「……さて、無事にShift-JISのCSVをJSONに変換できた」
絵子「めでたしめでたし、だね」
樹里「いやいや、データの形式を変換しただけで、何も出来上がってないぞ。大事なのは、このデータを使ってどんなアプリを作るかだ」
絵子「そりゃそうだ。さ、次はアプリ制作に挑戦だ!」