phantomjs가 "전체"페이지로드를 기다리지 않습니다


137

PhantomJS v1.4.1을 사용하여 일부 웹 페이지를로드하고 있습니다. 서버 측에 액세스 할 수 없으며 링크를 가져옵니다. 해당 웹 페이지에서 Adobe Flash를 지원해야하므로 사용되지 않는 Phantom 버전을 사용하고 있습니다.

문제는 많은 웹 사이트가 사소한 콘텐츠를 비동기 적으로로드하는 것이므로 모든 것이 아직로드되지 않은 경우 Phantom의 onLoadFinished 콜백 (HTML의 onLoad에 대한 아날로그)이 너무 일찍 발생하는 이유입니다. 누구나 웹 페이지가 가득 찰 때까지 기다릴 수있는 방법을 제안 할 수 있습니까 (예 : 광고와 같은 모든 동적 콘텐츠가 포함 된 스크린 샷)?


3
나는 대답을 받아 들일 시간이라고 생각합니다
spartikus

답변:


76

또 다른 방법은 일반적인 rasterize.js 예제에 따라 페이지를로드 한 후 PhantomJS에게 페이지로드 후 약간 기다렸다가 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 
    }
});

1
예, 현재이 접근법을 고수했습니다.
nilfalse

102
끔찍한 해결책입니다. 죄송합니다 (PhantomJS의 잘못입니다!). 1 초 동안 기다리지 만로드하는 데 20ms가 걸리면 전체 시간 낭비 (일괄 작업을 고려)이거나 1 초 이상 걸리더라도 여전히 실패합니다. 이러한 비효율 성과 신뢰성은 전문적인 업무에 견딜 수 없습니다.
CodeManX

9
여기서 진짜 문제는 자바 스크립트가 언제 페이지로드를 완료하는지 알 수없고 브라우저도이를 알지 못한다는 것입니다. 무한 루프로 서버에서 무언가를로드하는 자바 스크립트가있는 사이트를 상상해보십시오. 브라우저 관점에서-자바 스크립트 실행은 끝나지 않습니다. 그래서 phantomjs가 완료되었다고 알려주는 순간은 무엇입니까? 시간 초과 솔루션 대기 및 최선의 희망을 제외하고 일반적인 경우에는이 문제를 해결할 수 없습니다.
Maxim Galushka

5
2016 년 현재이 방법이 여전히 최고의 솔루션입니까? 우리가 이것보다 더 잘할 수있을 것 같습니다.
Adam Thompson

6
읽으려는 코드를 제어하는 ​​경우 phantomjs
Andy Smith

52

