cheerio-httpcli

html client module with cheerio & iconv(-jp|-lite) & promise

cheerio-httpcli - Node.js用WEBスクレイピングモジュール

Node.jsでWEBページのスクレイピングを行う際に必要となる文字コードの変換とHTMLのパースを行った後のオブジェクトを取得できるHTTPクライアントモジュールです。

実装にあたり、以下のモジュールを利用しています。

cheerioはHTMLをjQueryライクにパースしてくれるモジュールです。パース後のオブジェクトを格納する変数名を「$」にすると、$('title').text()のようなjQueryそのままの形で要素の情報を取得できます。

上記npmモジュールの他にURLエンコード用にecl_new.jsも利用しています。

  1. 取得先WEBページの文字コードを自動で判定してHTMLをUTF-8に変換してくれる。
  2. UTF-8に変換したHTMLをcheerio.load()でパースしたオブジェクトも取得できる。
  3. Node.jsお馴染みのコールバック形式と最近の流行であるプロミス形式どちらにも対応。
  4. cheerioのprototypeを拡張し、フォームの送信やリンクのクリックをエミュレート。
  5. ブラウザ指定による簡単User-Agent切り替え機能。
  6. 現在のクッキーの内容を簡単に取得できる(読み取り専用)。
npm install cheerio-httpcli

urlで指定したWEBページをGETメソッドで取得し、文字コードの変換とHTMLパースを行いcallback関数に返します。

callback関数には以下の4つの引数が渡されます。

  1. Errorオブジェクト
  2. cheerio.load()でHTMLコンテンツをパースしたオブジェクト(独自拡張版)
  3. requestモジュールのresponseオブジェクト(独自拡張版)
  4. UTF-8に変換したHTMLコンテンツ

GET時にパラメータを付加する場合は第2引数のget-paramに連想配列で指定します。

var client = require('cheerio-httpcli');
 
// Googleで「node.js」について検索する。
var word = 'node.js';
 
client.fetch('http://www.google.com/search', { q: word }, function (err, $, res, body) {
  // レスポンスヘッダを参照
  console.log(res.headers);
 
  // HTMLタイトルを表示
  console.log($('title').text());
 
  // リンク一覧を表示
  $('a').each(function (idx) {
    console.log($(this).attr('href'));
  });
});

同梱の「example/google.js」はGoogle検索結果の一覧を取得するサンプルです。参考にしてください。

fetch()の第3引数であるcallback関数を省略すると、戻り値としてPromiseオブジェクトが返ります。先ほどのサンプルをプロミス形式で呼び出すと以下のようになります。

var client = require('cheerio-httpcli');
 
// Googleで「node.js」について検索する。
var word = 'node.js';
 
// callbackを指定しなかったのでPromiseオブジェクトが返る
var p = client.fetch('http://www.google.com/search', { q: word })
p.then(function (result) {
  // レスポンスヘッダを参照
  console.log(result.response.headers);
 
  // HTMLタイトルを表示
  console.log(result.$('title').text());
 
  // リンク一覧を表示
  result.$('a').each(function (idx) {
    console.log(result.$(this).attr('href'));
  });
})
 
p.catch(function (err) {
  console.log(err);
});
 
p.finally(function () {
  console.log('done');
});

callback関数を指定しないfetch()の戻り値をp変数が受け取り、そのp変数を通してthen(正常終了時)およびcatch(エラー発生時)の処理を行っています。また、正常終了でもエラーでも必ず最後に通る処理であるfinallyも使用できます。

thenに渡されるパラメータはコールバック形式で呼び出した際にcallback関数に渡されるものと同じですが、第1引数のオブジェクトにまとめて入っているという点で異なるのでご注意ください。

  • error ... Errorオブジェクト
  • $ ... cheerio.load()でHTMLコンテンツをパースしたオブジェクト(独自拡張版)
  • response ... requestモジュールのresponseオブジェクト(独自拡張版)
  • body ... UTF-8に変換したHTMLコンテンツ
