在异步加载所有内容后捕获页面

Capturing page after all content have asynchronously loaded

我是 PhantomJS 的新手,正在尝试捕获 Trade Me 的主页。到目前为止,这是我的代码:

var page = require('webpage').create();

page.open('http://trademe.co.nz', function () {

  // Checks for bottom div and scrolls down from time to time
  window.setInterval(function() {
      // Checks if there is a div with class=".has-more-items" 
      // (not sure if this is the best way of doing it)
      // var count = page.content.match(/class=".site-footer"/g);
      var footer_visible = page.evaluate(function() {
        return $('.site-footer').is(':visible');
      });

      if(!footer_visible) { // Didn't find
        console.log('Scrolling');
        page.evaluate(function() {
          // Scrolls to the bottom of page
          window.document.body.scrollTop = document.body.scrollHeight;
        });
      }
      else { // Found
        console.log('Found');
        // Do what you want
        window.setTimeout( function() {
            console.log('Capturing');
            page.render('phantom-capture.png', {format: 'png'});
            phantom.exit();
        }, 10000);
      }
  }, 1000); // Number of milliseconds to wait between scrolls

});

有几件事让我感到困惑:

  1. 单词 Scrolling 永远不会打印出来。
  2. 最终达到Found,这个词打印了10次。我认为这是因为它包含在 setInterval 块中,间隔为 1 秒,并且 setTimeout?
  3. 导致等待 10 秒
  4. 页面最终呈现为 PNG 文件,但那些异步加载的面板的内容仍然是空的,并显示 Loading... 消息。

我对这一切都是陌生的,我对 Javascript 的了解很生疏。

您正在 运行遇到一个常见问题,即如何判断网页何时已完全加载。这其实挺难的!很久以前我就这个问题写了一篇博客 post:https://sorcery.smugmug.com/2013/12/17/using-phantomjs-at-scale/(参见问题 #1)这是我对您的代码和问题的反馈:

首先,您无需滚动即可知道页脚是否已加载,如果元素占用 [=62],jQuery 的 :visible 选择器将 return 为真=] 在文档中,而不是在视口内: https://api.jquery.com/visible-selector/ 。一般来说,我也不会使用 PhantomJS 的视口可见性,因为它确实 运行 无头。

其次,根据 PhantomJS,当页面有 'loaded' 时,page.open() 回调将触发。 mostly 表示当它完全加载 HTML 及其所有包含的资产时。但是,这并不意味着异步加载的内容已经加载。

第三,我相信您看到输出 'Found' 十次,因为您正在使用 window.setInterval 检查页脚 使用 window.setTimeout 做渲染。发生的事情是这样的:

  1. PhantomJS 开始加载页面并在加载后调用传递给 page.open() 的回调。
  2. 页脚在加载时可见,因此 footer_visible 为真
  3. 'found' 块 运行 的 第一次 时间。这会在未来 10 秒内设置一个 运行 的函数来呈现页面,然后退出。但是因为它正在使用 window.setTimeout,您的脚本会继续。
  4. 脚本继续,由于您的外部函数设置为每秒 运行,它再次 运行s!它检查页脚,找到它并在 10 秒内设置一个 运行 的函数来呈现页面。它会继续这样做 10 秒。
  5. 10 秒后,设置为呈现页面的第一个函数执行此操作,然后告诉 PhantomJS 退出。这会杀死所有其他设置为在 10 秒内呈现页面的功能。

如果你真的想在页脚在文档中时呈现页面,这里是你的固定代码:

var page = require('webpage').create();

page.open('http://trademe.co.nz', function () {

    window.setInterval(function() {
        var footer_visible = page.evaluate(function() {
            return $('.site-footer').is(':visible');
        });

        if(footer_visible) {
            page.render('phantom-capture.png', {format: 'png'});
            phantom.exit();
        }
    }, 1000);
});

但是,一旦加载了所有内容,这将不会呈现,这是一个 更难的问题。请阅读上面链接的我的博客 post,了解有关如何执行此操作的提示。这是一个非常困难的问题。如果您不想阅读我的博客 post,这里有一个 TLDR;

Through a lot of manual testing and QA we eventually came to a solution where we tracked each and every HTTP request PhantomJS makes and watch every step of the transaction (start, progress, end, failed). Only once every single request has completed (or failed, etc) we start ‘waiting’. We give the page 500ms to either start making more requests or finish adding content to the DOM. After that timeout we assume the page is done.

Ryan Doherty 对为什么 console.log('Scrolling'); 永远不会被调用提供了很好的解释,您自己弄清楚了为什么 Found 被打印了 10 次!

我想谈谈如何处理那些 ajaxified 页面。通常,当你使用这些网站时,你可以找出一个标准来判断页面是否已加载,或者至少是你需要的部分(尽管有时,正如 Ryan 正确指出的那样,这可能非常困难,尤其是如果有页面上有很多外部资源 and/or iframe)。

在这种情况下,我想我们可以在没有 "Loading" 标签时决定页面已加载。所以我们关闭 javascript 并检查这些标签。原来他们是<div class="carousel-loading-card">。这意味着我们只需要等到他们走了。但是要触发它们的加载,我们必须模拟页面滚动。在 PhantomJS 中,您可以 "natively" 通过更改 page.scrollPosition 设置来做到这一点。

var page = require('webpage').create();

// Let's not confuse the target site by our default useragent 
// and native viewport dinemsions of 400x300
page.settings.userAgent = 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:32.0) Gecko/20100101 Firefox/32.0';
page.viewportSize = { width: 1280, height: 1024 };

var totalHeight, scroll = 0;

page.open('http://trademe.co.nz', function(){

    totalHeight = page.evaluate(function(){
        return $(document).height();
    });

    wait();

});

function wait()
{
    var loading = page.evaluate(function(){
        return $(".carousel-loading-card").length;
    });

    if(loading > 0) {

        if(scroll <= totalHeight)
        {
            scroll += 200;

            page.scrollPosition = {
                top: scroll,
                left: 0
            };

            page.render('trademe-' + (new Date()).getTime() + '.jpg');
        }

        console.log(loading + " panels left. Scroll: " + scroll + "px");
        setTimeout(wait, 3000);        

    } else {
        // Restore defaults to make a full page screenshot at the end
        page.scrollPosition = { top: 0, left: 0 };        
        page.render('trademe-ready.png');
        phantom.exit();
    }

}