【node.js】Shift_JIS・EUC-JPなサイトをスクレイピングしてXPathで扱うPureJSなコード

Profile of concentrated young software developer eating pizza and coding at home

最近、自分専用の Alexa スキルを作ってるんですが、やっぱ、バックエンドは AWS Lambda で動かした方が楽だよねってんで、うっかり触ったこともない node.js に手を出してみました。が、これがまぁ、Lambda + node.js の組み合わせってばお世辞にもスクレイピングに向いてなくって若干の血ヘドを吐くハメに。ということで、ノウハウをメモしておきます。

やりたいこと自体は、「Alexa に質問したら、とあるサイトをリアルタイムでスクレイピングして、結果を答えてくれるスキルを作る」ってだけなんですが。

WEBって、UTF-8 だけじゃなくいろんなエンコーディングのページも混じってるし、それだけならまだしも、HTMLタグの書き方だって微妙なサイトも少なくない。だから、あらゆる文字コードに柔軟に対応できた方がいいし、XPathが使えるに越したこともない。って事です。

Lambda で動かす前提なので、PureJS 縛りです。

ソースはこんな感じ。

'use strict';

var request = require('request');
var iconv = require('iconv-lite');
var parse5 = require('parse5');
var xmlsrlz = require('xmlserializer');
var xpath = require('xpath');
var dom = require('xmldom').DOMParser;

var URL = 'http://navi.meitetsu-bus.co.jp/mb/DepQR.aspx?p=410102000';

function getDocFromURL(url, encoding, callback) {
    request({url: url, encoding: null }, function (error, response, body) {
        if (!error && response.statusCode == 200) {
            // to convert encoding.
            if (encoding != null) {
                body = iconv.decode(body,encoding);
            }
        }
        callback(error, response, body);
    });
};

getDocFromURL(URL, 'Shift_JIS', function(error, response, body) {
    var parsed = parse5.parse(body);
    var xhtml = xmlsrlz.serializeToString(parsed);
    var doc = new dom().parseFromString(xhtml);
    var select = xpath.useNamespaces({'x': 'http://www.w3.org/1999/xhtml'});
    var nodes = select('//*[@id="lbl_OpeSituat"]/text()', doc);
    
    console.log(nodes[0].toString());
    console.log(nodes[1].toString());
    console.log(nodes[2].toString());
    console.log(nodes[3].toString());
});

使用パッケージは、適宜、"npm install" してください。(つか、こういう処理すら外部モジュールなの?という感じ。)

ここではサンプルとして名鉄バスロケーションシステムをスクレイピングしていますが、こういう、Shift_JIS かつ、<BR> タグで区切りまくっているけしからんサイトでも、これなら <BR> タグ単位で情報をスクレイプできるわけです。

(※例えば、先のコードで XPath に '//*[@id="lbl_OpeSituat"]/text()[3]' を指定すれば、BRタグで区切られた4番目の要素だけ取れる)

内容としては、request() で encoding: null を指定すると body にバイナリ(Buffer)が返ってくるので、それをそのまま iconv-lite の decode() に渡してるってとこがポイント。

なお、request() で使えるエンコーディングの一覧はここにまとまっています。Shift_JIS だけでなく EUC_JP からも UTF-8 へ変換できるわけです。

以下、想定問答集。

Q:なんで iconv-lite 使ってんの?
A:iconv はデカくて重くて依存パッケージが多いから。環境によっては Python が無いって言われてインストールできなかったりする。

Q:なんで今さら XPath 使ってんの?
A:世の中、項目間の区切りにBRとかHRタグ使ってるサイトって多いんすよ。

 

Alexa で動くものをペペっと Lambda に載せたいだけだったので、普通に Python でやれば良かったかなー…。

参考情報:

Hatena Pocket Line

コメントを記入