phantomjs "tam" sayfa yüklemesini beklemiyor


137

Bazı web sayfalarını yüklemek için PhantomJS v1.4.1 kullanıyorum . Sunucu taraflarına erişimim yok, sadece onlara işaret eden bağlantılar alıyorum. Phantom'un eski sürümünü kullanıyorum çünkü bu web sayfalarında Adobe Flash'ı desteklemem gerekiyor.

Sorun, birçok web sitesinin küçük içerik zaman uyumsuzluğunu yüklemesidir ve bu nedenle Phantom'un onLoadFinished geri araması (HTML'de onLoad için analog), her şey hala yüklenmediğinde çok erken tetiklenir. Herkes bir web sayfasının tam olarak yüklenmesini, örneğin reklamlar gibi tüm dinamik içeriğe sahip bir ekran görüntüsü almasını nasıl bekleyebilirim?


3
Sanırım bir cevabı kabul etme zamanı
spartikus

Yanıtlar:


76

Başka bir yaklaşım, normal rasterize.js örneğine göre, render işleminden önce sayfa yüklendikten sonra PhantomJS'den biraz beklemesini istemek , ancak JavaScript'in ek kaynaklar yüklemeyi bitirmesine izin vermek için daha uzun bir zaman aşımı ile:

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
Evet, şu anda bu yaklaşıma bağlı kaldım.
nilfalse

