phantomjs不等待“全部”页面加载

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

问题是许多网站正在加载他们的次要内容asynchronous,这就是为什么Phantom的onLoadFinishedcallback(类似于HTML中的onLoad)发射太早,而不是所有东西都加载。 任何人都可以build议如何等待网页的满载,例如,广告的所有dynamic内容的截图?

另一种方法是在按照常规的rasterize.js示例执行渲染之前,先要求PhantomJS在页面加载之后等待一会儿,但延迟时间较长以允许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 } }); 

我宁愿定期检查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具有4ms的默认延迟( https://stackoverflow.com/a/3580085/1011156 ),所以活动轮询不会严重影响程序性能。

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

您可以尝试使用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 (eg: "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 (eg: "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); } }); } 

在我的程序中,我使用一些逻辑来判断它是否在线:看它是networking请求,如果在过去的200毫秒内没有新的请求,我把它加载。

在onLoadFinish()之后使用这个。

 function onLoadComplete(page, callback){ var waiting = []; // request id var interval = 200; //ms time waiting new request var timer = setTimeout( timeout, interval); var max_retry = 3; // var counter_retry = 0; function timeout(){ if(waiting.length && counter_retry < max_retry){ timer = setTimeout( timeout, interval); counter_retry++; return; }else{ try{ callback(null, page); }catch(e){} } } //for debug, log time cost var tlogger = {}; bindEvent(page, 'request', function(req){ waiting.push(req.id); }); bindEvent(page, 'receive', function (res) { var cT = res.contentType; if(!cT){ console.log('[contentType] ', cT, ' [url] ', res.url); } if(!cT) return remove(res.id); if(cT.indexOf('application') * cT.indexOf('text') != 0) return remove(res.id); if (res.stage === 'start') { console.log('!!received start: ', res.id); //console.log( JSON.stringify(res) ); tlogger[res.id] = new Date(); }else if (res.stage === 'end') { console.log('!!received end: ', res.id, (new Date() - tlogger[res.id]) ); //console.log( JSON.stringify(res) ); remove(res.id); clearTimeout(timer); timer = setTimeout(timeout, interval); } }); bindEvent(page, 'error', function(err){ remove(err.id); if(waiting.length === 0){ counter_retry = 0; } }); function remove(id){ var i = waiting.indexOf( id ); if(i < 0){ return; }else{ waiting.splice(i,1); } } function bindEvent(page, evt, cb){ switch(evt){ case 'request': page.onResourceRequested = cb; break; case 'receive': page.onResourceReceived = cb; break; case 'error': page.onResourceError = cb; break; case 'timeout': page.onResourceTimeout = cb; break; } } } 

也许你可以使用onResourceRequestedonResourceReceivedcallback来检测asynchronous加载。 下面是从他们的文档中使用这些callback的例子:

 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

这是等待所有资源请求完成的解决scheme。 一旦完成,它将把页面内容logging到控制台,并生成渲染页面的屏幕截图。

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

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

我受到phantomjs示例页面上的waitfor.js示例的影响 。

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

我发现这种方法在某些情况下很有用:

 page.onConsoleMessage(function(msg) { // do something eg page.render }); 

比如果你自己的页面里面放一些脚本:

 <script> window.onload = function(){ console.log('page loaded'); } </script> 

我发现这个解决scheme在NodeJS应用程序中很有用。 我只是在绝望的情况下使用它,因为它启动超时,以等待整个页面加载。

第二个参数是callback函数,一旦响应准备就会被调用。

 phantom = require('phantom'); var fullLoad = function(anUrl, callbackDone) { phantom.create(function (ph) { ph.createPage(function (page) { page.open(anUrl, function (status) { if (status !== 'success') { console.error("pahtom: error opening " + anUrl, status); ph.exit(); } else { // timeOut global.setTimeout(function () { page.evaluate(function () { return document.documentElement.innerHTML; }, function (result) { ph.exit(); // EXTREMLY IMPORTANT callbackDone(result); // callback }); }, 5000); } }); }); }); } var callback = function(htmlBody) { // do smth with the htmlBody } fullLoad('your/url/', callback); 

这是Supr的答案的实现。 另外它使用setTimeout而不是setInterval作为Mateusz Charytoniukbuild议。

当没有任何请求或响应时,幻影将在1000毫秒内退出。

 // load the module var webpage = require('webpage'); // get timestamp function getTimestamp(){ // or use Date.now() return new Date().getTime(); } var lastTimestamp = getTimestamp(); var page = webpage.create(); page.onResourceRequested = function(request) { // update the timestamp when there is a request lastTimestamp = getTimestamp(); }; page.onResourceReceived = function(response) { // update the timestamp when there is a response lastTimestamp = getTimestamp(); }; page.open(html, function(status) { if (status !== 'success') { // exit if it fails to load the page phantom.exit(1); } else{ // do something here } }); function checkReadyState() { setTimeout(function () { var curentTimestamp = getTimestamp(); if(curentTimestamp-lastTimestamp>1000){ // exit if there isn't request or response in 1000ms phantom.exit(); } else{ checkReadyState(); } }, 100); } checkReadyState(); 

这个我使用的代码:

 var system = require('system'); var page = require('webpage').create(); page.open('http://....', function(){ console.log(page.content); var k = 0; var loop = setInterval(function(){ var qrcode = page.evaluate(function(s) { return document.querySelector(s).src; }, '.qrcode img'); k++; if (qrcode){ console.log('dataURI:', qrcode); clearInterval(loop); phantom.exit(); } if (k === 50) phantom.exit(); // 10 sec timeout }, 200); }); 

基本上给你一个事实,你应该知道,当一个给定的元素出现在DOM上,页面被完全下载。 所以脚本就要等到这发生。

这是一个古老的问题,但因为我正在寻找整页加载,但为Spookyjs(使用casperjs和phantomjs),并没有find我的解决scheme,我做了我自己的脚本,用户的理念相同的方法。 这种方法的作用是,在给定的时间内,如果页面没有收到或启动任何请求,它将结束执行。

在casper.js文件中(如果你是全局安装的话,path就像/usr/local/lib/node_modules/casperjs/modules/casper.js)添加下面几行:

在所有全局variables的文件顶部:

 var waitResponseInterval = 500 var reqResInterval = null var reqResFinished = false var resetTimeout = function() {} 

然后在“var page = require('webpage')。create();”之后的函数“createPage(casper) 添加下面的代码:

  resetTimeout = function() { if(reqResInterval) clearTimeout(reqResInterval) reqResInterval = setTimeout(function(){ reqResFinished = true page.onLoadFinished("success") },waitResponseInterval) } resetTimeout() 

然后在第一行添加“page.onResourceReceived = function onResourceReceived(resource){”

  resetTimeout() 

对“page.onResourceRequested = function onResourceRequested(requestData,request){”

最后,在“page.onLoadFinished = function onLoadFinished(status){”的第一行添加:

  if(!reqResFinished) { return } reqResFinished = false 

就是这样,希望这个帮助像我一样麻烦的人。 这个解决scheme是用于casperjs,但是直接为Spooky工作。

祝你好运 !

我使用phantomjs waitfor.js示例的个人混合。

这是我的main.js文件:

 'use strict'; var wasSuccessful = phantom.injectJs('./lib/waitFor.js'); var page = require('webpage').create(); page.open('http://foo.com', function(status) { if (status === 'success') { page.includeJs('jquery-3.1.1.min.js', function() { waitFor(function() { return page.evaluate(function() { if ('complete' === document.readyState) { return true; } return false; }); }, function() { var fooText = page.evaluate(function() { return $('#foo').text(); }); phantom.exit(); }); }); } else { console.log('error'); phantom.exit(1); } }); 

lib/waitFor.js文件(这只是phantomjs waitfor.js例子中的waifFor()函数的复制和粘贴):

 function waitFor(testFx, onReady, timeOutMillis) { var maxtimeOutMillis = timeOutMillis ? timeOutMillis : 3000, //< Default Max Timout is 3s start = new Date().getTime(), condition = false, 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 condi> clearInterval(interval); //< Stop this interval } } }, 250); //< repeat check every 250ms } 

这种方法不是asynchronous的,但至less我确信,在尝试使用它们之前,所有资源都已加载。

这是我的解决scheme,为我工作。

 page.onConsoleMessage = function(msg, lineNum, sourceId) { if(msg=='hey lets take screenshot') { window.setInterval(function(){ try { var sta= page.evaluateJavaScript("function(){ return jQuery.active;}"); if(sta == 0) { window.setTimeout(function(){ page.render('test.png'); clearInterval(); phantom.exit(); },1000); } } catch(error) { console.log(error); phantom.exit(1); } },1000); } }; page.open(address, function (status) { if (status !== "success") { console.log('Unable to load url'); phantom.exit(); } else { page.setContent(page.content.replace('</body>','<script>window.onload = function(){console.log(\'hey lets take screenshot\');}</script></body>'), address); } });