.then(function (result) {
  console.log(result); => {
                            error: ...,
                            $: ...,
                            response: ...,
                            body: ...
                          };
});

とあるサイトのトップページにアクセスして、その中のとあるページに移動して...というように順を追ってWEBページに潜っていきたい場合などもメソッドチェーンでこんな感じに書くことができます。

var client = require('cheerio-httpcli');
 
client.fetch(<TOPページのURL>)
.then(function (result) {
  // 何か処理
  return client.fetch(<ページAのURL>);    // Promiseオブジェクトを返す
})
.then(function (result) {
  // 何か処理
  return client.fetch(<ページA-1のURL>);  // Promiseオブジェクトを返す
})
.then(function (result) {
  // 何か処理
  return client.fetch(<ページA-2のURL>);  // Promiseオブジェクトを返す
})
.catch(function (err) {
  // どこかでエラーが発生
  console.log(err);
})
.finally(function () {
  // TOPページ => ページA => ページA-1 => ページA-2の順にアクセスした後に実行される
  // エラーが発生した場合もcatchの処理後に実行される
  console.log('done');
});

実体はrsvpのPromiseオブジェクトなので、詳細はそちらのドキュメントをご覧ください。

fetch()の第3引数のcallback関数を指定した場合はPromiseオブジェクトは返しません。したがってコールバック形式で呼び出しつつPromiseオブジェクトで何かをするということはできません。

ブラウザごとのUser-Agentをワンタッチで設定するメソッドです。

var client = require('cheerio-httpcli');
 
client.setBrowser('chrome');    // GoogleChromeのUser-Agentに変更
client.setBrowser('android');   // AndroidのUser-Agentに変更
client.setBrowser('googlebot'); // GooglebotのUser-Agentに変更

User-Agentを指定したブラウザのものに変更した場合はtrue、対応していないブラウザを指定するとUser-Agentは変更されずにfalseが返ります。

対応しているブラウザは以下のとおりです。

  • ie default
  • chrome
  • firefox
  • opera
  • safari
  • ios
  • android
  • googlebot

なお、細かいバージョンの指定まではできないので、そういった指定も行いたい場合は手動で以下のようにUser-Agentを指定してください。

// IE6のUser-Agentを手動で指定
client.headers['User-Agent'] = 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)';

cheerio-httpcliは、実行時にインストールされているiconv系のモジュールをチェックして利用するモジュールを自動的に決定しています。優先順位は以下のとおりです。

  1. iconv-jp
  2. iconv
  3. iconv-lite

iconv-liteはcheerio-httpcliのインストール時に依存モジュールとして一緒にインストールされますが、ネイティブモジュールであるiconv-jpやiconvがインストールされている場合、処理速度や対応文字コードの多さというメリットがあるそちらを優先してロードするようになっています。

このメソッドは自動的にロードされたiconv系モジュールを破棄して、使用するiconv系モジュールを手動で指定するためのものです。モジュールテスト時の切り替え用メソッドなので基本的には実用性はありません。

iconv-module-nameには使用するiconv系モジュール名('iconv-jp', 'iconv', 'iconv-lite')のいずれかの文字列を指定します。

var client = require('cheerio-httpcli');
 
