61

I'm using PhantomJS page.evaluate() to do some scraping. My problem is that the code I pass to the webkit page is sandboxed, and so has no access to the variables of my main phantom script. This makes it hard make the scraping code generic.

page.open(url, function() {
  var foo = 42;

  page.evaluate(function() {
    // this code has no access to foo
    console.log(foo);
  });
}

How could I push arguments into the page?

TheFist
  • 623
  • 1
  • 5
  • 6
  • 2
    I believe you may find this answer more sufficient: http://stackoverflow.com/questions/28524130/phantomjs-does-not-take-arguments-to-function-passed-to-page-evaluate – user2977636 Aug 19 '16 at 02:54

8 Answers8

82

I've had that exact problem. It can be done with a little trickery, because page.evaluate also can accept a string.

There are several ways to do it, but I use a wrapper called evaluate, which accepts additional parameters to pass to the function that must be evaluated on the webkit side. You would use it like this:

page.open(url, function() {
  var foo = 42;

  evaluate(page, function(foo) {
    // this code has now has access to foo
    console.log(foo);
  }, foo);
});

And here is the evaluate() function:

/*
 * 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);
}
cthom06
  • 9,389
  • 4
  • 36
  • 28
Weston
  • 1,845
  • 13
  • 12
  • Brilliant! This source code clearly explained how it works. – Light Dec 23 '15 at 03:58
  • 1
    For an in-depth explanation as to why this happens, check out this question: http://stackoverflow.com/questions/12222856/passing-arguments-to-anonymous-function-inside-page-includejs-and-page-evaluat – Julian K Feb 29 '16 at 15:30
  • Your solution seems to be good, but what if I want to pass callback, is it possible? Two days went to garbage. Is there in JS callback global object like arguments? – Velaro Jul 13 '17 at 14:10
75

The change has been pushed and now you can use it as

page.open(url, function() {
  var foo = 42;

  page.evaluate( function(foo) {
  // this code has now has access to foo
  console.log(foo);
  }, foo);
}

The push details are here: https://github.com/ariya/phantomjs/commit/81794f9096

Bala Anirudh
  • 751
  • 5
  • 4
15

You can pass in the arguments to the function as arguments to page.evaluate.

Example:

page.evaluate(function(arg1, arg2){
    console.log(arg1); //Will print "hi"
    console.log(arg2); //Will print "there"
}, "hi", "there");
YTL
  • 201
  • 2
  • 5
3

There is the solution that works with PhantomJS 0.9.2 and 0.2.0:

page.evaluate(
    function (aa, bb) { document.title = aa + "/" + bb;}, //the function
    function (result) {}, // a callback when it's done
    "aaa", //attr 1
    "bbb"); //attr 2
Tomas Randus
  • 2,127
  • 4
  • 29
  • 38
  • 1
    This is probably not directly for PhantomJS, but a bridge between node.js and PhantomJS. Such bridges use slightly different syntax. – Artjom B. Jul 16 '15 at 11:48
  • 1
    This answer is the ticket if you are trying to pass argument into the sandboxed evaluate method in the [phantomjs-nodejs bridge](https://github.com/sgentle/phantomjs-node). The documentation is still fresh. I almost gave up until I found this answer. Thanks! – dmmfll Jan 19 '16 at 10:33
2

Another possibility: pass the variables in with the url. For example, to pass object x

// turn your object "x" into a JSON string
var x_json = JSON.stringify(x);

// add to existing url
// you might want to check for existing "?" and add with "&"
url += '?' + encodeURIComponent(x_json);

page.open(url, function(status){
    page.evaluate(function(){
        // retrieve your var from document URL - if added with "&" this needs to change
        var x_json = decodeURIComponent(window.location.search.substring(1));
        // evil or not - eval is handy here
        var x = eval('(' + x_json + ')');       
    )}
});
Rocco
  • 91
  • 7
A F
  • 7,424
  • 8
  • 40
  • 52
1

This works for me:

page.evaluate("function() {document.body.innerHTML = '" + size + uid + "'}");

Means to put everything as a string. Anyway later it become a string. Check the library source.

Tomas Randus
  • 2,127
  • 4
  • 29
  • 38
1

While you can pass arguments into evaluate(function, arg1, arg2, ...), this is often a little cumbersome. Especially in cases when passing in several variables, or worse, functions.

To get around this obstacle, one can use injectJs(filename) instead.

page.open(url, function() {
    if ( webpage.injectJs('my_extra_functionality.js') ) {
        page.evaluate( function() {
            // this code has access to foo and also myFunction();
            console.log(foo);
            console.log(myFunction());
        });
    }
    else {
        console.log("Failed to inject JS");
    }
}

Where my_extra_functionality.js is a local file in the same directory:

var foo = 42;
var myFunction = function(){
    return "Hello world!";
}
ChickenFeet
  • 2,653
  • 22
  • 26
  • in case someone is looking for a puppeteer solution, you can use `await page.addScriptTag({path: "/path/to/file/with/extra/functionality"});` – dcts Oct 24 '21 at 12:04
0

Can't you just bind the args to the function??

page.evaluate.bind(args)(callbackFn)
user2977636
  • 2,086
  • 2
  • 17
  • 21