在单个脚本中使用 Multiple page.open

IT技术 javascript phantomjs
2021-01-24 19:02:21

我的目标是使用以下方法执行 PhantomJS:

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

然后在里面script.js,我计划使用多个page.open()来捕获不同页面的屏幕截图,例如:

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
});

在运行时exec,我在页面上得到以下输出:

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

结果我只得到了第三页的截图。我不知道为什么 PhantomJS 跳过第一和第二个代码块(从console.log()应该从第一个和第二个块输出的丢失消息中可以看出)并且只执行第三个代码块。

4个回答

问题是第二个page.open在第一个完成之前被调用,这可能会导致多个问题。您需要大致如下的逻辑(假设文件名作为命令行参数给出):

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();

没错,它是递归的。这可确保传递给的函数的处理page.open完成,并有 100 毫秒的宽限期,然后再转到下一个文件。

顺便说一句,你不需要一直重复

page = require('webpage').create();
这就说得通了。让我试一试,然后再回复你。
2021-03-30 19:02:21
我尝试了大量页面并在 ubuntu 上遇到了分段错误。递归调用似乎对内存做了一些讨厌的事情。有没有更具可扩展性的解决方案?例如。不是递归和水平可扩展的。
2021-04-02 19:02:21
我来到这个页面是因为我们遇到了类似的问题。奇怪的是,它是在软件运行后同时打开多达 70 个网址(全部为file://后才开始发生的所以它绝对有可能,并且可以打开多个 url,但是 - 正如我们的情况所证明的那样,它可能有任意的问题。
2021-04-07 19:02:21
您有一个事实来源,即同时调用多个 page.open 会导致 PhantomJS 出现问题(或仅凭经验)?我遇到了相关问题并正在寻找解决方法。
2021-04-10 19:02:21
有用且简单!谢谢!
2021-04-13 19:02:21

我已经尝试了接受的答案建议,但它不起作用(至少不适用于 v2.1.1)。

准确地说,接受的答案在某些时候是有效的,但我仍然遇到了偶尔失败的 page.open() 调用,大约 90% 的时间是在特定数据集上。

我找到的最简单的答案是为每个 url 实例化一个新的页面module。

// 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(){
        // ... 
        // ...
    })
}

页面module api 文档中关于 close 方法的以下内容说

关闭(){无效}

关闭页面并释放与之关联的内存堆。调用此后不要使用页面实例。

由于某些技术限制,网页对象可能不会被完全垃圾收集。当反复使用同一个对象时,经常会遇到这种情况。调用此函数可能会停止增加的堆分配。

基本上在我测试了 close() 方法之后,我决定对不同的 open() 调用使用相同的网页实例太不可靠了,需要说明一下。

setTimeout(openPageB(), ...)行不应该在 openPageB 之后有括号。照原样,您只是在那里调用 openPageB 并将返回值传递给 setTimeout。
2021-04-10 19:02:21

您可以使用递归:

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());

使用排队进程,示例:

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();

我希望这是有用的,问候。