// あえてiconv-liteを使用
client.setIconvEngine('iconv-lite');
client.fetch( ...

cheerio-httpcliのバージョン情報です。

requestモジュールで使用するリクエストヘッダ情報の連想配列です。デフォルトでは何も指定されていませんが、fetch()実行時にUser-Agentが空の場合は自動的にUser-AgentにIE11の情報が入ります。

requestモジュールで指定するタイムアウト情報です。デフォルトでは30秒となっています(効いているかどうか不明)。

サーバーとの通信にgzip転送を使用するかどうかを真偽値で指定します。デフォルトはtrue(gzip転送する)です。

リファラーを自動でセットするかどうかの指定です。trueにすると1つ前にfetch()したページのURLが自動でリクエストヘッダのRefererにセットされます。デフォルトはtrueです。

cheerio-httpcliではcheerioオブジェクトのprototypeを拡張していくつかの便利メソッドを実装しています。

取得したWEBページに関する情報(URLとエンコーディング)を取得できます。

client.fetch('http://hogehoge/', function (err, $, res, body) {
  var docInfo = $.documentInfo();
  console.log(docInfo.url);      // http://hogehoge/
  console.log(docInfo.encoding); // 'utf-8'
});

fetch()で指定したURLがリダイレクトされた場合はリダイレクト先のURLがurlに入ります。encodingに関しても同様で、最終的に到達したページのエンコーディングが入ります。

aタグでのみ使用できます。

href属性に指定されているURLと取得したページのURLを組み合わせて移動先のURLを作成し、fetch()を実行します。fetch()と同様に引数のcallback関数の有無でコールバック形式とプロミス形式の指定を切り替えられます。

client.fetch('http://hogehoge/')
.then(function (result) {
  // id="login"の子のリンクをクリック(プロミス形式)
  return result.$('#login a').click();
})
.then(function (result) {
  // クリックした先のURL取得後の処理
});

注意点として、このclick()メソッドはjavascriptリンクやonclick="..."などの動的処理には対応していません。あくまでもhrefのURLに簡単にアクセスできるための機能です。

なお、$(...)で取得したaタグオブジェクトが複数ある場合は先頭のオブジェクトに対してのみ実行されます。

formタグでのみ使用できます。

指定したフォーム内に配置されているinputcheckboxなどのフォーム部品から送信パラメータを自動作成し、action属性のURLにmethod属性でフォームを送信します。fetch()と同様に引数のcallback関数の有無でコールバック形式とプロミス形式の指定を切り替えられます。

また、フォーム送信パラメータはparam引数で指定した連想配列の内容で上書きできるので、利用する側ではパラメータを変更したい項目だけ指定するだけで済みます。

client.fetch('http://hogehoge/')
.then(function (result) {
  // ユーザー名とパスワードだけ入力して、あとはフォームのデフォルト値で送信する
  var loginInfo = {
    user: 'guest',
    pass: '12345678'
  };
 
  // name="login"フォームを送信(コールバック形式)
  result.$('form[name=login]').submit(loginInfo, function (err, $, res, body) {
    // フォーム送信後に移動したページ取得後の処理
  });
})

cheerio-httpcliは内部でクッキーも保持するので、ログインが必要なページの取得などもsubmit()でログイン後に巡回できるようになります。

その他の仕様はclick()と同様です。

  • onsubmit="xxx"や送信ボタンのonclick="..."で実行される動的処理には対応していません。
  • $(...)で取得したformタグオブジェクトが複数ある場合は先頭のオブジェクトに対してのみ実行されます。

fetch()cheerio.click()cheerio.submit()などで取得できるresponseオブジェクトはrequestモジュールで取得したものですが、独自拡張としてcookiesプロパティを付け足しています。

client.fetch('http://hogehoge/')
.then(function (result) {
  // プロミス形式でログインフォーム送信
  return result.$('form[name=login]').submit({ user: 'hoge', pass: 'fuga' })
})
.then(function (result) {
  // ログイン後のクッキー内容確認
  console.log(result.response.cookies);
});

このcookiesプロパティには現在取得したページのサーバーから送られてきたクッキーのキーと値が連想配列で入っています。セッションIDやログイン状態の確認などに使えるかもしれません。

なお、このcookiesの値を変更してもリクエスト処理には反映されません。クッキー確認専用のプロパティです。

  • 文字コードの判別はjschardetで高精度で判別できた場合はその情報を使用しますが、そうでない場合は<head>タグのcharset情報を参照します。後者での判別時においてcharsetで指定された文字コードとWEBページの実際の文字コードが異なる場合は変換エラーや文字化けが発生します。

MIT licenseで配布します。

© 2013 ktty1220