はじめに
Node.js で html の表の内容を CSV 形式に加工し、ファイルシステムに保存するスクリプトを作成しました。
当初は、スクレイピングツールのように動的に Web サイトから HTML を取得する想定でしたが、
認証機構を突破したり、javascript で動的に出力されるコンテンツの取得がめんどうだったのと
対象の Web ページが限られていたことから Chrome で要素をエクスポートし、それを加工する方針としました。
当初の目論見
職場で、グループウェアで管理している社内帳票のドキュメント名と管理者、更新日時を
CSV 形式のファイルに落としたい状況となりました。
グループウェアは、Desknet's neo を利用しており、技術スタックは HTML5 + jquery ベースです。
当初の目論見では、URL を指定しあらかじめ決められたルールに従って対象画面のタグを解析し
CSV 形式に適宜加工するスクリプトを想定していました。
挫折、そして妥協
しかし、Desknet's の認証機構を突破し、javascript イベントを発火させて目的のコンテンツを表示させるのは
使い捨てのスクリプトとしては骨が折れました。
そこで、HTML の取得は手動ですることにしました。
幸い、Chrome の「要素の検証」機能により、javascript による動的コンテンツが出力する HTML を参照することができます。
それをバッファにコピーして、テキストエディタに貼り付ければ、望みの生 HTML が手に入ります。ダサいですがとりま妥協。
元データは以下のような形式です。
<table>
<tbody class="co-tbody-list ui-draggable" aria-disabled="false">
<tr class="doc-listmod ">
<td class="co-thd-handle"><span class="co-draghandle">:</span></td>
<td class="co-chk"><input type="checkbox" name="id" value="5527"><input type="hidden" class="jco-id" value="5527"></td>
<td class="doc-list-name jco-listview-widauto" style="width: 282px;"><a href="#cmd=docmsetrefer&id=5527&folder=134" class="jco-dragname" title=" (総-XXX)捺印簿"> (総-XXX)捺印簿</a></td>
<td class="doc-list-entry"><span title="田中 理央">田中 理央</span></td>
<td class="doc-th-ymdhi-his" style="width: 143.5px;"><a href="#cmd=docmsetrevindex&id=5527&folder=134">2013/01/02 12:21</a></td>
</tr>
<tr class="doc-listmod ">
<td class="co-thd-handle"><span class="co-draghandle">:</span></td>
<td class="co-chk"><input type="checkbox" name="id" value="4614"><input type="hidden" class="jco-id" value="4614"></td>
<td class="doc-list-name jco-listview-widauto"><a href="#cmd=docmsetrefer&id=4614&folder=134" class="jco-dragname" title="(総-XXX)社員情報(新規・変更)登録届">(総-XXX)社員情報(新規・変更)登録届</a></td>
<td class="doc-list-entry"><span title="山田 太一">山田 太一</span></td>
<td class="doc-th-ymdhi-his"><a href="#cmd=docmsetrevindex&id=4614&folder=134">2016/02/03 16:42</a></td>
</tr>
</tbody>
</table>
最終型
結果的に以下のコードに落ち着きました。
var client = require('cheerio');
var fs = require('fs');
var file = process.argv[2];
fs.readFile(file, function (err, contents) {
$ = client.load(contents.toString());
var result = [];
$('tr').each(function(i, tr){
var row = {};
$(tr).children().each(function(j, td){
if($(td).hasClass('doc-list-name')){
row.docname = $(td).text();
} else if($(td).hasClass('doc-list-entry')){
row.owner = $(td).text();
} else if($(td).hasClass('doc-th-ymdhi-his')){
row.ymdhi = $(td).text();
}
});
if (Object.keys(row).length != 0){
result.push(row);
}
});
result.forEach( function (h) {
console.log(`"${h.docname}","${h.owner}","${h.ymdhi}"`);
});
});
取得した HTML を解析し、加工するのは cheerio モジュールを使用しました。
cheerio は jquery ライクなセレクターの機能が利用でき、コードをシンプルに記述することができます。
前述の HTML に対する出力結果は以下のとおりです。
" (総-XXX)捺印簿","田中 理央","2013/01/02 12:21"
"(総-XXX)社員情報(新規・変更)登録届","山田 太一","2016/02/03 16:42"
終わりに
一昔前、テキスト加工といえば Ruby や Perl, Python に bash が定番でした。
しかしながら、マークアップ言語の加工は、XPath や xml ライブラリを使いこなす必要があり、煩雑になりがち。
Node.js のおかげで、コマンドライン上で jquery の強力なパース機能をつかえるのは嬉しいです。
私のプログラミング上の母語は Ruby なのですが、
javascript の無名関数は、Ruby のブロックとよく似た概念で、私の思考と親和性が高いです。
今後は積極的に node.js で実装していきたいと思います。