142 votes

phantomjs n'attend pas le chargement complet de la page

J'utilise PhantomJS v1.4.1 pour charger certaines pages web. Je n'ai pas accès à leur serveur, je reçois juste des liens pointant vers eux. J'utilise une version obsolète de Phantom car j'ai besoin de supporter Adobe Flash sur ces pages web.

Le problème est que de nombreux sites web chargent leur contenu mineur de manière asynchrone et c'est pourquoi le rappel onLoadFinished de Phantom (analogue à onLoad en HTML) se déclenche trop tôt alors que tout n'est pas encore chargé. Quelqu'un peut-il me suggérer comment attendre le chargement complet d'une page web pour faire, par exemple, une capture d'écran avec tout le contenu dynamique comme les publicités ?

80voto

rhunwicks Points 827

Une autre approche consiste à demander à PhantomJS d'attendre un peu après le chargement de la page avant d'effectuer le rendu, comme dans l'exemple suivant rasterize.js mais avec un délai plus long pour permettre à JavaScript de terminer le chargement de ressources supplémentaires :

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

53voto

Je préférerais vérifier périodiquement document.readyState statut ( https://developer.mozilla.org/en-US/docs/Web/API/document.readyState ). Bien que cette approche soit un peu maladroite, vous pouvez être sûr qu'à l'intérieur de onPageReady vous utilisez un document entièrement chargé.

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

Explication supplémentaire :

L'utilisation d'un système imbriqué setTimeout au lieu de setInterval empêche checkReadyState de "chevauchement" et de conditions de course lorsque son exécution est prolongée pour des raisons aléatoires. setTimeout a un délai par défaut de 4ms ( https://stackoverflow.com/a/3580085/1011156 ), de sorte que l'interrogation active n'affectera pas radicalement les performances du programme.

document.readyState === "complete" signifie que le document est complètement chargé avec toutes les ressources ( https://html.spec.whatwg.org/multipage/dom.html#current-document-readiness ).

EDIT 2022 : J'ai créé cette réponse il y a 8 ans et je n'ai pas utilisé PhantomJS depuis. Il est très probable que cela ne fonctionne pas maintenant dans certains cas. De plus, je pense maintenant qu'il n'est pas possible de créer une solution unique pour être absolument sûr que la page est chargée. En effet, certaines pages peuvent charger des ressources supplémentaires après que le document est prêt. Par exemple, il peut y avoir du code JS sur le site web qui attend que le document soit prêt et qui charge ensuite des ressources supplémentaires (après que l'état du document soit passé à ready ) - dans ce cas, le onPageReady se déclenchera et la page recommencera à charger d'autres ressources.

Je continue de penser que le texte ci-dessus est un bon point de départ et qu'il peut fonctionner dans la plupart des cas, mais qu'il peut aussi être nécessaire de créer des solutions spécifiques pour gérer des sites web spécifiques.

21voto

rhunwicks Points 827

Vous pouvez essayer une combinaison des exemples waitfor et 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);
        }
    });
}

16voto

Dave Points 1329

Voici une solution qui attend que toutes les demandes de ressources soient terminées. Une fois terminée, elle enregistre le contenu de la page dans la console et génère une capture d'écran de la page rendue.

Bien que cette solution puisse constituer un bon point de départ, j'ai constaté qu'elle échouait et qu'il ne s'agissait donc pas d'une solution complète !

Je n'ai pas eu beaucoup de chance en utilisant document.readyState .

J'ai été influencé par la waitfor.js que l'on trouve sur le site page d'exemples 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);
  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);
});

14voto

Supr Points 4738

Vous pouvez peut-être utiliser le onResourceRequested y onResourceReceived rappels pour détecter les chargements asynchrones. Voici un exemple d'utilisation de ces rappels à partir de leur 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);

Vous pouvez également consulter examples/netsniff.js pour un exemple concret.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X