phantomjs page.evaluate から外のスコープの変数を参照する

信頼と実績の Stack Overflow でかいけつ
javascript - Pass arguments with page.evaluate - Stack Overflow

以下経緯

var page = require('webpage').create();
page.open("http://www.yahoo.co.jp/", function (status) {
  var selector = "title";
  var value = page.evaluate(function () {
    return document.querySelector(selector).innerHTML;
  });
  console.log(value);
  phantom.exit();
});
% phantomjs yahoo.js
ReferenceError: Can't find variable: selector

  phantomjs://webpage.evaluate():2
  phantomjs://webpage.evaluate():1
  yahoo.js:4
null

selector が参照できそうでできていない。document は参照エラーになってないように、open したページ内のスコープしか参照できない。ぱっと見できそうだったから、なかなか理解できなかった。

evaluate(function)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. Any return value must be of a simple object, i.e. no function or closure.

Interface - phantomjs - API Reference - headless WebKit with JavaScript API - Google Project Hosting


この問題について issue で議論されていて、参照させる変数を渡せる実装がコミットされているけどうまく参照できない... 使い方が違うんだろうか....
http://code.google.com/p/phantomjs/issues/detail?id=132
https://github.com/ariya/phantomjs/commit/81794f9096

var page = require('webpage').create();
page.open("http://www.yahoo.co.jp/", function (status) {
  var selector = "title";
  var value = page.evaluate(function (s) {
    return document.querySelector(s).innerHTML;
  }, selector);
  console.log(value);
  phantom.exit();
});
% phantomjs yahoo.js
TypeError: 'null' is not an object

  phantomjs://webpage.evaluate():2
  phantomjs://webpage.evaluate():1
  yahoo.js:4
null

というわけで Stack Overflow でかいけつできた。
javascript - Pass arguments with page.evaluate - Stack Overflow

var page = require('webpage').create();
page.open("http://www.yahoo.co.jp/", function (status) {
  var selector = "title";
  var value = evaluate(page, function  (s) {
    return document.querySelector(s).innerHTML;
  }, selector);
  console.log(value);
  phantom.exit();
});
/*
 * This function wraps WebPage.evaluate, and offers the possibility to pass
 * parameters into the webpage function. The PhantomJS issue is here:
 * 
 *   http://code.google.com/p/phantomjs/issues/detail?id=132
 * 
 * This is from comment #43.
 */
function evaluate(page, func) {
    var args = [].slice.call(arguments, 2);
    var fn = "function() { return (" + func.toString() + ").apply(this, " + JSON.stringify(args) + ");}";
    return page.evaluate(fn);
}
% phantomjs yahoo.js
Yahoo! JAPAN

phantomjs-node

sgentle/phantomjs-node

実際は phantomjs-node でこれをやりたかったので、上の evaluate を参考にした。

var phantom = require("phantom");

phantom.create(function (ph) {
  ph.createPage(function (page) {
    page.open("http://www.yahoo.co.jp/", function (status) {
      var selector = "title";
      page.evaluate((function () {
        var func = function (s) {
          return document.querySelector(s).innerHTML;
        };
        return "function() { return (" + func.toString() + ").apply(this, " + JSON.stringify([selector]) + ");}";
      }()), function (value) {
        console.log(value);
        ph.exit();
      });
    });
  });
});
% node phantom_yahoo.js
Yahoo! JAPAN