24

My goal is to execute PhantomJS by using:

// adding $op and $er for debugging purposes
exec('phantomjs script.js', $op, $er);
print_r($op);
echo $er;

And then inside script.js, I plan to use multiple page.open() to capture screenshots of different pages such as:

var url = 'some dynamic url goes here';
page = require('webpage').create();
page.open(url, function (status) {
    console.log('opening page 1');  
    page.render('./slide1.png');            
});

page = require('webpage').create();
page.open(url, function (status) {
    console.log('opening page 2');  
    page.render('./slide2.png');        
});

page = require('webpage').create();
page.open(url, function (status) {
    console.log('opening page 3');  
    page.render('./slide3.png');        
    phantom.exit(); //<-- Exiting phantomJS only after opening all 3 pages
});

On running exec, I get the following output on page:

Array ( [0] => opening page 3 ) 0

As a result I only get the screenshot of the 3rd page. I'm not sure why PhantomJS is skipping the first and second blocks of code (evident from the missing console.log() messages that were supposed to be output from 1st and 2nd block) and only executing the third block of code.

Artjom B.
  • 61,146
  • 24
  • 125
  • 222
asprin
  • 9,579
  • 12
  • 66
  • 119
  • I found this https://github.com/ariya/phantomjs/blob/master/examples/render_multi_url.js But I was looking to implement it in a simple manner as the one I'm using currently. – asprin Jun 08 '13 at 07:58
  • Yes but from your question *it's not really clear* where you hit the roadblock doing that. You basically only write that "it does not work" and that you "think it should work somehow". That's pretty in-descriptive. So even someone who might know that might not be able to decipher your question in a manner knowing to have an answer for it. code-examples add good context, but you also should pin-point what exactly you expected to work with your code example. – hakre Jun 08 '13 at 08:01
  • Updated my question with some debugging information – asprin Jun 08 '13 at 08:12
  • have you double-checked the first two of the three functions are executed at all? – hakre Jun 08 '13 at 08:35
  • Doesn't `console.log` prove that the first two functions are not running?. I also tried running only the 1st code by commenting out the other two and that worked fine. – asprin Jun 08 '13 at 08:38
  • As written, if those methods are not invoked you're obviously doing it wrong. What does the documentation suggest? You can't just type in some code and expect it to work unless you can base your decisions either on documentation and/or the source-code itself. Something "just not working" is not a proper error description. – hakre Jun 08 '13 at 08:43
  • Did my answer work for you? –  Jul 24 '13 at 03:53
  • Please try out the tick provided below: [https://github.com/zubair1024/PhantomJs-Cases/blob/master/post-test.js](https://github.com/zubair1024/PhantomJs-Cases/blob/master/post-test.js) – zubair1024 Jul 03 '16 at 19:31

4 Answers4

44

The problem is that the second page.open is being invoked before the first one finishes, which can cause multiple problems. You want logic roughly like the following (assuming the filenames are given as command line arguments):

function handle_page(file){
    page.open(file,function(){
        ...
        page.evaluate(function(){
            ...do stuff...
        });
        page.render(...);
        setTimeout(next_page,100);
    });
}
function next_page(){
    var file=args.shift();
    if(!file){phantom.exit(0);}
    handle_page(file);
}
next_page();

Right, it's recursive. This ensures that the processing of the function passed to page.open finishes, with a little 100ms grace period, before you go to the next file.

By the way, you don't need to keep repeating

page = require('webpage').create();
  • That makes sense. Let me try it out and get back to you on this. – asprin Jun 11 '13 at 06:29
  • 1
    Helpful and simple! Thank you! – Justin Geeslin Jul 23 '13 at 21:43
  • You have a source for the fact that having multiple page.open being invoked at the same time causes problems for PhantomJS (or just from experience)? I'm running into a related issue and looking for a workaround. – user941238 Oct 09 '13 at 22:33
  • I came to this page because we're having a similar problem. The odd thing is that it just started happening after the software being in operation opening as many as 70 urls (all to `file://`) at the same time. So its absolutely possible and will work to open multiple urls but - as evidenced by our situation, it might have arbitrary problems. – George Mauer Oct 09 '13 at 22:47
  • I tried this with a large number of pages and got a segmentation fault on ubuntu. It seems the recursive calls is doing something nasty with the memory. Is there a more scalable solution? eg. not recursive and horizontally scalable. – MKoosej Sep 17 '15 at 04:12
8

I've tried the accepted answer suggestions, but it doesn't work (at least not for v2.1.1).

To be accurate the accepted answer worked some of the time, but I still experienced sporadic failed page.open() calls, about 90% of the time on specific data sets.

The simplest answer I found is to instantiate a new page module for each url.

// first page
var urlA = "http://first/url"
var pageA = require('webpage').create()

pageA.open(urlA, function(status){
    if (status){
        setTimeout(openPageB, 100) // open second page call
    } else{
        phantom.exit(1)
    }
})

// second page
var urlB = "http://second/url"
var pageB = require('webpage').create()

function openPageB(){
    pageB.open(urlB, function(){
        // ... 
        // ...
    })
}

The following from the page module api documentation on the close method says:

close() {void}

Close the page and releases the memory heap associated with it. Do not use the page instance after calling this.

Due to some technical limitations, the web page object might not be completely garbage collected. This is often encountered when the same object is used over and over again. Calling this function may stop the increasing heap allocation.

Basically after I tested the close() method I decided using the same web page instance for different open() calls is too unreliable and it needed to be said.

Community
  • 1
  • 1
JackyJohnson
  • 3,106
  • 3
  • 28
  • 35
  • That `setTimeout(openPageB(), ...)` line shouldn't have the parens after openPageB. As is, you're just calling openPageB right there and passing the return value into setTimeout. – drmercer Aug 20 '16 at 00:08
2

You can use recursion:

var page = require('webpage').create();

// the urls to navigate to
var urls = [
    'http://phantomjs.org/',
    'https://twitter.com/sidanmor',
    'https://github.com/sidanmor'
];

var i = 0;

// the recursion function
var genericCallback = function () {
    return function (status) {
        console.log("URL: " + urls[i]);
        console.log("Status: " + status);
        // exit if there was a problem with the navigation
        if (!status || status === 'fail') phantom.exit();

        i++;

        if (status === "success") {

            //-- YOUR STUFF HERE ---------------------- 
            // do your stuff here... I'm taking a picture of the page
            page.render('example' + i + '.png');
            //-----------------------------------------

            if (i < urls.length) {
                // navigate to the next url and the callback is this function (recursion)
                page.open(urls[i], genericCallback());
            } else {
                // try navigate to the next url (it is undefined because it is the last element) so the callback is exit
                page.open(urls[i], function () {
                    phantom.exit();
                });
            }
        }
    };
};

// start from the first url
page.open(urls[i], genericCallback());
sidanmor
  • 5,079
  • 3
  • 23
  • 31
1

Using Queued Processes, sample:

var page = require('webpage').create();

// Queue Class Helper
var Queue = function() {
    this._tasks = [];
};
Queue.prototype.add = function(fn, scope) {
    this._tasks.push({fn: fn,scope: scope});
    return this;
};
Queue.prototype.process = function() {
    var proxy, self = this;
    task = this._tasks.shift();
    if(!task) {return;}
    proxy = {end: function() {self.process();}};
    task.fn.call(task.scope, proxy);
    return this;        
};
Queue.prototype.clear = function() {
    this._tasks = []; return this;
};

// Init pages .....  
var q = new Queue();       

q.add(function(proxy) {
  page.open(url1, function() {
    // page.evaluate
    proxy.end();
  });            
});

q.add(function(proxy) {
  page.open(url2, function() {
    // page.evaluate
    proxy.end();
  });            
});


q.add(function(proxy) {
  page.open(urln, function() {
    // page.evaluate
    proxy.end();
  });            
});

// .....

q.add(function(proxy) {
  phantom.exit()
  proxy.end();
});

q.process();

I hope this is useful, regards.

froilanq
  • 934
  • 7
  • 8