理系学生日記

おまえはいつまで学生気分なのか

PhantomJS ではてなにログインしたけど挙動が不明な件について

PhantomJS は、公式サイトで以下のように説明されています。

PhantomJS is a headless WebKit with JavaScript API. It has fast and native support for various web standards: DOM handling, CSS selector, JSON, Canvas, and SVG.

PhantomJS - Scriptable Headless Browser

PhantomJS: Headless WebKit with JavaScript API

これだと良く分からないんですけど、一言で言えば人間に見えるインタフェースがないままブラウジングができる、みたいなソフトウェアです。
誤解を恐れずに言えば、ブラウザ(Webkit)をスクリプトから操作できると言えばいいんでしょうか。ブラウザですから、アクセスしたページの DOM 構造も当然分かりますし、読み込まれている JavaScript も当然実行されます。

でまぁ、簡単なサンプルプログラムは phantomjs/examples at master · ariya/phantomjs · GitHub に公開されていて、JSONP が余裕でできたりとか、wikipedia にアクセスして余裕で PDF 化できたりする。
まぁでも画面遷移はできないと使えないし、ログインとかもできないと使いづらいから、サンプルプログラムつくったら良くわからない動作した。

#!/usr/local/bin/phantomjs --cookies-file=./cookie2.txt

var page   = require('webpage').create(),
    cnt    = 0;

page.onConsoleMessage = function (msg, lineNum, sourceId) {
    console.log(msg);
};

page.customHeaders = {
    "User-Agent": 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.75 Safari/535.7',
};

function login(username, password) {
    console.log("login starts.");
    page.open('https://www.hatena.ne.jp/login', function (status) {
	page.render("login.png");
	page.evaluate( function (page, username, password, cnt) {
	    console.log("try login: [" + cnt + "] " + username);

	    // page.onLoadFinished = function (status) {
	    // 	page.render('test.png');
	    // 	console.log("login complete");
	    // 	phantom.exit();
	    // };

	    document.getElementById('login-name').value    = username;
	    document.querySelector('input.password').value = password;
	    document.querySelector('form').submit();

	}, page, username, password, ++cnt );
    });
}

login('hatena id', 'pass);

で、これを実行すると、

% ./hatena.js                                                                                    [~/work/phantomjs]
login starts.
try login: [1] kiririmode
try login: [2] kiririmode
TypeError: 'null' is not an object (evaluating 'document.getElementById('login-name').value    = username')

  phantomjs://webpage.evaluate():10
  phantomjs://webpage.evaluate():14
  phantomjs://webpage.evaluate():14
try login: [3] kiririmode
TypeError: 'null' is not an object (evaluating 'document.getElementById('login-name').value    = username')

  phantomjs://webpage.evaluate():10
  phantomjs://webpage.evaluate():14
  phantomjs://webpage.evaluate():14
try login: [4] kiririmode
TypeError: 'null' is not an object (evaluating 'document.getElementById('login-name').value    = username')

  phantomjs://webpage.evaluate():10
  phantomjs://webpage.evaluate():14
  phantomjs://webpage.evaluate():14

ななななんで try login 4 回も呼ばれちゃってんのーみたいな!意味わかんないみたいな!!!
ちなみに、try login 1 回目で既にログインはできていて、ログイン後のリダイレクト先に対して、page.open に与えている無名関数が再度呼ばれている動作をしている。
かんぜんに謎。よくわからん。こわい。
phantom.exit() 呼ぶタイミングの問題だと思うんだけど、いったいどのタイミングだったらええんや…。