Help us understand the problem. What is going on with this article?

Node.jsのスクレイピングモジュール「cheerio-httpcli」が大規模アップデートして帰ってきた

More than 3 years have passed since last update.

なんか大げさなタイトルですが要するにバージョンアップのお知らせだったります。

「cheerio-httpcliって何よ」という場合は紹介記事をご覧になると、こちらの記事もより分かりやすくなると思います。

簡単に説明するとこんな感じのことができるモジュールです。

Google検索
var client = require('cheerio-httpcli');

// Googleで「node.js」について検索する。
client.fetch('http://www.google.com/search', { q: 'node.js' }, function (err, $, res) {
  // レスポンスヘッダを参照
  console.log(res.headers);

  // HTMLタイトルを表示
  console.log($('title').text());

  // リンク一覧を表示
  $('a').each(function (idx) {
    console.log($(this).attr('href'));
  });
});

さて、今回のバージョンアップで結構機能を追加しました。npmにも登録済みなので

$ npm install cheerio-httpcli

でインストールできます(バージョン0.3.0)。

ソースやサンプルはこちら

では新機能の紹介です。

:eyes: 目玉機能1: コールバック形式とプロミス形式のハイブリッド化

今まではNode.jsお馴染みのコールバック形式での実装だったので、順を追ってWEBサイトの中に潜っていく場合はいわゆるコールバック地獄が発生してしまう状態でした。

コールバック形式
var client = require('cheerio-httpcli');

client.fetch('http://hogehoge/', function (err, $, res) {
  if (err) {
    return console.log(err);
  }
  client.fetch('http://hogehoge/page-1.html', function (err, $, res) {
    if (err) {
      return console.log(err);
    }
    client.fetch('http://hogehoge/page-1-1.html', function (err, $, res) {
      if (err) {
        return console.log(err);
      }
      client.fetch('http://hogehoge/page-1-2.html', function (err, $, res) {
        if (err) {
          return console.log(err);
        }
        // リンク一覧を表示
        $('a').each(function (idx) {
          console.log($(this).attr('href'));
        });
      });
    });
  });
});

今回のバージョンアップにより今流行のプロミス形式で呼び出すこともできるようになりました。

上記コールバック地獄もプロミス形式のメソッドチェーンを使用すればこんな感じにスッキリさせることができます。エラーの捕捉も下の方にあるcatchでまとめて面倒見てくれます。

プロミス形式
client.fetch('http://hogehoge/')
.then(function (result) {
  return client.fetch('http://hogehoge/page-1.html');
})
.then(function (result) {
  return client.fetch('http://hogehoge/page-1-1.html');
})
.then(function (result) {
  return client.fetch('http://hogehoge/page-1-2.html');
})
.then(function (result) {
  var $ = result.$;
  // リンク一覧を表示
  $('a').each(function (idx) {
    console.log($(this).attr('href'));
  });
})
.catch(function (err) {
  console.log(err);
})
.finally(function () {
  // 処理完了でもエラーでも最終的に必ず実行される
});

それぞれの形式の使い分け方

fetch()でWEBページを取得する際の引数にコールバック関数を指定した場合は処理後にそのコールバック関数が呼ばれ、コールバック関数を省略した場合は戻り値にPromiseオブジェクトが返る仕様となっています。

コールバック関数を指定した場合はPromiseオブジェクトは返しませんので、コールバック関数で処理しつつ同時進行でプロミス形式でなんちゃらするといったカオスなことはできません。

引数の受け取り方の違い

従来のコールバック形式の場合は

コールバック形式
client.fetch('http://hogehoge/', { param1: 'fuga' }, function (err, $, res, body) {
  console.log(err);
  console.log($);
  console.log(res);
  console.log(body);
});

というようにコールバック関数に引数が4つ入る形で受け取りますが、プロミス形式では

プロミス形式
client.fetch('http://hogehoge/', { param1: 'fuga' })
.then(function (result) {
  console.log(result.error);
  console.log(result.$);
  console.log(result.response);
  console.log(result.body);
});

というように1つの引数内にコールバック形式の時と同じ情報がまとめて入ります。

:eyes: 目玉機能2: cheerioオブジェクトのプロトタイプを拡張してフォームの送信やリンクのクリックをエミュレート

fetch後のコールバックやプロミスのthenで取得できるcheerioオブジェクトに以下のメソッドを独自拡張として実装しました。

$.documentInfo()

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

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

$(link-element).click([ callback ])

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

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

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

javascriptリンクやonclick="..."などの動的処理には対応していません。あくまでもhrefのURLに簡単にアクセスできるための機能です。

$(form-element).submit([ param, callback ])

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

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

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

もちろん、cheerio-httpcliの売りである文字コードの自動判別にも対応しているので、フォームの設置されているページの文字コードに合わせたURLエンコードをして送信してくれます。

$(...).submit()
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()でログイン後に巡回できるようになります。

フォームの送信サンプルはGitHubリポジトリにもいくつか用意していあるので参考にしてみてください。

ちなみにExcite翻訳の自動実行はこんな感じです。

Excite翻訳
var client = require('cheerio-httpcli');
client.fetch('http://www.excite.co.jp/world/')
.then(function (result) {
  return result.$('#formTrans').submit({
    auto_detect: 'on',
    before: '翻訳したい文字列をうんたらかんたら'
  });
})
.then(function (result) {
  console.log('翻訳結果: ' + result.$('#after').val());
});

click()submit()はスクレイピングモジュールの老舗であるPerlのWWW::Mechanize にインスパイアされています。

:eyes: 目玉機能3: ブラウザごとのUser-Agentの簡易指定機能実装

今まで特定のブラウザになりきろうとする場合は手動でheadersにUser-Agentを指定する必要がありましたが、今バージョンから以下のようにブラウザごとのUser-Agentをセットするメソッドを実装しました。

setBrowser()
var client = require('cheerio-httpcli');

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

ブラウザの細かいバージョンまでは指定できないので、そういった場合は今までどおり手動でheaderに指定する必要があります。

その他のバージョンアップ内容

上記に挙げた大規模な目玉機能の他にも

  • クッキーの内容を簡単に確認できるようになった
  • cheerioオブジェクトにパースする前の生HTMLも取得できるようになった
  • リクエスト時にRefererを自動設定するオプション追加

など、色々といい感じに取り揃えていますので、Node.jsでスクレイピングという機会があった場合にはこの記事のことも思い出してもらえればうれしいのです。

それでは素敵なスクレイピングライフを:thumbsup:

ktty1220
WEBサービス運営で食べていくことを夢見て修行中。
https://ktty1220.me/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした