phantomjs tidak menunggu pemuatan halaman "penuh"


139

Saya menggunakan PhantomJS v1.4.1 untuk memuat beberapa halaman web. Saya tidak memiliki akses ke sisi server mereka, saya hanya mendapatkan tautan yang mengarah ke mereka. Saya menggunakan Phantom versi lama karena saya perlu mendukung Adobe Flash di halaman web itu.

Masalahnya adalah banyak situs web memuat asinkron konten minornya dan itulah mengapa callback onLoadFinished Phantom (analog untuk onLoad dalam HTML) diaktifkan terlalu dini ketika tidak semuanya masih dimuat. Adakah yang bisa menyarankan bagaimana saya bisa menunggu pemuatan penuh laman web, misalnya, tangkapan layar dengan semua konten dinamis seperti iklan?


3
Saya pikir
inilah

Jawaban:


76

Pendekatan lain adalah dengan meminta PhantomJS menunggu sebentar setelah halaman dimuat sebelum melakukan render, seperti contoh rasterize.js biasa , tetapi dengan waktu tunggu yang lebih lama untuk memungkinkan JavaScript menyelesaikan pemuatan sumber daya tambahan:

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

1
Ya, saat ini saya berpegang pada pendekatan ini.
nilfalse

104
Ini solusi yang mengerikan, maaf (ini salah PhantomJS!). Jika Anda menunggu satu detik penuh, tetapi perlu 20 md untuk memuat, itu hanya membuang-buang waktu (pikirkan pekerjaan batch), atau jika butuh waktu lebih dari satu detik, itu masih akan gagal. Inefisiensi dan tidak dapat diandalkan seperti itu tidak tertahankan untuk pekerjaan profesional.
CodeManX

9
Masalah sebenarnya di sini adalah Anda tidak pernah tahu kapan javascript akan selesai memuat halaman dan browser juga tidak mengetahuinya. Bayangkan situs yang memiliki beberapa javascript memuat sesuatu dari server dalam putaran tak terbatas. Dari sudut pandang browser - eksekusi javascript tidak pernah berakhir jadi saat apa Anda ingin phantomjs memberi tahu Anda bahwa itu telah selesai? Masalah ini tidak dapat dipecahkan dalam kasus umum kecuali dengan menunggu solusi batas waktu dan berharap yang terbaik.
Maxim Galushka

6
Apakah ini masih merupakan solusi terbaik pada 2016? Sepertinya kita harus bisa melakukan lebih baik dari ini.
Adam Thompson

6
Jika Anda mengontrol kode yang Anda coba baca, Anda dapat memanggil panggilan phantom js secara eksplisit: phantomjs.org/api/webpage/handler/on-callback.html
Andy Smith

52

