phantomjs 不等待“完整”页面加载

IT技术 javascript events phantomjs
2021-01-14 18:39:18

我正在使用PhantomJS v1.4.1 来加载一些网页。我无权访问他们的服务器端,我只是获得指向他们的链接。我正在使用 Phantom 的过时版本,因为我需要在该网页上支持 Adob​​e Flash。

问题是许多网站都在异步加载它们的次要内容,这就是为什么 Phantom 的 onLoadFinished 回调(类似于 HTML 中的 onLoad)在并非所有内容都已加载时过早触发的原因。任何人都可以建议我如何等待网页满载来制作,例如,包含所有动态内容(如广告)的屏幕截图?

6个回答

另一种方法是让 PhantomJS 在页面加载后等待一段时间,然后再进行渲染,就像常规的rasterize.js示例一样,但超时时间更长,以允许 JavaScript 完成加载其他资源:

page.open(address, function (status) {
    if (status !== 'success') {
        console.log('Unable to load the address!');
        phantom.exit();
    } else {
        window.setTimeout(function () {
            page.render(output);
            phantom.exit();
        }, 1000); // Change timeout as required to allow sufficient time 
    }
});
是的,目前我坚持这种方法。
2021-03-20 18:39:18
如果您可以控制要阅读的代码,则可以显式调用 phantom js 回调:phantomjs.org/api/webpage/handler/on-callback.html
2021-03-26 18:39:18
这仍然是 2016 年的最佳解决方案吗?看起来我们应该能够做得比这更好。
2021-03-27 18:39:18
这是一个可怕的解决方案,抱歉(这是 PhantomJS 的错!)。如果您等待一整秒,但加载需要 20 毫秒,这完全是在浪费时间(想想批处理作业),或者如果花费的时间超过一秒,它仍然会失败。这种低效率和不可靠性对于专业工作来说是无法忍受的。
2021-04-06 18:39:18
这里真正的问题是你永远不知道 javascript 什么时候会完成加载页面,浏览器也不知道。想象一下有一些 javascript 从服务器无限循环加载一些东西的站点。从浏览器的角度来看 - javascript 执行永无止境,那么您希望 phantomjs 告诉您它已经完成的那一刻是什么时候?这个问题在一般情况下无法解决,除非等待超时解决方案并希望最好。
2021-04-08 18:39:18

