我使用PhantomJS v1.4.1加載一些網頁。我沒有訪問他們的服務器端,我只是得到指向他們的鏈接。我使用Phantom的過時版本,因爲我需要在該網頁上支持Adobe Flash。phantomjs不等待「全部」頁面加載
問題是許多網站正在加載他們的次要內容異步,這就是爲什麼Phantom的onLoadFinished回調(類似於HTML中的onLoad)在未加載任何東西時觸發得太早。任何人都可以建議我如何等待網頁的全部加載,例如,所有動態內容(如廣告)的截圖?
我使用PhantomJS v1.4.1加載一些網頁。我沒有訪問他們的服務器端,我只是得到指向他們的鏈接。我使用Phantom的過時版本,因爲我需要在該網頁上支持Adobe Flash。phantomjs不等待「全部」頁面加載
問題是許多網站正在加載他們的次要內容異步,這就是爲什麼Phantom的onLoadFinished回調(類似於HTML中的onLoad)在未加載任何東西時觸發得太早。任何人都可以建議我如何等待網頁的全部加載,例如,所有動態內容(如廣告)的截圖?
也許你可以使用onResourceRequested
and onResourceReceived
callbacks來檢測異步加載。下面是使用這些回調from their documentation的例子:
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
的工作示例。
你可以嘗試的WAITFOR和光柵化實例的組合:
/**
* 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);
}
});
}
另一種方法是隻問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
}
});
在我的計劃,我用一些邏輯來判斷,如果它是有載:看它的網絡請求,如果沒有新的請求在過去的200毫秒,我tre在它上載。
在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;
}
}
}
我寧願定期檢查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)。
setTimeout的註釋vs setInterval很棒。 –
'readyState'將只在DOM觸發已經滿載,但是任何''
@rgraham這是不理想,但我認爲,我們只能做這麼多與這些renderers。將會出現邊緣情況,你只是不知道是否加載了一些東西。考慮一個頁面,內容會被故意拖延一兩分鐘。期望渲染過程坐在一旁並等待不確定的時間是不合理的。從外部來源加載的內容也可能很慢,這同樣適用。 –
我發現這種方法在某些情況下非常有用:
page.onConsoleMessage(function(msg) {
// do something e.g. page.render
});
比,如果你自己把一些腳本里面的頁面:
<script>
window.onload = function(){
console.log('page loaded');
}
</script>
這看起來像一個非常好的解決方法,但是,我無法從我的HTML/JavaScript頁面獲取任何日誌消息來通過phantomJS ... onConsoleMessage事件從未觸發,而我可以在瀏覽器控制檯上完美地看到消息,我不知道爲什麼。 – Dirk
我需要page.onConsoleMessage = function(msg){}; –
我發現在應用程序的NodeJS該解決方案非常有用。 我只是在絕望的情況下使用它,因爲它會啓動超時以等待整個頁面的加載。
第二個參數是回調函數,它將在響應準備就緒後調用。
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);
這是一個老問題,但因爲我一直在尋找的全頁面加載但Spookyjs(使用casperjs和phantomjs),並沒有發現我的解決方案,我做我自己的腳本,這是與與用戶理念相同的方法。 這種方法的作用是,對於給定的時間量,如果頁面沒有收到或啓動任何請求,它將結束執行。
在casper.js文件(如果全球安裝,路徑會像/usr/local/lib/node_modules/casperjs/modules/casper.js)加入下面幾行:
在與所有的全局變量的文件的頂部:剛過
var waitResponseInterval = 500
var reqResInterval = null
var reqResFinished = false
var resetTimeout = function() {}
然後內部功能「createPage(卡斯帕)」,「VAR頁=需要(‘網頁’)創建();」。添加以下代碼:
resetTimeout = function() {
if(reqResInterval)
clearTimeout(reqResInterval)
reqResInterval = setTimeout(function(){
reqResFinished = true
page.onLoadFinished("success")
},waitResponseInterval)
}
resetTimeout()
然後內部 「page.onResourceReceived =函數onResourceReceived(資源){」 在第一行添加:
resetTimeout()
執行相同的「page.onResourceRequested =函數onResourceRequested (的RequestData,請求){」
最後,在 「page.onLoadFinished =函數onLoadFinished(狀態){」 在第一行中添加:
if(!reqResFinished)
{
return
}
reqResFinished = false
就是這樣,希望這個幫助像我一樣陷入麻煩的人。此解決方案適用於casperjs,但可直接用於Spooky。
祝你好運!
這是Supr的答案的實現。它也使用setTimeout而不是setInterval,正如Mateusz Charytoniuk所建議的那樣。
當沒有任何請求或響應時,Phantomjs將在1000ms內退出。
// 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的事實。所以腳本將會等到這發生。
這是等待所有資源請求完成的解決方案。完成後,它會將頁面內容記錄到控制檯並生成渲染頁面的屏幕截圖。
雖然這個解決方案可以作爲一個很好的起點,但我發現它失敗了,所以它絕對不是一個完整的解決方案!
我沒有太多的運氣使用document.readyState
。
我受到上發現的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);
});
我使用幻影的人格混合waitfor.js
example。
這是我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('https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.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
example的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
}
這種方法不異步,但至少我確信所有資源在我嘗試使用它們之前都已加載。
這是我的解決方案,它爲我工作。
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);
}
});
但在這種情況下,我不能使用PhantomJS的一個實例一次加載多個頁面,對嗎? – nilfalse
onResourceRequested是否適用於AJAX /跨域請求?還是它只適用於像CSS,圖像..等? – CMCDragonkai
@CMCDragonkai我從來沒有使用過它,但基於[this](https://github.com/ariya/phantomjs/wiki/Network-Monitoring),它似乎包含所有請求。 Quote:'所有的資源請求和響應可以使用onResourceRequested和onResourceReceived嗅探' – Supr