Awesome
cheerio-httpcli - Node.js用WEBスクレイピングモジュール
Node.jsでWEBページのスクレイピングを行う際に必要となる文字コードの変換と、cheerioによってパースしたHTMLをjQueryのように操作できるHTTPクライアントモジュールです。
特徴
- 取得先WEBページの文字コードを自動で判定してHTMLをUTF-8に変換
- UTF-8に変換したHTMLをjQueryのように操作可能
- フォームの送信やリンクのクリックをエミュレート
- フォーム送信時のファイルアップロード対応
- Node.jsお馴染みのコールバック形式と最近の流行であるプロミス形式どちらにも対応
- 同期リクエスト対応
$('a')
要素のリンク先をファイルとしてダウンロード可能$('img')
要素画像をファイルとしてダウンロード可能(LazyLoad対応)$('a,img,script,link')
要素のURLを絶対パスで取得可能- ブラウザ指定による簡単User-Agent切り替え機能
- マルチインスタンス(ログインが必要な同一サイトに複数のアカウントで同時ログイン)対応
- Puppeteerと互換性のあるクッキーJSONファイルの保存・読み込み対応
静的なHTMLをベースに処理するモジュールなのでSPAなどクライアントサイドのJavaScriptによってコンテンツを取得/変更するタイプのWEBページには対応していません。
サンプル
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検索結果の一覧を取得するサンプルです。参考にしてください。
インストール
npm install cheerio-httpcli
API目次
- メソッド
- プロパティ
- cheerioオブジェクトの独自拡張
- $.documentInfo()
- $(link-element or submit-element).click()
- $(link-element or submit-element).clickSync()
- $(form-element).submit()
- $(form-element).submitSync()
- $(form-element).field()
- $(checkbox-element or radio-element).tick()
- $(checkbox-element or radio-element).untick()
- $(link-element or image-element).url()
- $(link-element or image-element).download()
- $(element).entityHtml()
メソッド
fetch(url[, get-param, encode, callback])
url
で指定したWEBページをGETメソッドで取得し、文字コードの変換とHTMLパースを行いcallback
関数に返します。
callback
関数には以下の4つの引数が渡されます。
- Errorオブジェクト
cheerio.load()
でHTMLコンテンツをパースしたオブジェクト(独自拡張版)- requestモジュールの
response
オブジェクト(独自拡張版) - UTF-8に変換したHTMLコンテンツ
各種引数の指定
-
GET時にパラメータ(
?foo=bar&hoge=fuga
)を付加する場合は第2引数のget-param
に連想配列で指定します。 -
あらかじめ取得対象のWEBページのエンコーディングが分かっている場合は
encode
にsjis
やeuc-jp
などの文字列をセットすることで自動判定による誤判定(滅多に発生しませんが)を防止することができます。 -
get-param
、encode
、場合によってはcallback
も省略可能です。// get-paramとencodeを省略 => GETパラメータ指定なし & エンコーディング自動判定 client.fetch('http://hogehoge.com/fuga.html', function (err, $, res, body) { ... }); // get-paramを省略 => GETパラメータ指定なし & エンコーディング指定(sjis) client.fetch('http://hogehoge.com/fuga.html', 'sjis', function (err, $, res, body) { ... }); // encodeを省略 => GETパラメータ指定(?foo=bar) & エンコーディング自動判定 client.fetch('http://hogehoge.com/fuga.html', { foo: 'bar' }, function (err, $, res, body) { ... }); // url以外全部省略 => GETパラメータ指定なし & エンコーディング自動判定 & プロミス形式(後述) client.fetch('http://hogehoge.com/fuga.html') .then(function (result) { ... });
プロミス形式での呼び出し
fetch()
の第4引数である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()
の第4引数のcallback
関数を指定した場合はPromiseオブジェクトは返しません。したがってコールバック形式で呼び出しつつPromiseオブジェクトで何かをするということはできません。
また、async
とawait
を使用して上記処理を以下のように書くこともできます。
const client = require('cheerio-httpcli');
(async () => {
try {
const result1 = await client.fetch(<TOPページのURL>);
// 何か処理
const result2 = await client.fetch(<ページAのURL>);
// 何か処理
const result3 = await client.fetch(<ページA-1のURL>);
// 何か処理
const result4 = await client.fetch(<ページA-2のURL>);
} catch (err) {
console.log(err);
}
console.log('done');
})();
fetchSync(url[, get-param, encode])
非同期で実行されるfetch()
の同期版(リクエストが完了するまで次の行に進まない)となります。fs.readFile()
に対するfs.readFileSync()
の関係と同じような意味合いになります。
- 呼び出し時のパラメータは
fetch()
のプロミス形式と同様です。 - 戻り値はプロミス形式の
then
に渡されるオブジェクトと同様の形式です。
var client = require('cheerio-httpcli');
var result1 = client.fetchSync('http://foo.bar.baz/');
console.log(result1);
// => {
// error: ...,
// $: ...,
// response: ...,
// body: ...
// }
console.log(result1.$('title')); // => http://foo.bar.baz/のタイトルが表示される
var result2 = client.fetchSync('http://hoge.fuga.piyo/');
console.log(result2.$('title')); // => http://hoge.fuga.piyo/のタイトルが表示される
- 同期リクエストは、外部スクリプトをspawnSync()で実行して処理が完了するまで待つ、という形で実装しているのでパフォーマンスは非常に悪いです(非同期リクエストの10倍程度は時間がかかります)。したがって、実装しておいてなんですが、基本は非同期リクエストで処理を行い、どうしてもここだけは同期リクエストにしたいといった場合のみ、という使い方をお勧めします。
- 同期リクエストの戻り値内のレスポンスはresponse.toJSON()されたものなので非同期版とは内容が若干異なります。statusCodeやheaders、requestなどの主要プロパティは共通して使用できるので特に大きな問題はないかと思いますが、特殊な使い方をする場合には注意が必要です。
set(name, value, nomerge)
name
で指定したプロパティに値value
を設定するメソッドです。
オブジェクトはマージされます。第3引数のnomerge
がtrue
の時はマージせず、value
のオブジェクトで上書きされます。
var client = require('cheerio-httpcli');
client.set('timeout', 10000); // タイムアウトを30秒から10秒へ変更
client.set('headers', { // リクエストヘッダのrefereのみを変更
referer: 'http://hoge.fuga/piyo.html'
});
client.set('headers', {}, true); // リクエストヘッダを空に
オブジェクトの場合、キーに対する値にnull
をセットするとその値は削除されます。
// [before]
// client.headers => {
// lang: 'ja-JP',
// referer: 'http://hoge.fuga/piyo.html',
// 'user-agent': 'my custom user-agent'
// }
client.set('headers', {
referer: null
});
// [after]
// client.headers => {
// lang: 'ja-JP',
// 'user-agent': 'my custom user-agent'
// }
存在するプロパティについては プロパティ を参照してください。
現状ではTypeScript上でのプロパティ更新用のメソッドですが、直接プロパティに値を代入する方式は将来的に廃止する予定です。
setBrowser(browser-type)
ブラウザごとの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
が返ります。
0.7.0
から値を返さないようになりました。
対応しているブラウザは以下のとおりです。
- chrome
default
- firefox
- edge
- ie
- vivaldi
- opera
- yandex
- safari
- ipad
- iphone
- ipod
- android
- ps4
- 3ds
- switch
- googlebot
なお、ブラウザの細かいバージョンの指定まではできないので、そういった指定も行いたい場合は手動で以下のようにUser-Agentを指定してください。
// IE6のUser-Agentを手動で指定
client.set('headers', {
'User-Agent': 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)'
});
setBrowser()
は将来的に削除予定です。代わりに以下のようにset()
メソッドを使用するようにしてください。
client.set('browser', 'firefox');
setIconvEngine(iconv-module-name)
cheerio-httpcliは、実行時にインストールされているiconv系のモジュールをチェックして利用するモジュールを自動的に決定しています。優先順位は以下のとおりです。
- iconv
- iconv-lite
iconv-liteはcheerio-httpcliのインストール時に依存モジュールとして一緒にインストールされますが、ネイティブモジュールであるiconvがインストールされている場合、処理速度や対応文字コードの多さというメリットがあるそちらを優先してロードするようになっています。
バージョン0.3.1までは最優先はiconv-jpでしたが、長期間メンテされていないこととNode.js 0.12系でコンパイルできなくなっている現状を考慮してデフォルトの変換エンジン候補から除外しました。
あえて
setIconvEngine()
でiconv-jpを指定することは可能ですが非推奨です。
このメソッドは自動的にロードされたiconv系モジュールを破棄して、使用するiconv系モジュールを手動で指定するためのものです。モジュールテスト時の切り替え用メソッドなので基本的には実用性はありません。
iconv-module-name
には使用するiconv系モジュール名(iconv
, iconv-lite
, iconv-jp
)のいずれかの文字列を指定します。
サンプル
var client = require('cheerio-httpcli');
// あえてiconv-liteを使用
client.setIconvEngine('iconv-lite');
client.fetch( ...
setIconvEngine()
は将来的に削除予定です。代わりに以下のようにset()
メソッドを使用するようにしてください。
client.set('iconv', 'iconv-lite');
reset()
cheerio-httpcliはそのプロセスが動作している間、各種設定やクッキーを保持し続けます。
reset()
を実行すると、設定情報やクッキーをすべて初期化してプロセス起動時と同じ状態に戻します。
fork()
cheerio-httpcliは基本的にはシングルインスタンスで動作するため、ログインが必要な同一サイトに複数アカウントで同時にログインするといったことはできませんでした(クッキー情報が後からログインしたアカウントのもので上書きされてしまう)。
0.8.0
から実装されたfork()
メソッドを使用して子インスタンスを作成し、それぞれのインスタンスで別々のアカウントで同一サイトにログインできるようになりました。
// clientはメインインスタンス
var client = require('cheerio-httpcli');
// 子インスタンスを作成
var child1 = client.fork();
// 同一のサイトにそれぞれ別々のアカウントでログイン
var url = 'https://need.login.web.service/login';
client.fetch(url, function (err, $, res, body) {
$('#login').submit({
username: 'foo',
passowrd: 'password_for_foo',
}, function (err, $, res, body) {
console.log($('.user_name').text()); // => foo
});
});
child1.fetch(url, function (err, $, res, body) {
$('#login').submit({
username: 'bar,
passowrd: 'password_for_bar,
}, function (err, $, res, body) {
console.log($('.user_name').text()); // => bar
});
});
// 子インスタンスはいくつでも作成可能
var child2 = client.fork();
var child3 = client.fork();
メインインスタンスと子インスタンスの仕様
-
fork()
を実行できるのはメインインスタンスのみです。子インスタンスはfork()
メソッドを実行できません。var child = client.fork(); var grandChild = child.fork(); // => エラー
-
fork()
された直後の子インスタンスの設定やクッキー情報はメインインスタンスのものを引き継いだ状態ですが、その後はそれぞれのインスタンスで変更可能です。client.set('browser', 'ie'); // UserAgentをIEに設定 var child = client.fork(); // この時点では子インスタンスのUserAgentもIE child.set('browser', 'firefox'); // 子インスタンスのUserAgentだけをFirefoxに変更
-
子インスタンスはダウンロードマネージャを持っていません。子インスタンスで
$(...).download()
メソッドが実行された場合はメインインスタンスのダウンロードマネージャーに登録されます。// メインインスタンスのダウンロードマネージャー client.download.on('ready', function (stream) { ... }); var child = client.fork(); child.fetch('http://...', function (err, $, res, body) { $('img').download(); // メインインスタンスのダウンロードマネージャーに送られる });
exportCookies()
現在のクッキー情報をJSON化できる形で取得します。
// ログインが必要なサイトにログインする
client.fetch('https://need.login.web.service/login', function (err, $, res, body) {
$('#login').submit({
username: 'foo',
passowrd: 'password_for_foo',
}, function (err, $, res, body) {
// ログイン後のクッキー情報をJSONファイルとして書き出す
fs.writeFileSync('cookie.json', JSON.stringify(client.exportCookies()), 'utf-8');
});
});
クッキー情報にはWEBサイトのセッションIDなど、セキュリティ上非常に重要な情報が書き込まれている可能性があるので、取扱いには十分注意してください(セッションIDが漏洩するとアカウントの乗っ取りなどされる恐れがあります)。
importCookies()
exportCookies()
で取得したクッキー情報を読み込みます。
// ログイン後に出力したクッキー情報を読み込む
client.importCookies(JSON.parse(fs.readFileSync('cookie.json', 'utf-8')));
client.fetch('https://need.login.web.service/mypage', function (err, $, res, body) {
// いきなりログイン後のページにアクセスできる
console.log($('.user_name').text()); // => foo
});
あくまでクッキー情報を復元しているだけなので、上記のようにいきなりログイン後のページにアクセスできるかどうかは各WEBサイトの仕様によります。
プロパティ
各プロパティは以下のように取得、更新します。
// バージョン情報を表示
console.log(client.version);
// タイムアウト時間を変更
client.set('timeout', 5000);
// [非推奨] 直接更新することも可能ですが将来的に不可となります
client.timeout = 3000;
version readonly
cheerio-httpcliのバージョン情報です。
browser
set()
やsetBrowser()
でセットしたブラウザ名です(ie
、chrome
など)。
未設定の場合はnull
、手動でUser-Agentを設定した場合はcustom
と入っています。
※未設定時には初回fetch()
時にchrome
がセットされます。
iconv
使用中のiconv系モジュール名です(iconv
、iconv-lite
、iconv-jp
のいずれか)。
headers
requestモジュールで使用するリクエストヘッダ情報の連想配列です。デフォルトでは何も指定されていませんが、fetch()
実行時にUser-Agentが空の場合は自動的にUser-AgentにGoogleChromeの情報が入ります。
timeout
requestモジュールで指定するタイムアウト情報をミリ秒で指定します。デフォルトは30000
(30秒)となっています。
gzip
サーバーとの通信にgzip転送を使用するかどうかを真偽値で指定します。デフォルトはtrue
(gzip転送する)です。
referer
リファラを自動でセットするかどうかの指定です。true
にすると1つ前にfetch()
したページのURLが自動でリクエストヘッダのRefererにセットされます。デフォルトはtrue
です。
followMetaRefresh
<meta http-equiv="refresh" content="0;URL=...">
といったMETAタグをHTML内に発見した場合に自動でそのURLにリダイレクトします。ただし、<!--[if IE]>~<![endif]-->
のようなIE条件付きコメント内にある場合はリダイレクトしません。デフォルトはfalse
です。
Google検索をする場合は
followMetaRefresh
はfalse
にしてください。Googleの検索結果HTMLには常にMETAタグのRefresh指定が入っているので(しかも毎回微妙に異なるURL)、リダイレクトがループして最終的にエラーになります。
maxDataSize
fetch()
などで受信するデータの限界量を数値(バイト数)で指定します。この値を超えるサイズを受信した段階でエラーが発生します。ユーザーから入力されたURLを解析する用途などにおいて、不用意に大きいデータを読み込んでしまい回線を占有する可能性がある場合に指定しておいた方が良いでしょう。
画像のダウンロード時には適用されません。
デフォルトはnull
(制限なし)です。
var client = require('cheerio-httpcli');
// 受信料制限を1MBに指定
client.set('maxDataSize', 1024 * 1024);
// 1MB以上ののHTMLを指定
client.fetch('http://big.large.huge/data.html', function (err, $, res, body) {
console.log(err.message); // => 'data size limit over'
});
なお、maxDataSize
を超えた場合は途中まで受信したデータは破棄されます。
forceHtml
cheerio-httpcliは取得したページがXMLであると判別した場合、自動的にcheerioのXMLモードを有効にしてコンテンツをパースします(Content-Type
とURLの拡張子を見て判別しています)。
true
にするとこの自動判別を無効にして常にHTMLモードでコンテンツをパースするようになります。デフォルトはfalse
(自動判別する)です。
agentOptions
主にSSL接続などのセキュリティの設定を行うオプションです。cheerio-httpcli内部で使用しているrequestモジュールにそのまま渡されます。デフォルトは空連想配列です。
基本的には何も設定する必要はありませんが、httpsページへのアクセスができない場合にこのオプションを設定することにより解決する可能性があります。設定方法などの詳細はrequestモジュールのドキュメントを参照してください。
// TLS1.2での接続を強制する
client.set('agentOptions', {
secureProtocol: 'TLSv1_2_method'
});
debug
true
にするとリクエストの度にデバッグ情報を出力します(stderr
)。デフォルトはfalse
です。
var client = require('cheerio-httpcli');
// デバッグ表示ON
client.set('debug', true);
client.fetch( ...
download readonly
ファイルダウンロードマネージャーオブジェクトです。このオブジェクトを通してファイルダウンロードに関する設定を行います(詳細は$(image-element).download()を参照)。
cheerioオブジェクトの独自拡張
cheerio-httpcliではcheerioオブジェクトのprototypeを拡張していくつかの便利メソッドを実装しています。
$.documentInfo()
取得したWEBページに関する情報(url
、encoding
、isXml
)を取得できます。
client.fetch('http://hogehoge/', function (err, $, res, body) {
var docInfo = $.documentInfo();
console.log(docInfo.url); // http://hogehoge/
console.log(docInfo.encoding); // 'utf-8'
console.log(docInfo.isXml); // XMLモードでパースされた場合はtrue
});
fetch()
で指定したURLがリダイレクトされた場合はリダイレクト先のURLがurl
に入ります。encoding
に関しても同様で、最終的に到達したページのエンコーディングが入ります。
$(link-element).click([ callback ])
a
要素もしくは送信ボタン系要素で使用可能ですが、それぞれ挙動が異なります。
a
要素
href
属性に指定されているURLと取得したページのURLを組み合わせて移動先のURLを作成し、fetch()
を実行します。
client.fetch('http://hogehoge/')
.then(function (result) {
// id="login"の子のリンクをクリック(プロミス形式)
return result.$('#login a').click();
})
.then(function (result) {
// クリックした先のURL取得後の処理
});
注意点として、このclick()
メソッドはjavascriptリンクやonclick="..."
などの動的処理には対応していません。あくまでもhref
のURLに簡単にアクセスできるための機能です。
送信ボタン系要素
input[type=submit]
、button[type=submit]
、input[type=image]
要素が対象となります。
押された送信ボタンが所属するフォーム内に配置されているinput
やcheckbox
などのフォーム部品から送信パラメータを自動作成し、action
属性のURLにmethod
属性でフォーム送信を実行します。
client.fetch('http://hogehoge/')
.then(function (result) {
var form = $('form[name=login]');
// ユーザー名とパスワードをセット(field()については後述)
form.field({
user: 'guest',
pass: '12345678'
});
// 送信ボタンを押してフォームを送信(コールバック形式)
// ※上で指定したuserとpass以外はデフォルトのパラメータとなる
form.find('input[type=submit]').click(function (err, $, res, body) {
// フォーム送信後に移動したページ取得後の処理
});
})
cheerio-httpcliは内部でクッキーも保持するので、ログインが必要なページの取得などもこのフォーム送信でログインした後に巡回できるようになります。
なお、こちらも動的処理であるonsubmit="xxx"
や送信ボタンのonclick="..."
には対応していません。
共通の仕様
$(...).click()
時の対象要素が複数ある場合は先頭の要素に対してのみ処理が行われます。fetch()
と同様に引数のcallback
関数の有無でコールバック形式とプロミス形式の指定を切り替えられます。
$(link-element).clickSync()
非同期で実行されるclick()
の同期版となります。
- 戻り値はプロミス形式の
then
に渡されるオブジェクトと同様の形式です。
a
要素
var client = require('cheerio-httpcli');
// fetch()は非同期で行ってその中で同期リクエストする場合
client.fetch('http://foo.bar.baz/', function (err, $, res, body) {
var result = $('a#login').clickSync();
console.log(result);
// => {
// error: ...,
// $: ...,
// response: ...,
// body: ...
// }
});
送信ボタン系要素
var client = require('cheerio-httpcli');
// フォームのあるページに同期リクエスト
var result1 = client.fetchSync('http://foo.bar.baz/');
var form = result1.$('form[name=login]');
form.field({
user: 'guest',
pass: '12345678'
});
// フォーム送信も同期リクエスト
var result2 = form.find('input[type=submit]').clickSync();
// フォーム送信後に移動したページ取得後の処理
.
.
.
$(form-element).submit([ param, callback ])
form
要素でのみ使用できます。
指定したフォーム内に配置されているinput
やcheckbox
などのフォーム部品から送信パラメータを自動作成し、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) {
// フォーム送信後に移動したページ取得後の処理
});
})
その他の仕様は$(submit-element).click()
と同様です。
onsubmit="xxx"
には対応していません。$(...)
で取得したform
要素が複数ある場合は先頭の要素に対してのみ実行されます。
$(submit-element).click()との違い
$(submit-element).click()
は押したボタンのパラメータがサーバーに送信されますが、$(form-element).submit()
は送信系ボタンのパラメータをすべて除外した上でサーバーに送信します。
例
<form>
<input type="text" name="user" value="guest">
<input type="submit" name="edit" value="edit">
<input type="submit" name="delete" value="delete">
</form>
上記フォームは1フォーム内に複数のsubmit
ボタンがあります。それぞれのメソッドによるこのフォームの送信時のパラメータは以下のようになります。
// $(submit-element).click()の場合
$('[name=edit]').click(); // => '?user=guest&edit=edit'
// $(form-element).submit()の場合
$('form').submit(); // => '?user=guest'
このように1フォーム内に複数のsubmit
ボタンがある場合、サーバー側では押されたボタンのパラメータで処理を分岐させている可能性があるので、$('form').submit()
だと正常な結果が得られないかもしれません。
実際にブラウザから手動でフォームを送信した挙動に近いのは$(submit-element).click()
になります。
ファイルのアップロード
0.8.0
からinput[type=file]
要素にローカルファイルパスをセットすることにより、ファイルのアップロードができるようになりました。
<form action="/upload.php" enctype="multipart/form-data" method="post">
<input type="text" name="title">
<input type="file" name="upload_file">
<input type="submit">
</form>
上記のようなフォームでファイルをアップロードするには以下のように指定します。
$('form').submit({
title: 'お宝画像',
upload_file: '/path/to/secret/yabai.jpg'
});
// もしくは
$('form').find('input[name=title]').val('お宝画像');
$('form').find('input[name=upload_file]').val('/path/to/secret/yabai.jpg');
$('form').submit();
対象のinput[type=file]
要素が複数ファイル(multiple
)に対応している場合は配列で指定可能です。
$('form').submit({
title: 'お宝画像',
upload_file: [
'/path/to/secret/yabai.jpg',
'/path/to/secret/sugee.jpg'
]
});
// もしくは
$('form').find('input[name=title]').val('お宝画像');
$('form').find('input[name=upload_file]').val([
'/path/to/secret/yabai.jpg',
'/path/to/secret/sugee.jpg'
]);
$('form').submit();
指定したファイルパスが存在しなかったり、複数ファイルに対応していないinput[type=file]
要素で複数ファイルを指定した場合はエラーになります。
同梱のexample/upload.jsはファイルアップロードのサンプルです。参考にしてください。
$(form-element).submitSync([ param ])
非同期で実行されるsubmit()
の同期版となります。戻り値はプロミス形式のthen
に渡されるオブジェクトと同様の形式です。
- 呼び出し時のパラメータは
submit()
のプロミス形式と同様です。 - 戻り値はプロミス形式の
then
に渡されるオブジェクトと同様の形式です。
var client = require('cheerio-httpcli');
// トップページにアクセス(ここも同期リクエストにすることも可能)
client.fetch('http://foo.bar.baz/', function (err, $, res, body) {
// 同期リクエストでログインページに移動
var result1 = $('a#login').clickSync();
// 同期リクエストでログインフォーム送信
var result2 = result1.$('form[name=login]').submitSync({
account: 'guest',
password: 'guest'
});
// ログイン結果確認
console.log(result2.response.statusCode);
});
$(form-element).field([ name, value, onNotFound ])
$(...).css()
や$(...).attr()
と同じ感覚でフォーム部品の値を取得/指定できるメソッドです。呼び出し時の引数によって動作が変わります。form
要素で使用可能です。
name
:文字列、value
:なし
form-element
内の部品name
の現在の値を取得します。
// userのvalueを取得
$('form[name=login]').field('user'); // => 'guest'
name
:文字列、value
:文字列 or 配列
form-element
内の部品name
の値をvalue
に変更します。同一name
の複数チェックボックスや複数選択select
の場合は配列でまとめて選択値を指定できます。
// passのvalueを設定
$('form[name=login]').field('pass', 'admin');
// 複数選択可能部品の場合
$('form[name=login]').field('multi-select', [ 'hoge', 'fuga', 'piyo' ]);
name
:連想配列、value
:なし
指定された連想配列内のname
:value
を一括でform-element
内の部品に反映します。
// 一括で設定
$('form[name=login]').field({
user: 'foo',
pass: 'bar'
});
引数なし
form-element
内の全部品のname
とvalue
を連想配列で取得します。
// 一括で取得
$('form[name=login]').field();
// => {
// user: 'foo',
// pass: 'bar',
// remember: 1
// }
onNotFound
第3引数のonNotFound
は、部品に値を設定する際に参照されるオプションです。指定したname
の部品がフォーム内に存在しなかった時の動作を以下のいずれかの文字列で指定します。
throw
... 例外が発生します。append
... 新規にそのname
部品を作成してフォームに追加します(文字列の場合はhidden
、配列の場合はcheckbox
)。
onNotFound
を指定しなかった場合は例外は発生せず、新規にname
部品の追加もしません(何もしない)。
// loginフォーム内にabcというnameの部品がない時の動作
$('form[name=login]').field('abc', 'hello', 'throw');
// => 例外: Element named 'abc' could not be found in this form
$('form[name=login]').field('abc', 'hello', 'append');
// => <input type="hidden" name="abc" value="hello"> を追加
$('form[name=login]').field('abc', [ 'hello', 'world' ], 'append');
// => <input type="checkbox" name="abc" value="hello" checked>
// <input type="checkbox" name="abc" value="world" checked> を追加
$('form[name=login]').field('abc', 'hello');
// => 何もしない
$(checkbox-element or radio-element).tick()
指定したチェックボックス、ラジオボタンの要素を選択状態にします。対象の要素が元から選択状態の場合は何も変化しません。
対象要素が複数ある場合は対象すべてを選択状態にしますが、ラジオボタンに関しては同グループ内で複数を選択状態にすることはできないので、最初に該当した要素を選択状態にします。
$('input[name=check_foo]').tick(); // => check_fooを選択状態に
$('input[type=checkbox]').tick(); // => 全チェックボックスを選択状態に
$('input[name=radio_bar][value=2]').tick(); // => radio_barのvalueが2のラジオボタンを選択状態に
$('input[type=radio]').tick(); // => 各ラジオボタングループの先頭を選択状態に
$(checkbox-element or radio-element).untick()
指定したチェックボックス、ラジオボタンの要素を非選択状態にします。対象の要素が元から非選択状態の場合は何も変化しません。
対象要素が複数ある場合は対象すべてを非選択状態にします。
$('input[name=check_foo]').untick(); // => check_fooを非選択状態に
$('input[type=checkbox]').untick(); // => 全チェックボックスを非選択状態に
$('input[name=radio_bar][value=2]').untick(); // => radio_barのvalueが2のラジオボタンを非選択状態に
$('input[type=radio]').untick(); // => 全ラジオボタンを非選択状態に
$(link-element or image-element).url([ filter, src-attr ])
a
要素のhref
、img
要素のsrc
、script
要素のsrc
、もしくはlink
要素のhref
のURLを完全な形(絶対パス)にしたものを取得します。元から完全なURLになっている場合(外部リンクなど)やjavascript:void(0)
といったURLでないリンクはその内容をそのまま返します。
<a id="top" href="../index.html">トップページ</a>
http://foo.bar.baz/hoge/
というページ内に上記のようなリンクがある場合、$(...).attr('href')
と$(...).url()
の戻り値はそれぞれ以下のようになります。
console.log($('a#top').attr('href')); // => '../index.html'
console.log($('a#top').url()); // => 'http://foo.bar.baz/index.html'
また、対象の要素が複数ある場合は各要素の絶対URLを配列に格納して返します。
console.log($('a').url());
// => [
// 'http://foo.bar.baz/index.html',
// 'http://foo.bar.baz/xxx.html',
// 'https://www.google.com/'
// ]
filter
第1引数のfilter
は、対象要素のhref
やsrc
のURLを3種類に分類して、取得対象から除外するかどうかフィルタリングするオプションです。
relative
... 相対URL(サイト内リンク)absolute
... 絶対URL(http(s)から始まるリンク(主にサイト外リンク))invalid
... URL以外(JavaScriptなど)
サイト内リンクを絶対URLで指定しているページもあるので、絶対URL = サイト外リンクとは限りません。
各フィルタをtrue
にすると取得、false
にすると除外という意味になります。デフォルトはすべてtrue
になっています。
例
<a href="./page2.html">
<a href="./#foo">
<a href="javascript:hogehoge();">
<a href="http://www.yahoo.com/">
このようなHTMLに対して$('a').url()
を各種filter
オプション指定で実行した時の戻り値は以下のようになります。
// 指定無し
console.log($('a').url();
// => [
// 'http://foo.bar.baz/page2.html',
// 'http://foo.bar.baz/#foo',
// 'javascript:hogehoge();',
// 'https://www.yahoo.com/'
// ]
// 相対リンクのみ取得
console.log($('a').url({
relative: true,
absolute: false,
invalid: false
}));
// => [
// 'http://foo.bar.baz/page2.html',
// 'http://foo.bar.baz/#foo'
// ]
// URLとして有効なもののみ取得(除外するものだけfalseの指定でもOK)
console.log($('a').url({ invalid: false }));
// => [
// 'http://foo.bar.baz/page2.html',
// 'http://foo.bar.baz/#foo',
// 'https://www.yahoo.com/'
// ]
なお、対象となる要素が1つのみの時の戻り値は配列ではなく絶対URLの文字列になりますが、その際のfilter
オプションの指定とその結果は以下のようになります。
<a id="top" href="index.html">Ajax</a>
上記リンクは相対リンクなので分類としてはrelative
に入ります。この時relative
を除外するオプションでurl()
を呼び出すと戻り値はundefined
となります。
console.log($('#top').url({ relative: false })); // => undefined
src-attr
第2引数のsrc-attr
は、img
要素から画像URLとして取得する属性名を指定するオプションです(文字列 or 配列)。
取得対象のWEBページでLazyLoad系のjQueryプラグインなどを使っている場合はsrc
属性にダミーの画像URLが入っていたりしますが、そのようなimg
要素でsrc
属性以外からURLを取得する際に指定します。
<img src="blank.gif" data-original-src="http://this.is/real-image.png">
このようなHTMLで、src
のblank.gif
ではなくdata-original-src
のhttp://this.is/real-image.png
を取得したい場合は以下のように指定します。
// filterオプションは省略可能
$('img').url('data-original-src');
data-original-src
がその要素に存在しない場合はsrc
属性のURLを取得します。
なお、デフォルトではdata-original
>data-lazy-src
>data-src
>src
の優先順になっています。デフォルトの優先順位を破棄してsrc
属性の画像を最優先で取得したい場合は、
$('img').url({ invalid: false }, []);
のように空配列を指定します。
$(link-element or image-element).download([ src-attr ])
拡張cheerioオブジェクトからダウンロードマネージャーへの登録を行います。<img src="data:image/png;base64,/9j/4AAQSkZJRgABA ...">
といった埋め込み画像もバイナリ化してダウンロードできます。
a
要素、img
要素以外でdownload()
を実行すると例外が発生します。
a
要素の場合はリンク先のURLをダウンロードし、img
要素の場合は画像自体をダウンロードします。
なお、download()
を実行する際には以下の例のようなダウンロードマネージャーの設定が必要になります。
例
var fs = require('fs');
var client = require('cheerio-httpcli');
// ①ダウンロードマネージャーの設定(全ダウンロードイベントがここで処理される)
client.download
.on('ready', function (stream) {
// 保存先ファイルのストリーム作成
var write = fs.createWriteStream('/path/to/image.png');
write
.on('finish', function () {
console.log(stream.url.href + 'をダウンロードしました');
})
.on('error', console.error);
// ダウンロードストリームからデータを読み込んでファイルストリームに書き込む
stream
.on('data', function (chunk) {
write.write(chunk);
})
.on('end', function () {
write.end();
});
})
.on('error', function (err) {
console.error(err.url + 'をダウンロードできませんでした: ' + err.message);
})
.on('end', function () {
console.log('ダウンロードが完了しました');
});
// ④並列ダウンロード制限の設定
client.download.parallel = 4;
// ②スクレイピング開始
client.fetch('http://foo.bar.baz/', function (err, $, res, body) {
// ③class="thumbnail"の画像を全部ダウンロード
$('img.thumbnail').download();
console.log('OK!');
});
①のclient.download
というのがcheerio-httpcliに内蔵されているダウンロードマネージャーになります。
スクレイピング中に$(...).download()
メソッドで実行されたファイルのダウンロードが始まるとclient.download
のready
イベントが発生します(エラーが発生した場合はerror
イベント)。
end
イベントはダウンロード待ちのURLがなくなった時に発生します。
①では色々な場所から実行される$(...).download()
時の共通処理を設定しています。この例では引数に渡されたダウンロード元ファイルのストリームを/path/to/image.png
に保存しています。
client.download
のイベント処理設定が完了したら②スクレイピングに入ります。
②でWEBページを取得し、その中の③で$(...).download()
メソッドを実行しています。
この時、$('img.thumbnail')
に該当する画像要素が10個あったとすると、その10個の画像要素がまとめてダウンロードマネージャーに登録されます(すでに登録済みのURLは除外されます)。
少し戻って④を見ると並列ダウンロード数制限が設定されています。今回の例では4
なので、登録された10個の画像要素の内、即座に4つがダウンロード処理に入ります。
残りの6要素はダウンロード待ちキューに入り、最初の4つの内のどこかのダウンロードが完了して空きができると、次の画像URLがその空き部分にに登録されてダウンロードが実行される ... という流れです。
画像ダウンロードは本線である②③のスクレイピングとは非同期で行われます。
上記の例では③を実行して「OK!」が表示された段階で本線のスクレイピングは終わりますが、画像のダウンロードはまだ途中であり、また、ダウンロードマネージャーに登録した全画像のダウンロードが完了するまではこのスクリプト自体は終了しません。
「OK!」が表示されてもなかなかコンソールに制御が戻ってこないからといって
Ctrl+C
とかはせずに、ダウンロード完了までお待ちください。
なお、ダウンロードファイルのストリームをファイルに保存する際にはstream.pipe(fs.createWriteStream(<file>)
といった指定方法もありますが、この方法だとhttps://qiita.com/himox_x/items/a06d3fb111d67c0c8e8dのような問題が発生するケースがあるので、on('data')
とon('end')
で制御した方が安全です(原因については現時点で不明です)。
また、cheerio-httpcli自体にダウンロードファイルのストリームをファイルに保存する機能があるので、そちらを利用することをお勧めします(詳細はstream
に実装されているプロパティ/メソッドのstream.saveAs()
を参照)。
src-attr
第1引数のsrc-attr
オプションは、url()
と同様にimg
要素から画像URLとして取得する属性名を指定可能です(文字列 or 配列)。
a
要素に対してdownload()
メソッドを実行した場合にはこの指定は無視されます。
<img src="blank.gif" data-original-src="http://this.is/real-image.png">
上記のようなHTMLで、src
のblank.gif
ではなくdata-original-src
のhttp://this.is/real-image.png
をダウンロードしたい場合は以下のように指定します。
$('img').download('data-original-src');
その他仕様は
url()
のsrc-attr
項を参照
ダウンロードマネージャー
$(...).download()
で登録されたURLのダウンロード時共通設定になります。
プロパティ
parallel
ダウンロードの同時並列実行数を指定します。1
~5
の間で指定します(デフォルトは3
)。すでにダウンロードが始まっている段階で値を変更した場合は、現在実行中のダウンロードがすべて完了してから反映されます。
state
ダウンロードマネージャーの現在の処理状況を確認できます。読み取り専用なので数値を変更してもダウンロードの状況は変化しません。
state
には以下の2項目が登録されています。
queue
... ダウンロード待ち件数complete
... ダウンロード完了件数error
... エラー件数
console.log(client.download.state); // => { queue: 10, complete: 3, error: 0 }
また、ダウンロードイベント内でthis.state
でも確認できます。
client.download
.on('ready', function (stream) {
console.log(this.state); // => { queue: 2, complete: 5, error: 1 }
...
メソッド
clearCache()
ダウンロードマネージャーは重複したURLを除外するためにURLキャッシュを内部で持っています。何らかの理由でそのキャッシュをクリアする場合に使用します。
キャッシュがクリアされると、もう一度同じURLをダウンロードマネージャーに登録できます。
イベント
download.on
で設定可能なイベントは以下の通りです。
on('add', funciton (url) { ... })
$(...).download()
メソッドで要素のURLがダウンロードマネージャーに登録された時に発生するイベントです。引数には登録されたURLが入ります。
on('ready', funciton (stream) { ... })
ダウンロード開始時に発生するイベント時の処理です。URL毎に発生します。引数のstream
にはダウンロード元ファイルのストリームが入ります。stream
に実装されているプロパティ/メソッドは以下のとおりです。
url
... ダウンロードファイルのURLオブジェクトです。URLの文字列はstream.url.href
で取得できます。Base64埋め込み画像の場合はURLオブジェクトではなくbase64
という文字列が入ります。type
... Content-Typeが入ります。サーバーから返されたレスポンスヘッダにContent-Typeがない場合はundefined
になります。length
... Content-Lengthが入ります。サーバーから返されたレスポンスヘッダにContent-Lengthがない場合は-1
になります。toBuffer(callback)
... ストリームをBufferに変換してコールバック関数(err
,buffer
)に返します。ダウンロードファイルの内容をすべてメモリ上に読み込むので巨大なファイルの場合はそれだけメモリを消費します。saveAs(filepath, callback)
... ストリームをfilepath
で指定したファイルに保存します。end()
... ストリームの読み込みを終了します。ready
イベント内で ストリームを読み込まずに処理を抜ける場合などは必ず呼び出してください (そのままにしておくとキューが詰まって次のダウンロードができなくなることがあります。また、ストリームが読み込まれずに放置されたままtimeout
時間が経過するとerror
イベントが発生して強制的にエラー扱いとなります)。
toBuffer()
とsaveAs()
についての仕様
-
callback
を省略した場合、プロミス形式での実行となります。// コールバック形式 stream.toBuffer(function (err, buffer) { ... }); stream.saveAs('/path/to/save.file', function (err) { ... }); // プロミス形式 stream.toBuffer() .then(function (buffer) { ... }) .catch(function (err) { ... }) .finally(function () { ... }); stream.saveAs('/path/to/save.file') .then(function () { ... }) .catch(function (err) { ... }) .finally(function () { ... });
-
stream.on('data')
などで自力でストリームを読み出し始めた後にtoBuffer()
やsaveAs()
を実行するとエラーとなります。stream .on('data', function (chunk) { ... }) .on('end', function () { ... }); stream.saveAs('/path/to/save.file', function (err) { // エラーが発生 });
on('error', funciton (err) { ... })
ダウンロード中にエラーが発生した時に発生するイベント時の処理です。引数のerr
オブジェクトにはurl
プロパティ(ダウンロード元のURL)が入っています。
on('end', funciton () { ... })
ダウンロード待ちキューが空になった時に発生するイベント時の処理です。引数はありません。
ダウンロードマネージャー設定例
client.download
.on('add', function (url) {
console.log('added: ' + url);
})
.on('ready', function (stream) {
// gif画像以外はいらない
if (! /\.gif$/i.test(stream.url.pathname)) {
return stream.end();
}
// 各種情報表示
console.log(stream.url.href); // => 'http://hogehoge.com/foobar.png'
console.log(stream.type); // => 'image/png'
console.log(stream.length); // => 10240
// Buffer化してファイルに保存
stream.toBuffer(function (err, buffer) {
fs.writeFileSync('foobar.png', buffer, 'binary');
});
})
.on('error', function (err) {
console.error(err.url + ': ' + err.message);
})
.on('end', function (err) {
console.log('queue is empty');
});
同梱のexample/irasutoya.jsとexample/yubin.jsは画像やリンク先のファイルをダウンロードするサンプルです。参考にしてください。
$(element).entityHtml()
対象要素のHTML部分をすべてHTMLエンティティ化した文字列を返します。基本的には使い道はないと思います。
// <h1>こんにちは</h1>
console.log($('h1').html()) // => 'こんにちは'
console.log($('h1').entityHtml()); // => 'こんにちは'
Tips
responseオブジェクトの独自拡張
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
の値を変更してもリクエスト処理には反映されません。クッキー確認専用のプロパティです。
Basic認証
Basic認証が必要なページには以下の二通りの方法でアクセスできます。
リクエストヘッダに認証情報をセット
var client = require('cheerio-httpcli');
var user = 'hoge';
var password = 'foobarbaz';
client.set('headers', {
Authorization: 'Basic ' + new Buffer(user + ':' + password).toString('base64')
});
client.fetch('http://securet.example.com', function (err, $, res, body) {
.
.
.
// 不要になったら消去(これを忘れるとその後別のページにアクセスするときにも認証情報を送信してしまう)
delete(client.headers['Authorization']);
});
URLに認証情報をセット
var client = require('cheerio-httpcli');
var user = 'hoge';
var password = 'foobarbaz';
client.fetch('http://' + user + ':' + password + '@securet.example.com', function (err, $, res, body) {
詳細はこちら
プロキシサーバー経由でアクセス
環境変数HTTP_PROXY
にhttp://プロキシサーバーのアドレス:ポート/
をセットするとプロキシサーバー経由でWEBページを取得します。
process.env.HTTP_PROXY = 'http://proxy.hoge.com:18080/'; // プロキシサーバーを指定
var client = require('cheerio-httpcli');
client.fetch('http://foo.bar.baz/', ...
今まで接続できていたhttpsのページに接続できなくなった場合
0.7.2
からhttps接続方法に関する仕様に若干変更があり、その影響で今まで接続できていたページに接続できないというケースが発生するかもしれません。
0.7.1
までと同じ挙動にする場合は以下のように設定してください。
var client = require('cheerio-httpcli');
var constants = require('constants'); // <- constantsモジュールを別途インストール
client.set('agentOptions', {
secureOptions: constants.SSL_OP_NO_TLSv1_2
});
httpsページ接続時の「unable to verify the first certificate」エラー
cheerio-httpcliでアクセスしたサーバー側のSSL証明書設定に不備があると発生することがあります。
基本的にはサーバー側で対応してもらわないと接続はできないのですが、スクリプトの最初の方に以下の指定をするとSSL証明書検証でエラーが発生しても無理やり処理を続行してアクセスすることが可能です。
process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0; // SSL証明書検証エラーを無視する設定
var client = require('cheerio-httpcli');
ただし、この方法は安全でないサイトにアクセスできるという危険な指定なので、あくまでも緊急時においてのみ自己責任で設定するようにしてください。
XMLの名前空間付きタグの指定方法
<dc:title>タイトル</dc:title>
このようなXMLタグは
$('dc:title').text();
では取得できません。
$('dc\\:title').text();
といった具合にコロンを「\」でエスケープすることで取得することができます。
クッキーJSONの読み書きについて
0.8.0
で実装されたexportCookies()
とimportCookies()
ですが、これらのメソッドで読み書きされるクッキーJSONはPuppeteerで使用されているものと互換性があります。
したがって、cheerio-httpcliのexportCookies()
で書き出したクッキーJSONをPuppeteerから読み込んだり、Puppeteerで書き出したクッキーJSONをcheerio-httpcliのimportCookies()
で読み込むことができます。
例: cheerio-httpcli => Puppeteer
// cheerio-httpcliでログインが必要なサイトにログインする
client.fetch('https://need.login.web.service/login', function (err, $, res, body) {
$('#login').submit({
username: 'foo',
passowrd: 'password_for_foo',
}, function (err, $, res, body) {
// ログイン後のクッキー情報をJSONファイルとして書き出す
fs.writeFileSync('cookie.json', JSON.stringify(client.exportCookies()), 'utf-8');
});
});
// cheerio-httpcliでログイン後に出力したクッキー情報をPuppeteerから読み込む
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
const cookies = JSON.parse(fs.readFileSync('cookie.json', 'utf-8'));
for (let cookie of cookies) {
await page.setCookie(cookie);
}
// いきなりログイン後のページにアクセスできる
await page.goto('https://need.login.web.service/mypage');
例: Puppeteer => cheerio-httpcli
// Puppeteerでログインが必要なサイトにログインする
const browser = await puppeteer.launch({headless: true});
const page = await browser.newPage();
await page.goto('https://need.login.web.service/login', { waitUntil: 'domcontentloaded' });
await page.type('input[name="username"]', 'foo');
await page.type('input[name="password"]', 'password_for_foo');
page.click('input[type="submit"]');
await page.waitForNavigation({ timeout: 60000, waitUntil: 'domcontentloaded' });
const cookies = await page.cookies();
fs.writeFileSync('cookie.json', JSON.stringify(cookies), 'utf-8');
// Puppeteerでログイン後に出力したクッキー情報をcheerio-httpcliから読み込む
client.importCookies(JSON.parse(fs.readFileSync('cookie.json', 'utf-8')));
client.fetch('https://need.login.web.service/mypage', function (err, $, res, body) {
// いきなりログイン後のページにアクセスできる
});
2段階認証が設定されているWEBサイトをスクレイピングする
cheerio-httpcliでは2段階認証が設定されているサイトには基本的にログインすることはできませんが、上記のクッキー読み書き機能と拙作のブラウザ拡張機能であるクッキーJSONファイル出力 for Puppeteerを使用してログイン後のページにアクセスすることができるかもしれません。
- 上記拡張機能をインストールしたブラウザで2段階認証が必要なサイトに手動でログインする。
- ログインした状態で拡張機能のアイコンをクリックしてクッキーJSONをエクスポートする。
- エクスポートしたクッキーJSONをcheerio-httpcliから読み込んでログイン後のページにアクセスする。
importCookies()の説明でも触れましたが、クッキー情報を復元しただけでそのサイト内のページにアクセスできるかどうかはWEBサイト側の仕様によりますので、この方法で確実に2段階認証が必要なサイト内のページにアクセスできるとは限りません。
ダメ元でやってみる価値はある、といった類のものとお考えください。
Electronに組み込む
いろいろと工夫するとWebpackで固められますが、それでもSync
系メソッドは正常に動作しません(利用不可)。また、Webpackの際に大量のwarningが発生するので、その他の機能に関しても正常に動作するかは分かりません。詳細はこちらをご覧ください。
ご利用の際は自己責任でお願いします。
また、Electronという環境に起因する動作不良に関しては、ちょっとした修正で解決するものは対応しますが、現行の仕組みを大きく変える必要がある場合は対応しない事もあります。ご了承ください。
Webpack環境下での制限
Node.js用にWebpackで固めた場合、一部動作に制限が生じます。該当する処理が発生した場合はwarningメッセージが表示されます。
-
Accept-Languageリクエストヘッダが自動で付加されません。必要であれば以下のように手動で設定してください。
var client = require('cheerio-httpcli'); client.set('headers', { 'Accept-Language': 'ja,en-US' });
-
Iconvモジュールの動的変更はできません。
iconv-lite
固定となります。 -
fetchSync()
、clickSync()
などの非同期メソッドは使用できません(実行してもエラーになります)。
TypeScriptからの利用
@typesではなくcheerio-httpcli本体に定義ファイルが同梱されています。
import * as client from 'cheerio-httpcli';
client.fetch('http://foo.bar.baz/', (err, $, res, body) => {
...
といった形でTypeScriptから利用できます。
文字コード判別の仕様
文字コードの判別はjschardetで高精度で判別できた場合はその情報を使用しますが、そうでない場合は<head>
タグのcharset情報を参照します。後者での判別時においてcharsetで指定された文字コードとWEBページの実際の文字コードが異なる場合は変換エラーや文字化けが発生します。
ライセンス
MIT licenseで配布します。
© 2013-2020 ktty1220