我宁愿定期检查document.readyState状态(https://developer.mozilla.org/en-US/docs/Web/API/document.readyState)。虽然这种方法有点笨拙,但您可以确定onPageReady您正在使用完整加载的文件内部函数。

var page = require("webpage").create(),
    url = "http://example.com/index.html";

function onPageReady() {
    var htmlContent = page.evaluate(function () {
        return document.documentElement.outerHTML;
    });

    console.log(htmlContent);

    phantom.exit();
}

page.open(url, function (status) {
    function checkReadyState() {
        setTimeout(function () {
            var readyState = page.evaluate(function () {
                return document.readyState;
            });

            if ("complete" === readyState) {
                onPageReady();
            } else {
                checkReadyState();
            }
        });
    }

    checkReadyState();
});

补充说明:

当由于某些随机原因延长执行时间时,使用嵌套setTimeout而不是setInterval防止checkReadyState“重叠”和竞争条件。setTimeout默认延迟为 4 毫秒(https://stackoverflow.com/a/3580085/1011156),因此主动轮询不会显着影响程序性能。

document.readyState === "complete"意味着该文档已完全加载了所有资源(https://html.spec.whatwg.org/multipage/dom.html#current-document-readiness)。

这不考虑 DOM 完全加载后的任何 JavaScript 加载,例如使用 Backbone/Ember/Angular。
2021-03-16 18:39:18
关于 setTimeout 与 setInterval 的评论很棒。
2021-03-23 18:39:18
对我根本不起作用。readyState complete 可能已经触发,但此时页面是空白的。
2021-03-24 18:39:18
readyState只会在 DOM 完全加载后触发,但是任何<iframe>元素可能仍在加载,因此它并没有真正回答原始问题
2021-04-03 18:39:18
@rgraham 这并不理想,但我认为我们只能用这些渲染器做这么多。会有一些边缘情况,您只是不知道某些东西是否已完全加载。想想一个内容故意延迟一两分钟的页面。期望渲染过程无所事事地等待无限长的时间是不合理的。从可能很慢的外部源加载的内容也是如此。
2021-04-08 18:39:18

您可以尝试结合使用 waitfor 和 rasterize 示例:

/**
 * See https://github.com/ariya/phantomjs/blob/master/examples/waitfor.js
 * 
 * Wait until the test condition is true or a timeout occurs. Useful for waiting
 * on a server response or for a ui change (fadeIn, etc.) to occur.
 *
 * @param testFx javascript condition that evaluates to a boolean,
 * it can be passed in as a string (e.g.: "1 == 1" or "$('#bar').is(':visible')" or
 * as a callback function.
 * @param onReady what to do when testFx condition is fulfilled,
 * it can be passed in as a string (e.g.: "1 == 1" or "$('#bar').is(':visible')" or
 * as a callback function.
 * @param timeOutMillis the max amount of time to wait. If not specified, 3 sec is used.
 */
function waitFor(testFx, onReady, timeOutMillis) {
    var maxtimeOutMillis = timeOutMillis ? timeOutMillis : 3000, //< Default Max Timout is 3s
        start = new Date().getTime(),
        condition = (typeof(testFx) === "string" ? eval(testFx) : testFx()), //< defensive code
        interval = setInterval(function() {
            if ( (new Date().getTime() - start < maxtimeOutMillis) && !condition ) {
                // If not time-out yet and condition not yet fulfilled
                condition = (typeof(testFx) === "string" ? eval(testFx) : testFx()); //< defensive code
            } else {
                if(!condition) {
                    // If condition still not fulfilled (timeout but condition is 'false')
                    console.log("'waitFor()' timeout");
                    phantom.exit(1);
                } else {
                    // Condition fulfilled (timeout and/or condition is 'true')
                    console.log("'waitFor()' finished in " + (new Date().getTime() - start) + "ms.");
                    typeof(onReady) === "string" ? eval(onReady) : onReady(); //< Do what it's supposed to do once the condition is fulfilled
                    clearInterval(interval); //< Stop this interval
                }
            }
        }, 250); //< repeat check every 250ms
};

var page = require('webpage').create(), system = require('system'), address, output, size;

if (system.args.length < 3 || system.args.length > 5) {
    console.log('Usage: rasterize.js URL filename [paperwidth*paperheight|paperformat] [zoom]');
    console.log('  paper (pdf output) examples: "5in*7.5in", "10cm*20cm", "A4", "Letter"');
    phantom.exit(1);
} else {
    address = system.args[1];
    output = system.args[2];
    if (system.args.length > 3 && system.args[2].substr(-4) === ".pdf") {
        size = system.args[3].split('*');
        page.paperSize = size.length === 2 ? {
            width : size[0],
            height : size[1],
            margin : '0px'
        } : {
            format : system.args[3],
            orientation : 'portrait',
            margin : {
                left : "5mm",
                top : "8mm",
                right : "5mm",
                bottom : "9mm"
            }
        };
    }
    if (system.args.length > 4) {
        page.zoomFactor = system.args[4];
    }
    var resources = [];
    page.onResourceRequested = function(request) {
        resources[request.id] = request.stage;
    };
    page.onResourceReceived = function(response) {
        resources[response.id] = response.stage;
    };
    page.open(address, function(status) {
        if (status !== 'success') {
            console.log('Unable to load the address!');
            phantom.exit();
        } else {
            waitFor(function() {
                // Check in the page if a specific element is now visible
                for ( var i = 1; i < resources.length; ++i) {
                    if (resources[i] != 'end') {
                        return false;
                    }
                }
                return true;
            }, function() {
               page.render(output);
               phantom.exit();
            }, 10000);
        }
    });
}
做任何驱动程序,例如。poltergeist,有这样的功能吗?
2021-03-25 18:39:18
似乎它不适用于使用任何服务器推送技术的网页,因为在发生 onLoad 后资源仍将被使用。
2021-04-02 18:39:18
是否可以使用 waitFor 轮询整个 html 文本并搜索定义的关键字?我试图实现这一点,但似乎轮询没有刷新到最新下载的 html 源。
2021-04-07 18:39:18

这是一个等待所有资源请求完成的解决方案。完成后,它会将页面内容记录到控制台并生成渲染页面的屏幕截图。

虽然这个解决方案可以作为一个很好的起点,但我观察到它失败了,所以它绝对不是一个完整的解决方案!

我没有太多运气使用document.readyState.

我被影响waitfor.js信中例如phantomjs例子页面

var system = require('system');
var webPage = require('webpage');

var page = webPage.create();
var url = system.args[1];

page.viewportSize = {
  width: 1280,
  height: 720
};

var requestsArray = [];

page.onResourceRequested = function(requestData, networkRequest) {
  requestsArray.push(requestData.id);
};

page.onResourceReceived = function(response) {
  var index = requestsArray.indexOf(response.id);
  if (index > -1 && response.stage === 'end') {
    requestsArray.splice(index, 1);
  }
};

page.open(url, function(status) {

  var interval = setInterval(function () {

    if (requestsArray.length === 0) {

      clearInterval(interval);
      var content = page.content;
      console.log(content);
      page.render('yourLoadedPage.png');
      phantom.exit();
    }
  }, 500);
});
在从请求数组中删除它之前,您应该检查 response.stage 是否等于 'end',否则它可能会被过早删除。
2021-03-13 18:39:18
竖起大拇指,但使用 setTimeout 和 10,而不是间隔
2021-03-22 18:39:18
如果您的网页动态加载 DOM,这将不起作用
2021-03-26 18:39:18

也许您可以使用onResourceRequestedonResourceReceived回调来检测异步加载。这是从他们的文档中使用这些回调的示例

var page = require('webpage').create();
page.onResourceRequested = function (request) {
    console.log('Request ' + JSON.stringify(request, undefined, 4));
};
page.onResourceReceived = function (response) {
    console.log('Receive ' + JSON.stringify(response, undefined, 4));
};
page.open(url);

此外,您可以查看examples/netsniff.js一个工作示例。

但在这种情况下,我不能使用 PhantomJS 的一个实例一次加载多个页面,对吗?
2021-03-11 18:39:18
@CMCDragonkai 我自己从未使用过它,但基于此,它似乎包含所有请求。引用:All the resource requests and responses can be sniffed using onResourceRequested and onResourceReceived
2021-04-05 18:39:18
onResourceRequested 是否适用于 AJAX/跨域请求?还是它只适用于像 css、图像等?
2021-04-10 18:39:18
我已经将这种方法用于大规模 PhantomJS 渲染,并且效果很好。您确实需要很多智能来跟踪请求并观察它们是否失败或超时。更多信息:sorcery.smugmug.com/2013/12/17/using-phantomjs-at-scale
2021-04-10 18:39:18