【連載】CasperJS でスクレイピング (3) - 実践テクニック

今回は CasperJS を使い実際のサイトをスクレイピングしてみる。

北陸鉄道の時刻表が2014年4月の改正で路線時刻表の permalink がなくなって不便なため、今回はこれを取得してミラーサイトを作成することを考える。

Referer 制限や POST メソッドを通過する

まず路線別時刻表検索 (busline.php) から路線の一覧を取得する。単にこの URL へアクセスするだけでは次のエラーが出てしまう。

原因はサーバ側のプログラムが POST メソッドしか受け付けないため。このような場合は何も考えず、人が操作したのと同じことをスクリプトで指示してやればよい。リファラ制限がかけられている場合も同じ方法でよい。よほど大規模なサイトで、厳しいチェックがかけられている場合を除き、この方法で通過できるはずだ。

人が操作する場合は時刻表メニュー (menu.php) において「路線別時刻検索 (busline.php)」をクリックするので、 click() メソッドを使って次のように書ける。

casper.start("http://arj.hokutetsu.co.jp/timetable/menu.php");
casper.then(function() {
	this.click("a[href$='busline.php']");
});

引数として CSS セレクタを使用する。今回の場合は a 要素の、 href 属性が “busline.php” で終わるものを選びたいのでこのような書き方になる。

非同期読み込みを待つ

時刻表検索を使っていて気付くのが、ページを読み込んだあとにくるくる回る「ローダ」が表示されること。これが消えてからでないと操作ができないようだ(確認はしていない)。

この場合は waitFor メソッドを使い、ローダが消えたかどうかを判断してから処理を実行するようにする。ブラウザの開発者ツールで確認したところ、読み込み完了時に div#loader にスタイル display: none が設定されていたのでここで判断した。

casper.waitFor(isLoaderHidden, function() {
	// 処理...
});

function isLoaderHidden() {
	return this.evaluate(function() {
		return document.querySelector("#loader").style.display == "none";
	});
}

ここで登場する evaluate メソッドは、Web ページ内の変数やメソッドにアクセスし、値を取得するためのものである。引数として渡した関数内では Web ページで使う JavaScript と同じスコープを持っており、値の書き換えも行える。

Web ページから値を得る

ここまで来ればあとは値を抽出してくるだけ。 evaluate() を使い、あとは DOM 操作を駆使してやればよい。

var rosenList = this.evaluate(function() {
	var options = document.querySelectorAll("#ros_list > option");
	return Array.prototype.map.call(options, function(e) {
		return { name: e.innerText };
	});
});

querySelectorAll() の返す値は単なる配列ではなく NodeList という型なので Array.prototype.〜.call() という形をよく使う。 map 以外にも forEach, slice などが使える。

WebKit にはもともと Internet Explorer の独自拡張だった innerText プロパティが実装されているため、要素内のテキストの抽出が簡単にできて便利だ。textContent と違い、見えていない文字は返さないので、ほぼ見た通りのテキストが得られるのが利点だ。

(次回へ続く)