Saya lebih suka memeriksa document.readyStatestatus secara berkala ( https://developer.mozilla.org/en-US/docs/Web/API/document.readyState ). Meskipun pendekatan ini agak kikuk, Anda dapat yakin bahwa di dalam onPageReadyfungsi Anda menggunakan dokumen yang dimuat sepenuhnya.

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

Penjelasan tambahan:

Menggunakan bersarang, setTimeoutbukan setIntervalmencegah checkReadyState"tumpang tindih" dan kondisi balapan saat eksekusinya diperpanjang karena beberapa alasan acak. setTimeoutmemiliki penundaan default 4ms ( https://stackoverflow.com/a/3580085/1011156 ) sehingga polling aktif tidak akan mempengaruhi kinerja program secara drastis.

document.readyState === "complete"berarti dokumen dimuat sepenuhnya dengan semua sumber daya ( https://html.spec.whatwg.org/multipage/dom.html#current-document-readiness ).


4
komentar di setTimeout vs setInterval bagus.
Gal Bracha

1
readyStatehanya akan terpicu setelah DOM dimuat penuh, namun <iframe>elemen apa pun mungkin masih dimuat sehingga tidak benar-benar menjawab pertanyaan asli
CodingIntrigue

1
@rgraham Ini tidak ideal tapi saya pikir kita hanya bisa melakukan banyak hal dengan penyaji ini. Akan ada kasus tepi di mana Anda tidak akan tahu apakah ada sesuatu yang dimuat dengan penuh. Pikirkan tentang halaman di mana konten tertunda, dengan sengaja, satu atau dua menit. Tidak masuk akal untuk mengharapkan proses render hanya diam dan menunggu waktu yang tidak ditentukan. Hal yang sama berlaku untuk konten yang dimuat dari sumber eksternal yang mungkin lambat.
Brandon Elliott

3
Ini tidak mempertimbangkan pemuatan JavaScript apa pun setelah DOM dimuat penuh, seperti dengan Backbone / Ember / Angular.
Adam Thompson

1
Tidak berhasil sama sekali untuk saya. readyState complete mungkin telah diaktifkan, tetapi halaman itu kosong pada saat ini.
Steve Staple

21

Anda dapat mencoba kombinasi dari contoh tunggu dan rasterisasi:

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

3
Sepertinya ini tidak akan berfungsi dengan halaman web, yang menggunakan teknologi server push, karena sumber daya akan tetap digunakan setelah onLoad terjadi.
nilfalse

Lakukan driver apa saja, mis. poltergeist , punya fitur seperti ini?
Jared Beck

Apakah mungkin menggunakan waitFor untuk mengumpulkan seluruh teks html dan mencari kata kunci yang ditentukan? Saya mencoba menerapkan ini tetapi tampaknya jajak pendapat tidak menyegarkan ke sumber html yang diunduh terbaru.
fpdragon

15

Berikut adalah solusi yang menunggu semua permintaan sumber daya selesai. Setelah selesai, itu akan mencatat konten halaman ke konsol dan menghasilkan tangkapan layar dari halaman yang dirender.

Meskipun solusi ini dapat berfungsi sebagai titik awal yang baik, saya telah mengamati bahwa ini gagal sehingga jelas bukan solusi yang lengkap!

Saya tidak beruntung menggunakan document.readyState.

Saya dipengaruhi oleh contoh waitfor.js yang ditemukan di halaman contoh 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);
});

Memberi jempol, tetapi menggunakan setTimeout dengan 10, bukan interval
GDmac

1
Anda harus memeriksa bahwa response.stage sama dengan 'end' sebelum menghapusnya dari larik permintaan, jika tidak maka akan dihapus sebelum waktunya.
Reimund

Ini tidak berfungsi jika laman web Anda memuat DOM secara dinamis
Sobat

14

Mungkin Anda dapat menggunakan callback onResourceRequesteddanonResourceReceived untuk mendeteksi pemuatan asinkron. Berikut adalah contoh penggunaan callback tersebut dari dokumentasinya :

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

Juga, Anda dapat melihat examples/netsniff.jscontoh yang berfungsi.


Tetapi dalam kasus ini saya tidak dapat menggunakan satu instance PhantomJS untuk memuat lebih dari satu halaman sekaligus, bukan?
nihil

Apakah onResourceRequested berlaku untuk permintaan AJAX / Cross Domain? Atau apakah itu hanya berlaku untuk seperti css, gambar .. dll?
CMCDragonkai

@CMCDragonkai Saya tidak pernah menggunakannya sendiri, tetapi berdasarkan ini sepertinya itu mencakup semua permintaan. Kutipan:All the resource requests and responses can be sniffed using onResourceRequested and onResourceReceived
Supr

Saya telah menggunakan metode ini dengan rendering PhantomJS skala besar dan bekerja dengan cukup baik. Anda memang membutuhkan banyak kecerdasan untuk melacak permintaan dan melihat apakah permintaan tersebut gagal atau habis. Info lebih lanjut: sihir.smugmug.com/2013/12/17/using-phantomjs-at-scale
Ryan Doherty

13

Dalam program saya, saya menggunakan beberapa logika untuk menilai apakah itu onload: menonton permintaan jaringannya, jika tidak ada permintaan baru pada 200ms terakhir, saya memperlakukannya sebagai onload.

Gunakan ini, setelah 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;
        }
    }
}

11

Saya menemukan pendekatan ini berguna dalam beberapa kasus:

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

Dibandingkan jika Anda memiliki laman, masukkan beberapa skrip di dalamnya:

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

Ini terlihat seperti penyelesaian yang sangat bagus, namun, saya tidak bisa mendapatkan pesan log dari halaman HTML / JavaScript saya untuk melewati phantomJS ... event onConsoleMessage tidak pernah terpicu sementara saya dapat melihat pesan dengan sempurna di konsol Browser, dan Saya tidak tahu kenapa.
Dirk

