Node.jsで文字コードを推測しつつスクレイピングしてみる


サーバ さくらのVPS 1G
OS CentOS release 6.3 (Final) x86_64
Node.js v0.9.0
node-iconv(文字コード変換) 1.2.3
node-icu-charset-detector(文字コード推測) v0.0.3
cheerio(jQueryライクなDOM解析) 0.9.0
サンプルコード 0.0.1

Webページのタイトル取得例

Node.jsでスクレイピングする方法は色々と公開されていますが、文字コードを推測しつつページを取得するような処理の方法がわからなかったため、調べながら書いてみました。具体的には、URLが発言されるとタイトルを返すIRC-BOTの機能をイメージしています。

サンプルを動かす

ICUのインストール

今回文字コードの推測に使うnode-icu-charset-detectorは、ICUのNode.jsバインディングなので、元となるICUがインストールされていない場合は、先にインストールしておく必要があります。

root権限を使える場合は、yumやapt-getを使うのが簡単です。

% sudo apt-get install libicu-dev
% sudo yum install libicu-devel

root権限を使えない場合は、ソースコードからコンパイルする方法もあります。今回は~/user/localにインストールしてみます。

% wget http://download.icu-project.org/files/icu4c/49.1.2/icu4c-49_1_2-src.tgz
% tar zxf icu4c-49_1_2-src.tgz
% cd icu/source
% ./configure --prefix=~/usr/local
% make
% make install

node-icu-charset-detectorのインストール時に~/usr/local/bin/icu-configを使うので、~/usr/local/binへのPATHを通しておきます。

export PATH=$HOME/usr/local/bin:$PATH

パッケージのインストール

サンプルをcloneしてきていつものようにnpm install

% git clone git://github.com/fumiz/node-scraping-sample.git
% cd node-scraping-sample
% npm install

動かす

サンプルは、第一引数にURLを指定するとそのURLが示すウェブページを取得し、titleタグの中身を出力するようになっています。

% node app.js http://blog.fumiz.me/2012/07/27/hubot-irc-bot-easy/
hubotでIRCのBOTを簡単に動かす(hubot2.3 + hubot-irc0.1.0) | mitc
% coffee app.coffee http://blog.fumiz.me/2012/07/27/hubot-irc-bot-easy/
hubotでIRCのBOTを簡単に動かす(hubot2.3 + hubot-irc0.1.0) | mitc

説明

var util    = require("util");
var urlutil = require("url");
var http    = require('http');
var https   = require('https');
var Iconv   = require("iconv").Iconv;
var cheerio = require('cheerio')

var charsetDetector = require("node-icu-charset-detector");
var CharsetMatch    = charsetDetector.CharsetMatch;

var getWebPageTitle = function(url, callback) {
  var urlElements = urlutil.parse(url, false);
  var requester = (urlElements.protocol === 'https:') ? https : http;

  var options = {
    host: urlElements.hostname,
    port: urlElements.port,
    path: urlElements.path,
    headers: {'user-agent': 'node title fetcher'}
  };

  var request = requester.get(options, function(response) {
    var binaryText = '';
    response.setEncoding('binary');

    response.on('data', function(chunk) {
      binaryText += chunk
    });

    response.on('end',function() {
      var textBuffer = new Buffer(binaryText, 'binary');
      var charsetMatch = new CharsetMatch(textBuffer);
      var text = bufferToString(textBuffer, charsetMatch.getName());

      var $ = cheerio.load(text);
      var title = $('title').text().replace(/\n/g, '');
      var title = (title === '') ? util.format("couldn't find title from %s", url) : title;

      callback(title);
    });
  });

  request.setTimeout(2000, function() {
    request.abort()
  });

  request.on('error', function(error) {
    callback(util.format("couldn't fetch web page from %s", url));
  });
};

var bufferToString = function(buffer, charset) {
  try {
    return buffer.toString(charset);
  } catch(error) {
    charsetConverter = new Iconv(charset, "utf8");
    return charsetConverter.convert(buffer).toString();
  }
};

var url = process.argv[2];
getWebPageTitle(url, function(title) {
  console.log(title);
});

だいたい見ての通りです。
http.getのresponse.onでウェブページのデータをバイナリで取得して、node-icu-charset-detectorで文字コードを推測、その情報を元にnode-iconvでutf8化している感じですね。

参考文献

Leave a Comment


NOTE - You can use these HTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>