Murayama blog.

プログラミング教育なブログ

PhantomJS入門 ページオートメーション

PhantomJSは、Webページのロード/操作が可能です。
Webページ上での様々な操作を自動化することができます。

DOMの操作

PhantomJSではDOMスクリプティングやCSSセレクタを使うことができます。
ブラウザで実行されているかのように振る舞います。

page = require("webpage").create()
url = "http://www.httpuseragent.org"

console.log "The default user agent is #{page.settings.userAgent}"

page.settings.userAgent = "SpecialAgent"
page.open url, (status)->
	if status != "success"
		console.log "Unable to access network"
	else
		ua = page.evaluate ->
			document.getElementById('myagent').textContent
		console.log ua
	phantom.exit()

上記のサンプルは、User Agentのカスタマイズ方法を示しています。
接続先の"http://www.httpuseragent.org"では、クライアントのUser Agentを表示してくれます。
PhantomJSはそのレスポンスのHTMLを解析しています。

jQueyを使うには

page.includeJsメソッドを使います。

page = require("webpage").create()
url = "http://localhost:4567"

page.open url, (status)->
	page.includeJs "http://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js", ->
		page.evaluate ->
			$("button").click()
		phantom.exit()

簡単そう?

と、ここまで本家GitHubのサンプルを参考にしてみました。

jQuery使えるのは便利かもー、といろいろいじっていたところ、まぁ上手くいかずはまるはまる。
原因は、page.evaluateの仕組みを理解していなかったためです。

page.evaluateについて

Web Pageオブジェクトにはevaluateメソッドがあります。
このevaluateメソッドを使って、Web Pageを解析したり、ボタンをクリックしたりできます。

ただし、注意しないとはまるのが、

Evaluates the given function in the context of the web page. The execution is sandboxed, the web page has no access to the phantom object and it can't probe its own setting.

https://github.com/ariya/phantomjs/wiki/API-Reference-WebPage#wiki-webpage-evaluate

evaluateメソッドはWeb Pageのコンテキストで評価されます。
この実行はサンドボックス化されているため、PhantomJSと直接やりとりできません。

たとえば、次のようにevaluateメソッドの中で(コールバックの無名関数内で)、
console.log "evaluate: #{text}"みたいにしてしまうと、
Web Pageのコンテキストで評価されるため、ターミナルに結果は出力されません。
consoleオブジェクトが、Web Page上のconsoleオブジェクトとして解釈されてしまうためです。
#ブラウザコンソールに出力されるかんじ

page = require("webpage").create()
url = "http://phantomjs.org/"
lib = "http://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"

page.open url, (status)->
	page.includeJs lib, ->
		page.evaluate ->
			text = $("title").text()
			console.log "evaluate: #{text}"
		phantom.exit()


ですが、こうなるとデバッグが不便です。
PhantomJSでは、Web PageオブジェクトのコールバックメソッドonConsoleMessageを使うことで、この問題を解決できます。

page = require("webpage").create()
url = "http://phantomjs.org/"
lib = "http://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"

page.onConsoleMessage = (msg)->
	console.log("console> #{msg}")

page.open url, (status)->
	page.includeJs lib, ->
		page.evaluate ->
			text = $("title").text()
			console.log "evaluate: #{text}"
		phantom.exit()

実行結果

% phantomjs evaluate_sample.coffee
console> evaluate: PhantomJS: Headless WebKit with JavaScript API

evaluateメソッドの引数と戻り値を使う

evaluateメソッド呼び出しの引数や戻り値で、サンドボックス上のオブジェクトとやりとりすることもできます。ただし、受け渡し可能なデータはJSONにシリアライズ可能なオブジェクトに限定されます。クロージャや関数オブジェクトを使ってやりとりすることはできません。

page = require("webpage").create()
url = "http://phantomjs.org/"
lib = "http://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"

page.open url, (status)->
	page.includeJs lib, ->
		title = page.evaluate (selector)->
			$(selector).text()
		, "title"
		console.log "title: #{title}"
		phantom.exit()

まとめ

PhatomJSでは、jQueryなどの外部ライブラリを使ってWebページを解析したり、ボタンをクリックしたりできます。ただし、page.evaluateでのコードの解析/実行はサンドボックス化されるので注意が必要です。