Skip to content

Latest commit

 

History

History
175 lines (143 loc) · 7.64 KB

File metadata and controls

175 lines (143 loc) · 7.64 KB

福井県オープンデータ ごみ収集日一覧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に変換します。
*/

const scraper = require('./lib/scraper');
const generator = require('./lib/generator');
const page = 'https://www.pref.fukui.lg.jp/doc/toukei-jouhou/opendata/list_ct_gomisyusyubi.html'; // 「ごみ収集日一覧」ページのURL

(async () => {
    // ページ内の17市町のCSVデータのURLを取得
    const resources = await scraper.scrape(page)
    // すべてのCSVを取得してJSONに変換して出力
    await Promise.all(resources.map(resource => generator.generate(resource)))
    console.log('できたよ!')
})();

絵子「scrapergeneratorっていうのが、樹里が作ったモジュール?」
樹里「そう。scraperで「ごみ収集日一覧」ページからCSVへのリンクを取得している。で、generatorでCSVを取得してJSONに変換している」
絵子「なるほど」
樹里「では、各モジュールの動きをみてみよう」

lib/scraper.js

樹里「次に、ページをスクレイピングして、CSVへのリンクを取得するscraperモジュールだ」

/*
  指定されたページ内の、CSVへのリンクをを取得します。
*/
const fetch = require('node-fetch');
const cheerio = require('cheerio');
const url = require('url');

module.exports.scrape = async (page) => {
    // 指定されたページのHTMLを取得する
    const response = await fetch(page)
    const body = await response.text()
    // cheerioでページをスクレイピング
    const $ = await cheerio.load(body)
    // 末尾が'.csv'のリンクをすべて取得
    const relativePaths = await $('a[href$=".csv"]').map((i, el) => $(el).attr('href')).get()
    // 絶対パスに変換
    const absolutePaths = await relativePaths.map(path => url.resolve(page, path))
    return absolutePaths
}

樹里「スクレイピングにはcheerioというライブラリを使用している」
絵子「知ってる。jQueryっぽく操作できるやつだよね」
樹里「そう。そのcheerioで、属性セレクターを使って、href属性 が ".csv" で終わるa要素、すなわちCSVへのリンクを取得してだな、その配列を返している」
絵子「なるほど」

lib/generator.js

樹里「最後に、データの変換を行うgeneratorモジュールだ」

/*
  指定されたURLのCSVを取得し、文字コードをShift-JISからUTF-8にに変換して出力します。
*/
const path = require('path');
const fs = require('fs-extra');
const fetch = require('node-fetch');
const parse = require('csv-parse/lib/sync');

module.exports.generate = async (resource) => {
    // ファイル名を生成
    const file = path.basename(resource, '.csv') + '.json'
    // 指定されたURLのCSVを取得
    const response = await fetch(resource)
    const buffer = await response.arrayBuffer()
    // CSVの文字コードをShift-JISからUTF-8にに変換
    const decoder = new TextDecoder("Shift_JIS")
    const csv = decoder.decode(buffer)
    // CSVをJSONに変換
    const json = await parse(csv, { columns: true, trim: true })
    // JSONを出力
    const result = await fs.outputJson(path.join('dist', file), json, { spaces: 4 })
    return result
};

樹里「まず、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に変換できた」
絵子「めでたしめでたし、だね」
樹里「いやいや、データの形式を変換しただけで、何も出来上がってないぞ。大事なのは、このデータを使ってどんなアプリを作るかだ」
絵子「そりゃそうだ。さ、次はアプリ制作に挑戦だ!」

Author

8amjp