javascript - phantomjs no espera la carga de la página "completa"
events (13)
Aquí hay una solución que espera a que se completen todas las solicitudes de recursos. Una vez completado, registrará el contenido de la página en la consola y generará una captura de pantalla de la página renderizada.
Aunque esta solución puede servir como un buen punto de partida, he observado que falla, ¡así que definitivamente no es una solución completa!
No tuve mucha suerte con document.readyState
.
Me influenció el ejemplo de waitfor.js encuentra en la página de ejemplos de 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);
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);
});
Estoy usando PhantomJS v1.4.1 para cargar algunas páginas web. No tengo acceso a su lado del servidor, solo recibo enlaces apuntando a ellos. Estoy usando una versión obsoleta de Phantom porque necesito apoyar Adobe Flash en esas páginas web.
El problema es que muchos sitios web están cargando su menor contenido asincrónico y es por eso que la devolución de llamada onLoadFinished de Phantom (análoga para onLoad en HTML) se activó demasiado pronto cuando aún no todo se ha cargado. ¿Alguien puede sugerir cómo puedo esperar a cargar una página web para realizar, por ejemplo, una captura de pantalla con todo el contenido dinámico como los anuncios?
En mi programa, utilizo un poco de lógica para juzgar si fue onload: viendo su solicitud de red, si no hubo una nueva solicitud en los últimos 200 ms, lo trato onload.
Úselo después de 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;
}
}
}
Encontré esta solución útil en una aplicación NodeJS. Lo uso solo en casos desesperados porque inicia un tiempo de espera para esperar la carga de la página completa.
El segundo argumento es la función de devolución de llamada que se llamará una vez que la respuesta esté lista.
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);
Esta es una implementación de la respuesta de Supr. También usa setTimeout en lugar de setInterval como sugirió Mateusz Charytoniuk.
Phantomjs saldrá en 1000 ms cuando no haya ninguna solicitud o respuesta.
// 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();
Esta es una vieja pregunta, pero como estaba buscando carga de página completa, pero para Spookyjs (que usa casperjs y phantomjs) y no encontré mi solución, hice mi propio script para eso, con el mismo enfoque que el usuario deemstone. Lo que hace este enfoque es, por una cantidad de tiempo dada, si la página no recibió o no inició ninguna solicitud, terminará la ejecución.
En el archivo casper.js (si lo instaló de manera global, la ruta sería algo así como /usr/local/lib/node_modules/casperjs/modules/casper.js) agregue las siguientes líneas:
En la parte superior del archivo con todos los vars globales:
var waitResponseInterval = 500
var reqResInterval = null
var reqResFinished = false
var resetTimeout = function() {}
Luego, dentro de la función "createPage (casper)" justo después de "var page = require (''página web''). Create ();" agregue el siguiente código:
resetTimeout = function() {
if(reqResInterval)
clearTimeout(reqResInterval)
reqResInterval = setTimeout(function(){
reqResFinished = true
page.onLoadFinished("success")
},waitResponseInterval)
}
resetTimeout()
Luego, dentro de "page.onResourceReceived = function onResourceReceived (resource) {" en la primera línea agrega:
resetTimeout()
Haga lo mismo para "page.onResourceRequested = function onResourceRequested (requestData, request) {"
Finalmente, en "page.onLoadFinished = function onLoadFinished (status) {" en la primera línea agrega:
if(!reqResFinished)
{
return
}
reqResFinished = false
Y eso es todo, espero que esto ayude a alguien en problemas como yo. Esta solución es para casperjs pero funciona directamente para Spooky.
Buena suerte !
Este es el código que uso:
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);
});
Básicamente dado el hecho de que se supone que debes saber que la página está completamente descargada cuando aparece un elemento en el DOM. Entonces, el script esperará hasta que esto suceda.
Este método me resultó útil en algunos casos:
page.onConsoleMessage(function(msg) {
// do something e.g. page.render
});
Que si tienes la página, pon algo de script dentro:
<script>
window.onload = function(){
console.log(''page loaded'');
}
</script>
Otro enfoque es pedirle a PhantomJS que espere un poco después de que la página se haya cargado antes de hacer el renderizado, según el ejemplo de rasterize.js normal, pero con un tiempo de espera más largo para permitir que el JavaScript termine de cargar recursos adicionales:
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
}
});
Preferiría verificar periódicamente el estado de document.readyState
( https://developer.mozilla.org/en-US/docs/Web/API/document.readyState ). Aunque este enfoque es un poco torpe, puede estar seguro de que dentro de la función de onPageReady
está utilizando un documento completamente cargado.
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();
});
Explicación adicional:
El uso de setTimeout
anidado en lugar de setInterval
evita que checkReadyState
"superponga" y checkReadyState
a las condiciones de carrera cuando se prolonga su ejecución por algún motivo aleatorio. setTimeout
tiene un retraso predeterminado de 4 ms ( https://.com/a/3580085/1011156 ) por lo que el sondeo activo no afectará drásticamente el rendimiento del programa.
document.readyState === "complete"
significa que el documento está completamente cargado con todos los recursos ( https://html.spec.whatwg.org/multipage/dom.html#current-document-readiness ).
Puede intentar una combinación de los ejemplos de espera y rasterizar:
/**
* 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);
}
});
}
Tal vez pueda usar las devoluciones de llamada onResourceRequested
y onResourceReceived
para detectar la carga asíncrona. Aquí hay un ejemplo del uso de esas devoluciones de llamada de su documentación :
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);
Además, puede ver examples/netsniff.js
para un ejemplo de trabajo.
Yo uso una combinación personal del waitfor.js phantomjs waitfor.js
.
Este es mi archivo 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);
}
});
Y el archivo lib/waitFor.js
(que es solo una copia y pega de la función waifFor()
del waitfor.js phantomjs waitfor.js
):
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
}
Este método no es asincrónico, pero al menos estoy seguro de que todos los recursos se cargaron antes de intentar usarlos.
esta es mi solución, funcionó para mí.
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);
}
});