やりたいこと
図書館のWebサイトにログインし、貸出中の図書のタイトルと返却日を取得してみます。
(最終的にはそのデータをGoogleカレンダーに反映させたりリマインドメールを送信したりやSlackに通知したりする予定ですが、ここでは割愛します)
当初PHPでPostリクエストを投げてログインしようとしたのですが、セッションの扱いや動的に発行されるURLのルールがよくわからなかったため、ブラウザ操作を再現できるCasperJSを使うことにしました。
また一連のスクレイピング処理をNode.jsモジュールとして扱うために、CasperJSをラッパー操作できるSpookyJSも利用しています。Spookyの導入方法や簡単な使い方は以前の記事にまとめています。
SpookyJSでJavaScript有効状態のページをスクレイピング
基本的な流れ
最初のページのURLを読み込んだ後は、フォームの入力やクリックやスクロールをしながら適当に待機条件をはさみつつ、ブラウザ操作を繰り返していきます。
// 初期URLをロード
spooky.start(url);
// ロード完了
spooky.then(function() {
// フォーム入力
this.fill('form', {name: 'Alice'});
// クリック
this.click('.login');
// 次のthen に移る条件 (.username という要素が現れるまで次に進まない)
this.waitForSelector('.username');
});
spooky.then(function() {
});
spooky.then(function() {
});
上記のソースを見て頂ければ一目瞭然ですが、CasperJSのAPIは非常に直感的にわかりやすいものになっています。function内の待ち条件 (ここではwaitForSelector
) がクリアされていたら次のthenに進んでいくだけです。
待ち条件はwaitForSelector
以外にも、特定の文字列が表示されるまで待つwaitForText
や、特定の要素がappendされるだけでなくvisibleになるまで待つwaitUntilVisible
など、痒いところに手が届くAPIがいくつもありますし、無名関数で条件を独自に設定することもできます。
またclick
は要素の指定だけでなく座標も指定できるので、ブラウザゲームの自動化なんかもできそうです。
詳しくはCasperJSのAPIドキュメントページをご参照ください。
http://docs.casperjs.org/en/latest/modules/
実践
今回の対象は僕が普段よく利用している港区図書館です。
まず全体のソースはこちらです。
import Spooky from 'spooky';
import cheerio from 'cheerio';
const options = {
child: {
transport: 'http'
}
};
const spooky = new Spooky(options, function(err) {
let url = 'https://www.lib.city.minato.tokyo.jp/licsxp-opac/WOpacTifSchCmpdExecAction.do?tifschcmpd=1';
spooky.start(url);
spooky.then(function() {
this.click('#login');
this.waitForSelector('form[name="LBForm"]');
});
spooky.then(function() {
account = {
username: '**user**',
j_password: '**pass**'
};
this.fill('form[name="LBForm"]', account, false);
this.click('.exec');
this.waitForSelector('a#logout');
});
spooky.then(function() {
this.click('#stat-lent');
this.waitForSelector('table.list');
});
spooky.then(function() {
this.emit('body', this.getHTML());
});
spooky.run();
});
spooky.on('body', function(body) {
let $ = cheerio.load(body);
let books = [];
$('.list tbody tr').each(function(i, tr) {
let book = {
title: $('td', tr).eq(0).text().trim(),
returnDate: $('td', tr).eq(4).text().trim()
};
books.push(book);
});
console.log(books);
});
const spooky = new Spooky(options, function(err) {
以下を順番に解説します。
初期ページ読み込み
let url = 'https://www.lib.city.minato.tokyo.jp/licsxp-opac/WOpacTifSchCmpdExecAction.do?tifschcmpd=1';
spooky.start(url);
spooky.start
で指定したURLが読み込まれます。ロードが完了すると次のthen
に進みます。
ちなみに、読み込んでいるページはログインページでは無く検索システムのトップページです。ログインページをいきなり開くと、セッション管理の関係でリダイレクトされてしまうという仕様らしいので、このようにしています。
ログインページに移動
spooky.then(function() {
this.click('#login');
this.waitForSelector('form[name="LBForm"]');
});
初期ページの読み込みが終わると上記が実行されます。
#login
という要素をクリックするとページ遷移が発生しますが、waitForSelector
によってform[name="LBForm"]
という要素が現れるまで次のthen
に進まずに待機します。
フォーム入力
spooky.then(function() {
account = {
username: '0095510665',
j_password: '******'
};
this.fill('form[name="LBForm"]', account, false);
this.click('.exec');
this.waitForSelector('a#logout');
});
フォームが表示されているはずなので、fill
でアカウント情報を指定するフォームに入力します。更に.exec
というボタンをクリックし、a#logout
という要素が現れるまで待機しています。
貸出リストページに移動して貸出図書リストを取得
spooky.then(function() {
this.click('#stat-lent');
this.waitForSelector('table.list');
});
spooky.then(function() {
this.emit('body', this.getHTML());
});
ログイン後はマイページのトップにいるので、this.click('#stat-lent')
で貸出図書リストページに移動し、this.waitForSelector('table.list')
でリストテーブルが表示されるまで待った後、this.emit
でHTML全体を外部に投げています。
あとはイベントリスナーでHTMLを受け取ってcheerio
でパースするだけなので省略します。
実質CasperJSの紹介になってますが、SpookyJSでラップしているのでこれをexportすれば他のNodeプログラムと連携できそうです。
参考ページ
API Documentation — CasperJS 1.1.0-DEV documentation
http://docs.casperjs.org/en/latest/modules/
SpookyJS/SpookyJS: Drive CasperJS from Node.js
https://github.com/SpookyJS/SpookyJS