오히려 주기적으로 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않고 중첩 을 사용 합니다. 기본 지연이 4ms ( https://stackoverflow.com/a/3580085/1011156 )이므로 활성 폴링은 프로그램 성능에 큰 영향을 미치지 않습니다.setIntervalcheckReadyStatesetTimeout

document.readyState === "complete"문서에 모든 자원이 완전히로드되었음을 의미합니다 ( https://html.spec.whatwg.org/multipage/dom.html#current-document-readiness ).


4
setTimeout 대 setInterval에 대한 의견은 훌륭합니다.
Gal Bracha

1
readyStateDOM이 완전히로드 된 후에 만 ​​트리거되지만 모든 <iframe>요소가 여전히로드 될 수 있으므로 원래 질문에 실제로 답변하지 않습니다
CodingIntrigue

1
@rgraham 이상적이지는 않지만이 렌더러로만 할 수 있다고 생각합니다. 무언가가 완전히로드되었는지 알지 못하는 경우가 있습니다. 내용이 의도적으로 1 ~ 2 분 지연된 페이지를 생각해보십시오. 렌더 프로세스가 제자리에 앉아 무한한 시간을 기다리는 것은 부당한 일입니다. 속도가 느릴 수있는 외부 소스에서로드 된 콘텐츠도 마찬가지입니다.
Brandon Elliott

3
이는 Backbone / Ember / Angular와 같이 DOM이 완전히로드 된 후 JavaScript로드를 고려하지 않습니다.
Adam Thompson

1
나를 위해 전혀 작동하지 않았습니다. readyState complete가 제대로 실행되었을 수 있지만이 시점에서 페이지가 비어있었습니다.
Steve Staple

21

대기 및 래스터 화 예제의 조합을 시도해 볼 수 있습니다.

/**
 * 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가 발생한 후에도 리소스가 계속 사용되므로 서버 푸시 기술을 사용하는 웹 페이지에서는 작동하지 않는 것 같습니다.
nilfalse

드라이버를 수행하십시오 (예 : poltergeist , 이와 같은 기능이 있습니까?
Jared Beck

waitFor를 사용하여 전체 HTML 텍스트를 폴링하고 정의 된 키워드를 검색 할 수 있습니까? 이것을 구현하려고 시도했지만 폴링이 최신 다운로드 한 html 소스로 새로 고쳐지지 않는 것 같습니다.
fpdragon

14

onResourceRequestedonResourceReceived콜백 을 사용하여 비동기로드를 감지 할 수 있습니다 . 다음 은 문서에서 콜백을 사용하는 예입니다 .

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실제 예제를 볼 수 있습니다 .


그러나이 경우 한 번에 여러 페이지를로드하기 위해 PhantomJS 인스턴스를 사용할 수 없습니다.
nilfalse

onResourceRequested가 AJAX / Cross Domain 요청에 적용됩니까? 아니면 CSS, 이미지 등에 만 적용됩니까?
CMCDragonkai

@ CMCDragonkai 나는 그것을 직접 사용해 본 적이 없지만 이것을 기반으로 모든 요청을 포함하는 것처럼 보입니다. 인용 :All the resource requests and responses can be sniffed using onResourceRequested and onResourceReceived
Supr

나는이 방법을 대규모 PhantomJS 렌더링과 함께 사용했으며 꽤 잘 작동합니다. 요청을 추적하고 요청이 실패하거나 시간 초과되는지 확인하려면 많은 스마트가 필요합니다. 더 많은 정보 : sorcery.smugmug.com/2013/12/17/using-phantomjs-at-scale
Ryan Doherty

14

다음은 모든 리소스 요청이 완료되기를 기다리는 솔루션입니다. 완료되면 페이지 내용을 콘솔에 기록하고 렌더링 된 페이지의 스크린 샷을 생성합니다.

이 솔루션이 좋은 출발점으로 작용할 수는 있지만 이것이 실패한 것으로 보아 완벽한 솔루션은 아닙니다!

를 사용하여 많은 운이 없었습니다 document.readyState.

나는에 의해 영향을 받았다 waitfor.js의 온 발견 예를 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);
});

엄지 손가락을
올리지 만

requests.request.stage에서 response.stage가 'end'와 같은지 확인해야합니다. 그렇지 않으면 조기에 제거 될 수 있습니다.
Reimund

웹 페이지가 DOM을 동적으로로드하는 경우 작동하지 않습니다.
Buddy

13

내 프로그램에서는로드가 있는지 여부를 판단하기 위해 몇 가지 논리를 사용합니다. 네트워크 요청을보고, 지난 200ms 동안 새로운 요청이 없었 으면 온로드로 처리합니다.

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

이 접근법이 어떤 경우에는 유용하다는 것을 알았습니다.

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

페이지를 소유 한 경우보다 일부 스크립트를 내부에 넣습니다.

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

이것은 정말 좋은 해결 방법처럼 보이지만 HTML / JavaScript 페이지에서 phantomJS를 통과하는 로그 메시지를 얻을 수 없었습니다. 브라우저 콘솔에서 메시지를 완벽하게 볼 수는 있지만 onConsoleMessage 이벤트가 트리거되지 않았습니다. 이유가 없습니다.
Dirk

1
page.onConsoleMessage = function (msg) {};이 필요했습니다.
Andy Balaam

5

이 솔루션이 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);

3

이것은 Supr의 답변을 구현 한 것입니다. 또한 Mateusz Charytoniuk이 제안한대로 setInterval 대신 setTimeout을 사용합니다.

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

3

이것은 내가 사용하는 코드입니다.

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에 나타날 때 페이지가 완전히 다운로드되었음을 알고 있어야합니다. 따라서 스크립트는 이러한 상황이 발생할 때까지 기다립니다.


3

나는 phantomjs waitfor.js예제 의 개인적 혼합을 사용합니다 .

이것은 내 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파일 ( waifFor()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
}

이 방법은 비동기식이 아니지만 적어도 모든 리소스가 사용되기 전에로드되었다고 확신합니다.


2

이것은 오래된 질문이지만 전체 페이지로드를 찾고 있었지만 Spookyjs (casperjs 및 phantomjs를 사용하는)를 찾고 솔루션을 찾지 못했기 때문에 사용자 deemstone과 동일한 접근 방식으로 자체 스크립트를 만들었습니다. 이 접근 방식은 주어진 시간 동안 페이지가 요청을 수신하거나 시작하지 않으면 실행을 종료합니다.

casper.js 파일에서 (전역으로 설치 한 경우 경로는 /usr/local/lib/node_modules/casperjs/modules/casper.js와 같습니다) 다음 행을 추가하십시오.

모든 전역 변수가있는 파일 맨 위에 :

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

그런 다음 "var page = require ( 'webpage'). create ();"바로 뒤에있는 "createPage (casper)"함수 내부 다음 코드를 추가하십시오.

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

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

그런 다음 첫 번째 행의 "page.onResourceReceived = function onResourceReceived (resource) {"안에 다음을 추가하십시오.

 resetTimeout()

"page.onResourceRequested = function onResourceRequested (requestData, request) {"에 대해서도 동일하게 수행하십시오.

마지막으로 첫 번째 줄의 "page.onLoadFinished = function onLoadFinished (status) {"에 다음을 추가하십시오.

 if(!reqResFinished)
 {
      return
 }
 reqResFinished = false

그게 다야,이 사람이 나처럼 곤경에 처한 사람을 돕기를 바랍니다. 이 솔루션은 casperjs 용이지만 Spooky 용으로 직접 작동합니다.

행운을 빕니다 !


0

이것은 내 솔루션으로 나를 위해 일했습니다.

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);
    }
});
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.