102
Bu korkunç bir çözüm, üzgünüm (PhantomJS'nin hatası!). Tam bir saniye beklerseniz, ancak yüklenmesi 20 ms sürer, tam bir zaman kaybıdır (toplu işleri düşünün) veya bir saniyeden uzun sürerse, yine de başarısız olur. Bu tür verimsizlik ve güvenilmezlik profesyonel iş için dayanılmazdır.
CodeManX

9
Buradaki asıl sorun, javascript'in ne zaman sayfa yüklemeyi bitireceğini ve tarayıcının da bilmediğini bilmemenizdir. Sonsuz döngüde sunucudan bir şeyler yükleyen bazı javascript olan siteyi düşünün. Tarayıcı bakış açısından - javascript yürütme hiç bitmiyor, bu yüzden phantomjs'un bitmiş olduğunu söylemesini istediğiniz an nedir? Bu sorun, zaman aşımı çözümünü beklemek ve en iyisini ummak dışında genel bir durumda çözülemez.
Maxim Galushka

5
Bu 2016 itibariyle hala en iyi çözüm mü? Görünüşe göre bundan daha iyisini yapabiliriz.
Adam Thompson


52

Periyodik olarak kontrol etmeyi tercih ederim document.readyStateDurumu ( https://developer.mozilla.org/en-US/docs/Web/API/document.readyState ). Bu yaklaşım biraz karmaşık olsa da, onPageReadytam işlevli belgeyi kullandığınız iç işlevden emin olabilirsiniz .

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

Ek açıklama:

İç içe kullanma setTimeoutyerinesetIntervalcheckReadyState , yürütülmesi bazı rasgele nedenlerle uzatıldığında "çakışmayı" ve yarış koşullarını önler . setTimeoutvarsayılan 4ms gecikme süresi vardır ( https://stackoverflow.com/a/3580085/1011156 ), böylece etkin yoklama program performansını önemli ölçüde etkilemez.

document.readyState === "complete"belgenin tüm kaynaklarla tamamen yüklü olduğu anlamına gelir ( https://html.spec.whatwg.org/multipage/dom.html#current-document-readiness ).


4
setTimeout vs setInterval hakkındaki yorum harika.
Gal Bracha

1
readyStateyalnızca DOM tamamen yüklendikten sonra tetiklenir, ancak tüm <iframe>öğeler yüklenmeye devam edebilir, böylece orijinal soruyu gerçekten yanıtlamaz
CodingIntrigue 29:15

1
@rgraham İdeal değil, ancak bu rendererlarla çok fazla şey yapabileceğimizi düşünüyorum. Bir şeyin tamamen yüklenip yüklenmediğini bilmeyeceğiniz uç durumlar olacak. İçeriğin bilerek bir veya iki dakika geciktiği bir sayfayı düşünün. Oluşturma işleminin oturmasını ve süresiz olarak beklemesini beklemek mantıksızdır. Aynı şey harici kaynaklardan yüklenen ve yavaş olabilecek içerik için de geçerlidir.
Brandon Elliott

3
Bu, Backbone / Ember / Angular gibi DOM tamamen yüklendikten sonra herhangi bir JavaScript yüklemesini dikkate almaz.
Adam Thompson

1
Benim için hiç çalışmadı. readyState complete tetiklenmiş olabilir, ancak sayfa bu noktada boştu.
Steve Staple

21

Waitfor kombinasyonunu deneyebilir ve örnekleri rasterleştirebilirsiniz:

/**
 * 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
OnLoad ortaya çıktıktan sonra kaynak hala kullanımda olacağından, sunucu push teknolojilerinden herhangi birini kullanan web sayfalarıyla çalışmaz gibi görünüyor.
nilfalse

Herhangi bir sürücü yapın, örn. poltergeist , böyle bir özelliği var mı?
Jared Beck

Tüm html metnini yoklamak ve tanımlı bir anahtar kelime aramak için waitFor kullanmak mümkün müdür? Bunu uygulamaya çalıştım ama yoklama en son indirilen html kaynağına yenilemez gibi görünüyor.
fpdragon

14

Belki de eşzamansız yüklemeyi algılamak için onResourceRequestedve onResourceReceivedgeri çağrıları kullanabilirsiniz. Aşağıda, bu geri aramaları belgelerinden kullanma örneği verilmiştir :

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

Ayrıca, examples/netsniff.jsçalışan bir örneğe bakabilirsiniz .


Ancak bu durumda, aynı anda birden fazla sayfa yüklemek için bir PhantomJS örneğini kullanamıyorum, değil mi?
nilfalse

OnResourceRequested, AJAX / Web Alanları Arası talepler için geçerli midir? Yoksa sadece css, resim vb. İçin mi geçerlidir?
CMCDragonkai

@CMCDragonkai Kendim kullanılır, ancak esas asla bu tüm istekler için de gibi görünüyor. Alıntı:All the resource requests and responses can be sniffed using onResourceRequested and onResourceReceived
Supr

Bu yöntemi büyük ölçekli PhantomJS oluşturma ile kullandım ve oldukça iyi çalışıyor. İstekleri takip etmek ve başarısız veya zaman aşımı olup olmadığını izlemek için çok sayıda akıllıya ihtiyacınız var. Daha fazla bilgi: sorcery.smugmug.com/2013/12/17/using-phantomjs-at-scale
Ryan Doherty

14

İşte tüm kaynak taleplerinin tamamlanmasını bekleyen bir çözüm. Tamamlandığında sayfa içeriğini konsola kaydeder ve oluşturulan sayfanın ekran görüntüsünü oluşturur.

Bu çözüm iyi bir başlangıç ​​noktası olarak hizmet edebilse de, başarısız olduğunu gözlemledim, bu yüzden kesinlikle tam bir çözüm değil!

Kullanırken fazla şansım olmadı document.readyState.

Phantomjs örnekler sayfasında bulunan waitfor.js örneğinden etkilendim .

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

Bir başparmak-up
verdim

İstek dizisinden kaldırmadan önce response.stage öğesinin 'end' değerine eşit olup olmadığını kontrol etmelisiniz, aksi takdirde erken kaldırılabilir.
Reimund

Web sayfanız DOM'u dinamik olarak yüklüyorsa çalışmaz
Buddy

13

Programımda, yüklenip yüklenmediğini değerlendirmek için bazı mantık kullanıyorum: ağ isteğini izlemek, son 200 ms'de yeni bir istek yoksa, ona yüklenirim.

Bunu onLoadFinish () öğesinden sonra kullanın.

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

Bu yaklaşımı bazı durumlarda yararlı buldum:

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

Sayfanın sahibi sizden daha fazla komut dosyası koyun:

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

Bu gerçekten güzel bir çözüm gibi görünüyor, ancak HTML / JavaScript sayfamdan phantomJS'den geçmek için herhangi bir günlük mesajı alamadım ... Tarayıcı konsolunda mesajları mükemmel bir şekilde görebildiğimde onConsoleMessage olayı hiç tetiklenmedi ve neden olduğuna dair bir fikrim yok.
Dirk

1
Sayfa.onConsoleMessage = işlev (msg) {};
Andy Balaam

5

Bu çözümü bir NodeJS uygulamasında yararlı buldum. Sadece umutsuz durumlarda kullanıyorum çünkü tam sayfa yüklemesini beklemek için bir zaman aşımı başlattı.

İkinci argüman, yanıt hazır olduğunda çağrılacak olan geri arama işlevidir.

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

Bu Supr'in cevabının bir uygulamasıdır. Ayrıca Mateusz Charytoniuk'un önerdiği gibi setInterval yerine setTimeout kullanır.

Phantomjs, herhangi bir istek veya yanıt olmadığında 1000 ms içinde çıkacaktır.

// 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

Bu kullandığım kod:

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

Temel olarak, DOM'da belirli bir öğe göründüğünde sayfanın tam indirildiğini bilmeniz gerekir. Yani senaryo bu gerçekleşene kadar bekleyecek.


3

Phantomjs waitfor.jsörneğinin kişisel bir karışımını kullanıyorum .

Bu benim main.jsdosyam:

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

Ve lib/waitFor.jsdosya (bu sadece waifFor()phantomjs waitfor.jsörneğinden işlevin bir kopyası ve yapıştırmasıdır ):

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
}

Bu yöntem eşzamansız değil ama en azından bunları kullanmaya çalışmadan önce tüm kaynakların yüklendiğinden eminim.


2

Bu eski bir soru, ama tam sayfa yükü aradığımdan ancak Spookyjs (casperjs ve phantomjs kullanan) ve çözümümü bulamadığım için, kullanıcı komut dosyası ile aynı yaklaşımla kendi senaryomu yaptım. Bu yaklaşımın yaptığı, belirli bir süre boyunca, sayfa herhangi bir istek almazsa veya başlatmazsa, yürütmeyi sonlandırır.

Casper.js dosyasında (global olarak yüklediyseniz, yol /usr/local/lib/node_modules/casperjs/modules/casper.js gibi bir şey olacaktır) aşağıdaki satırları ekleyin:

Tüm global değişkenlerle dosyanın üst kısmında:

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

Daha sonra, "var page = requir ('web sayfası'). Create ();" aşağıdaki kodu ekleyin:

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

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

Sonra ilk satırdaki "page.onResourceReceived = function onResourceReceived (resource) {" içine şunu ekleyin:

 resetTimeout()

"Page.onResourceRequested = function onResourceRequested (requestData, request) {" için de aynısını yapın

Son olarak, ilk satırdaki "page.onLoadFinished = function onLoadFinished (status) {" üzerinde şunu ekleyin:

 if(!reqResFinished)
 {
      return
 }
 reqResFinished = false

Ve bu kadar, umarım bu benim başım belada olan birine yardım eder. Bu çözüm casperjs içindir ancak doğrudan Spooky için çalışır.

İyi şanslar !


0

benim çözümüm bu benim için çalıştı.

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);
    }
});
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.