これまでの記事
前回の要約
- KdBからCSVファイルをダウンロードした
- npmを使った基本的な開発フローを学んだ
- 文字列をパースした

この記事を読む前に
この記事はシリーズものです。前回の記事を読んである前提で話を進めるので注意してください。
また、ある特定の人にしかわからない単語が出現する可能性が高いです。あらかじめご了承ください。
この記事での開発環境
- MacOS 10.14.3
- Visual Studio Code 1.31.1
- Node.js LTS 10.15.1
今回やること
- javascriptで提供されているStreamAPIを使ってファイル読み込みをする。
- 読み込んできた文字列を扱いやすい文字コードに変換する。
キーワード 文字コード, ファイル読み込み, 高階関数
実際のデータをフォルダに入れよう
javascript内で定義した文字列をパースして配列に変換することができた前回。そこから少しだけステップアップして、今回は実際にダウンロードしたファイルに対してパースしていきます!
まずは状況把握から
$ pwd #作業ディレクトリの確認
/Users/〇〇/myKdb #作業ディレクトリにいることを確認
$ ls
index.js node_modules kdb.csv
package-lock.json package.json
KdBからダウンロードしてきたファイルをディレクトリに追加しています。前回書いた通り、このkdc.csvの中身はShift_JISで文字エンコーディングされていますので、表示すると以下のようになります。
"01AA007","�����Ȋw�I���j�o�X���u","1","1.0","1�E2","�HA","�W��","","���� �P��,�ɓ� �|��,�吼 �a�v,�J�� ����,�A�� �b�q,���� ��,�ז� ��,�גJ ����,���� ��,�ݞ� �_�u,�c�� �ؖȎq,�i�@ ��O�Y,���� ���݂�,���� �v�T,�牮 �ɏt","�����Ȋw��U�S���̑S�A�g��w�@�����ɂ��I���j�o�X�����̏W���u�`�ł���.���ꂼ��̋����������������ʂɊ�Â��������w�̍Ő�[�����ɂ��ďЉ��ƂƂ���,�����̌����̈Ӌ`�⌤���@�̌����Ɖ��p���ɂ��ču�`����.","02AU002�Ɠ������{�B
10/29-10/30","","","Omnibus Lecture in Biology","01AA007","�����Ȋw�I���j�o�X���u2006","2019-02-15 11:17:48" ...
今回はこの状況を考慮して書いていきます。フローとしては ファイル読み込み → 文字コード変換 → パース となります。
実際のデータをパースしてみよう
では早速index.js
を編集していきます。
const fs = require('fs'); // Node.js 標準実装の File System Module を使います
const parse = require('csv').parse; // 前回の記述のショートハンド(動作は同じ)
const filename = 'kdb.csv' // 読み込むファイル名
// ①
const parser = parse( (erorr, data) => {
console.log(data)
})
// ②
fs.createReadStream(__dirname + '/' + filename)
.pipe(parser);
さて、それでは解説していきます。
前半部分で、新しくfs
というNode.jsの標準パッケージを読み込みました。これを読み込むとファイル入出力関連の機能を使うことができます。また、しれっと2行目でショートハンド記述をしていますが、ここら辺は宗教になるので、どうしても気になる方は前回の記事の記述方法に修正して大丈夫です。どちらの記述方法でも問題なく先に進むことができます。
① 未完成な関数
気づいた方もいるかもしれませんが、こちらの記述部分が前回の記述方法と少し違います。
parse( str, (erorr, data) => {
console.log(data);
});
const parser = parse( (erorr, data) => {
console.log(data)
})
説明が難しいのですが、前回は「関数をすぐに実行していた」のに対して、今回は「関数を定義した」のです。よくよくみてみると、前回は引数を2つparse()
に与えていましたが、今回与えた引数は1つだけです。つまり、parse()
は未完成な関数として関数内の処理を行わず、変数parser
に格納されます。このように「関数を返す・関数を引数にとる関数」を高階関数といいます。
今回はparse()
が関数ではなく高階関数として機能し、変数parser
には関数が格納されているのです。
関数を格納したparser
を実際に使うにはあと1つ足りない引数を渡せばOKです。つまりparser("hoge,fuga")
とすれば"hoge,fuga"
をパースしてくれます。
何となくわかったでしょうか?難しいですが先に進みましょう!
② Stream API
何やらかっこいい名前が出てきました。ここでは「ファイル読み込みストリーム」を用意し、そのストリームに対して逐次処理をしています。
イメージとしては原始的なろ過装置です。流れてきた泥水にそれぞれ層でフィルターして最終的に綺麗な水を得るように、流れてきた単純な文字列をそれぞれの処理過程で加工して最終的に欲しいデータを得ると言うことです。(伝わったか...?)
fs.createReadStream(__dirname + '/' + filename)
fs.createReadStream()
は読んで字のまま、読み込みファイルストリームを作るという関数になります。このとき、引数でファイルまでのパスを与えます。
しれっと出てきた__dirname
ですが、これはshellコマンドで言うところのpwd
で表示される文字列(現在の作業ディレクトリまでのパス)に変換されます。なので、環境に左右されず普遍的なパス名を獲得することができる便利なキーワードです。__dirname
では最後のスラッシュまではカバーしてくれないので/
を文字列連結して、最後にCSVファイル名
を連結します。
fs.createReadStream(__dirname + '/' + filename)
.pipe(parser);
次に、そのストリームに対してparser
を施していきます。この時、引数を与えていないように見えますが、ファイルから読み込んできた文字列が自動的に引数に流れ込みます。内部的にはparser(流れ込んできた文字列)
という処理になります。
それでは実行してみましょう
$ node index.js
[
[ '01AB255',
'�A���זE��`���w���KI',
'2',
'3.0',
'1',
'�ʔN',
'���k',
'������',
'',
'�A���זE,�g�D�|�{����ш�`�q�H�w�Ɋւ�����O�̌����_�����u�ǂ���,���̓��e�ɂ��đ��݂ɓ��_��,���̗�������w�[�߂�����.',
'01AB256�Ɠ������{�B\n�A�g��w�@�����Ɋ֘A����w���̂ݎ�u�\\',
'�~',
'���ƒS�������̔��f�ɂ��',
'Seminar in Plant Cell Engineering I',
'01AB255',
'�A���זE��`���w���KI',
'2014-08-13 21:34:20' ],
[ '01AB255', ...
ヌオッ...!おのれ!文字コード!
そうでした、文字コードの処理をしていませんね...
それでは文字コードの処理に必要なパッケージをインストールしましょう。
$ npm install iconv-lite
インストールが終わったらindex.jsを修正していきます。
const fs = require('fs');
const parse = require('csv').parse;
const iconv = require('iconv-lite'); // 文字コード変換用
const filename = 'kdb.csv'
const parser = parse( (erorr, data) => {
console.log(data)
})
fs.createReadStream(__dirname + '/' + filename)
.pipe(iconv.decodeStream('shift_jis')) // Shift_JIS から変換
.pipe(parser);
では変更箇所を見ていきましょう
fs.createReadStream(__dirname + '/' + filename)
.pipe(iconv.decodeStream('shift_jis'))
.pipe(parser);
変換用に.pipe()
が一行追加されました。セミコロンの有無には気をつけましょう。
ここでは、流れてきた文字列をShift_JISから普遍的な文字コードに変換して、次の処理に流します。
次の.pipe(parser)
は人間が読める文字列を受け取ってパースするので、今度こそ読める形式になっていると思います。
それでは実行しましょう
[
[ '01AB256',
'植物細胞遺伝情報学演習II',
'2',
'3.0',
'2',
'通年',
'応談',
'研究室', ...
実際のデータ読み込めた!
これにて第一部完ッ!といったところです!おつかれさまでした!
まだファイルから読み込んできた文字列をjavascriptで調理しやすいデータ形式に変換してきただけですが、扱えるデータにするまでの作業がめちゃめちゃ大変なことは実際多いので、最初の難関突破といっても過言ではありません!
次回は読み込めたデータを実際にブラウザに表示してみましょう!
今回のあとがき
今回はCSVという形式でしたが、javascriptと相性がそこまで良くないということもあって大変かと思います。一般的に使われているJSON形式はjavascriptとの親和性は最高です。というのも、JSONはJavaScript Object Notationの略称であり、気味の悪い拡張子どころか、javascriptの名前が入っているくらい潔いです。
JSON形式に対してはパッケージを1つもインストールすることなくJSON.parse("JSONの:文字列")
でパースできますし、何の加工も必要なくObject型になるのでアレコレと処理しやすいです。
名門ならCSVとかTSVじゃなくてJSONでダウンロードさせてくれよ〜って思いますけど、たぶんよくわからないイザコザとかがあって簡単にはいかないんでしょう。
100歩譲ってCSV形式は軽量化できるので許せるとしても、Excel形式をダウンロードさせるのは謎