1
Saya membutuhkan page.onConsoleMessage = function (msg) {};
Andy Balaam

5

Saya menemukan solusi ini berguna dalam aplikasi NodeJS. Saya menggunakannya hanya dalam kasus putus asa karena meluncurkan batas waktu untuk menunggu pemuatan halaman penuh.

Argumen kedua adalah fungsi panggilan balik yang akan dipanggil setelah respons siap.

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

3

Ini adalah implementasi dari jawaban Supr. Juga menggunakan setTimeout dan bukan setInterval seperti yang disarankan Mateusz Charytoniuk.

Phantomjs akan keluar dalam 1000ms jika tidak ada permintaan atau respon.

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

3

Ini kode yang saya gunakan:

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

Pada dasarnya mengingat fakta bahwa Anda seharusnya tahu bahwa halaman telah didownload secara penuh ketika elemen tertentu muncul di DOM. Jadi skrip akan menunggu sampai ini terjadi.


3

Saya menggunakan campuran personnal dari waitfor.jscontoh phantomjs .

Ini main.jsfile saya :

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

Dan lib/waitFor.jsfile tersebut (yang hanya merupakan copy dan paste waifFor()fungsi dari waitfor.jscontoh phantomjs ):

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
}

Metode ini tidak asinkron tetapi setidaknya saya yakin bahwa semua sumber daya telah dimuat sebelum saya mencoba menggunakannya.


2

Ini adalah pertanyaan lama, tetapi karena saya sedang mencari pemuatan halaman penuh tetapi untuk Spookyjs (yang menggunakan casperjs dan phantomjs) dan tidak menemukan solusi saya, saya membuat skrip sendiri untuk itu, dengan pendekatan yang sama seperti yang dianggap pengguna. Apa yang dilakukan pendekatan ini, untuk jangka waktu tertentu, jika halaman tidak menerima atau memulai permintaan apa pun, itu akan mengakhiri eksekusi.

Pada file casper.js (jika Anda menginstalnya secara global, jalurnya akan seperti /usr/local/lib/node_modules/casperjs/modules/casper.js) tambahkan baris berikut:

Di bagian atas file dengan semua vars global:

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

Kemudian di dalam fungsi "createPage (casper)" tepat setelah "var page = require ('webpage'). Create ();" tambahkan kode berikut:

 resetTimeout = function() {
     if(reqResInterval)
         clearTimeout(reqResInterval)

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

Kemudian di dalam "page.onResourceReceived = function onResourceReceived (resource) {" pada baris pertama tambahkan:

 resetTimeout()

Lakukan hal yang sama untuk "page.onResourceRequested = function onResourceRequested (requestData, request) {"

Terakhir, pada "page.onLoadFinished = function onLoadFinished (status) {" pada baris pertama tambahkan:

 if(!reqResFinished)
 {
      return
 }
 reqResFinished = false

Dan hanya itu, semoga yang ini membantu seseorang yang dalam kesulitan seperti saya. Solusi ini untuk casperjs tetapi bekerja langsung untuk Spooky.

Semoga berhasil !


0

ini adalah solusi saya yang berhasil untuk saya.

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

0

Apakah gerakan Mouse saat halaman sedang dimuat seharusnya berfungsi.

 page.sendEvent('click',200, 660);

do { phantom.page.sendEvent('mousemove'); } while (page.loading);

MEMPERBARUI

Saat mengirimkan formulir, tidak ada yang dikembalikan, sehingga program dihentikan. Program tidak menunggu hingga halaman dimuat karena butuh beberapa detik untuk memulai pengalihan.

menyuruhnya untuk menggerakkan mouse sampai URL berubah ke halaman beranda memberi browser waktu sebanyak yang diperlukan untuk mengubahnya. kemudian menyuruhnya untuk menunggu halaman selesai memuat memungkinkan halaman untuk memuat penuh sebelum konten diambil.

page.evaluate(function () {
document.getElementsByClassName('btn btn-primary btn-block')[0].click();
});
do { phantom.page.sendEvent('mousemove'); } while (page.evaluate(function()
{
return document.location != "https://www.bestwaywholesale.co.uk/";
}));
do { phantom.page.sendEvent('mousemove'); } while (page.loading);

Saya khawatir itu tidak akan banyak membantu, tapi terima kasih telah mencoba membantu :)
nilfalse
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.