node.jsでhello world気分でスクレイピング2

前回は、環境構築+JQueryを使うための調べもので終わってしまいましたが、今回は、いろんなエンコードのサイトを取得して、utf-8に変換し、JQueryを使えるところまで作ります。
スクレイピングするにもまずはスタートラインにたたないとね。

処理を作る前に、iconvが必要なので、まずはその取得からです。

iconvのインストール

昔は、npm未対応だったみたいですが、現在はnpmに対応しているようです。
以下のコマンドでインストールできます。

npm install iconv-jp

自分は、これであっさり使えるようになりました(コンパイルしてたから、もしかしたら上手く動かない場合もあるかも)。

いろんなエンコードのサイトを取得して、utf-8に変換する処理

node.jsとjQueryでスクレイピングするウェブアプリの作り方 のコードをベースに不要なところを削って、v0.6.7のドキュメントみて、htmlの取得部分を書き換えました。
あとは、以下のサイトを参考にリダイレクトできるようにしました(短縮URLぐらいは処理できるようにした方がいいかと思って)。

node.jsでURLをGETしてファイルに保存する


そんな感じで作ったのが以下のコード。

// httpGet.js
var http = require('http'),
    https = require('https'),
    iconv = require('iconv').Iconv,
    url = require('url');

// Bufferを連結する
function concatBuffer(src1 /* , src2, ... */) {
    var i, buf, start;
    var len = 0;

    for (i = 0; i < arguments.length; ++i) {
        len += arguments[i].length;
    }

    buf = new Buffer(len);
    start = 0;
    for (i = 0; i < arguments.length; ++i) {
        var chunk = arguments[i];
        chunk.copy(buf, start, 0);
        start += chunk.length;
    }

    return buf;
}

// HTTPレスポンスとBufferからエンコーディングを検出し
// レスポンスボディを文字列で返す
function convertCharset(response, buf) {
    var charset = null;

    var content_type = response.headers['content-type'];
    if (content_type) {
        re = content_type.match(/\bcharset=([\w\-]+)\b/i);
        if (re) {
            charset = re[1];
        }
    }

    if (!charset) {
        var bin = buf.toString('binary');
        re = bin.match(/<meta\b[^>]*charset=([\w\-]+)/i);
        if (re) {
            charset = re[1];
        } else {
            charset = 'utf-8';
        }
    }

    switch (charset) {
    case 'ascii':
    case 'utf-8':
        return buf.toString(charset);
        break;

    default:
        var ic = new iconv(charset, 'utf-8');
        var buf2 = ic.convert(buf);
        return buf2.toString('utf8');
        break;
    }
}

exports.httpGet = function(targetUrl, callback) {
  var callee = arguments.callee;
  var chunks = [];
  var opts = url.parse(targetUrl);
  var req = (opts.protocol.match(/https/) ? https : http);
  req.get(opts, function(res) {
    if(res.statusCode == 301 ||res.statusCode == 302) {
        callee(res.headers.location, callback);
    } else if(res.statusCode == 200) {
      res.on('data', function (chunk) {
        chunks.push(chunk);
      });
      res.on('end', function () {
        var buf = concatBuffer.apply({}, chunks);
        delete(chunks);
        var body = convertCharset(res, buf);
        callback(null, body);
      });
    } else {
      callback(new Error('statusCode is ' + res.statusCode),null);
    }
    res.on('close', function (err) {
      delete(chunks);
      callback(err, null);
    });
  });
}

いろんなエンコードのサイトを取得して、utf-8に変換して、JQueryをつかるようにする処理

htmlを取得できるようになったので、次はJQueryです。
httpGet.jsを読み込んで、JQueryを使えるようにする感じにしました。

// httpGetWithJquery.js
// jsdomとjQueryのラッパー

var jsdom = require('jsdom/lib/jsdom'),
    httpget = require('httpGet');

// URLからリソースを読み込みjQueryを追加する
exports.getWithJqeruy = function(targetUrl,jquery_js, callback) {
    httpget.httpGet(targetUrl , function(err, body) {
        if (err) {
            if (callback) {
                callback(err,null,null);
            } else {
                throw err;
            }
        }
        var options = {};
        options.features = {};
        options.features.FetchExternalResources = false;
        options.features.ProcessExternalResources = false;
        var window = jsdom.jsdom(body, null, options).createWindow();
        jsdom.jQueryify(window, jquery_js, function(window, $) {
            // callbackを呼び出す
            if (callback) {
                callback(null, window, $);
            }
        });
    });
}

サイトを取得して、utf-8に変換してみる(動作確認)

動作確認に以下のようなコードを作成。

#!/usr/bin/env node
// clientTesthttpGet.js

httpGet = require('httpGet');

if (process.argv.length <= 2) {
    console.log('Usage: node clientTesthttpGet.js [url]');
    process.exit(1);
}
process.argv.forEach(function(val, index, array) {
  if (index >= 2) {
    httpGet.httpGet(val , function(err, body) {
        if (err) {
          console.log(err);
        }
        console.log(body);
    });
  }
});


以下のようにターミナルで実行します。

node clientTesthttpGet.js [取得したいサイトのurl]

いくつか試してみましたが、euc-jpのサイトも正常に変換されるし、短縮urlも上手く取得できました。



サイトを取得して、utf-8に変換し、JQueryをつかるようにしてみる(動作確認)


で、やっと目標だったJQueryを使ってみます。
動作確認用に、selectorを使ってテキストを取得する処理を作成。

#!/usr/bin/env node
// clientTesthttpGetWithJquery.js

var httpGet = require('httpGetWithJquery');
var jquery_js = 'https://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js';

if (process.argv.length <= 2) {
    console.log('Usage: node clientTesthttpGet.js [url]');
    process.exit(1);
}
process.argv.forEach(function(val, index, array) {
  if (index >= 2) {
    httpGet.getWithJqeruy(val, jquery_js, function(err, window, $, body) {
        if (err) {
          console.log(err);
        }
        console.log($('.hatena-moduletitle').text());
    });
  }
});


以下のようにターミナルで実行します。

node clientTesthttpGetWithJquery.js [はてなブログのurl]


軽くためしてみましたが、きちんとselectorが機能してブログのタイトル取得できました。

これで、スクレイピングのするための準備はできましたね。
JQueryの力で、ごにょごにょすればスクレイピングし放題です。
まあ、JQueryでごにょごにょできればだけど(笑)。