【问题标题】:Puppeteer wait until page is completely loadedPuppeteer 等到页面完全加载
【发布时间】:2019-03-01 00:33:13
【问题描述】:

我正在从网页创建 PDF。

我正在处理的应用程序是单页应用程序。

我在https://github.com/GoogleChrome/puppeteer/issues/1412上尝试了很多选项和建议

但它不起作用

    const browser = await puppeteer.launch({
    executablePath: 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
    ignoreHTTPSErrors: true,
    headless: true,
    devtools: false,
    args: ['--no-sandbox', '--disable-setuid-sandbox']
});

const page = await browser.newPage();

await page.goto(fullUrl, {
    waitUntil: 'networkidle2'
});

await page.type('#username', 'scott');
await page.type('#password', 'tiger');

await page.click('#Login_Button');
await page.waitFor(2000);

await page.pdf({
    path: outputFileName,
    displayHeaderFooter: true,
    headerTemplate: '',
    footerTemplate: '',
    printBackground: true,
    format: 'A4'
});

我想要的是在页面完全加载后立即生成 PDF 报告。

我不想写任何类型的延迟,即 await page.waitFor(2000);

我不能做waitForSelector,因为页面有计算后呈现的图表和图形。

我们将不胜感激。

【问题讨论】:

    标签: javascript pdf-generation puppeteer google-chrome-headless


    【解决方案1】:

    您可以使用page.waitForNavigation() 等待新页面完全加载后再生成PDF:

    await page.goto(fullUrl, {
      waitUntil: 'networkidle0',
    });
    
    await page.type('#username', 'scott');
    await page.type('#password', 'tiger');
    
    await page.click('#Login_Button');
    
    await page.waitForNavigation({
      waitUntil: 'networkidle0',
    });
    
    await page.pdf({
      path: outputFileName,
      displayHeaderFooter: true,
      headerTemplate: '',
      footerTemplate: '',
      printBackground: true,
      format: 'A4',
    });
    

    如果您希望将某个动态生成的元素包含在 PDF 中,请考虑使用 page.waitForSelector() 以确保内容可见:

    await page.waitForSelector('#example', {
      visible: true,
    });
    

    【讨论】:

    【解决方案2】:

    有时networkidle 事件并不总是表明页面已完全加载。仍然可能有一些 JS scripts 修改页面上的内容。因此,观看浏览器完成HTML 源代码修改似乎会产生更好的结果。这是您可以使用的功能 -

    const waitTillHTMLRendered = async (page, timeout = 30000) => {
      const checkDurationMsecs = 1000;
      const maxChecks = timeout / checkDurationMsecs;
      let lastHTMLSize = 0;
      let checkCounts = 1;
      let countStableSizeIterations = 0;
      const minStableSizeIterations = 3;
    
      while(checkCounts++ <= maxChecks){
        let html = await page.content();
        let currentHTMLSize = html.length; 
    
        let bodyHTMLSize = await page.evaluate(() => document.body.innerHTML.length);
    
        console.log('last: ', lastHTMLSize, ' <> curr: ', currentHTMLSize, " body html size: ", bodyHTMLSize);
    
        if(lastHTMLSize != 0 && currentHTMLSize == lastHTMLSize) 
          countStableSizeIterations++;
        else 
          countStableSizeIterations = 0; //reset the counter
    
        if(countStableSizeIterations >= minStableSizeIterations) {
          console.log("Page rendered fully..");
          break;
        }
    
        lastHTMLSize = currentHTMLSize;
        await page.waitFor(checkDurationMsecs);
      }  
    };
    

    您可以在页面 load / click 函数调用之后和处理页面内容之前使用它。例如

    await page.goto(url, {'timeout': 10000, 'waitUntil':'load'});
    await waitTillHTMLRendered(page)
    const data = await page.content()
    

    【讨论】:

    • 我不确定为什么这个答案没有得到更多的“爱”。实际上,很多时候我们真的只需要在抓取页面之前确保 JavaScript 完成了对页面的处理。网络事件不能做到这一点,如果你有动态生成的内容,你并不总是可以可靠地在上执行“waitForSelector/visible:true”
    • 谢谢@roberto - 顺便说一句,我刚刚更新了答案,您可以将它与 'load' 事件一起使用,而不是 'networkidle2' 。认为它会更优化。我已经在生产中对此进行了测试,并且可以确认它也可以正常工作!
    • 这是一个很好的解决方案。谢谢分享!
    • 很好的解决方案,应该是 puppeteer 库的一部分,但是请不要 waitFor 已弃用,将在未来的版本中删除:github.com/puppeteer/puppeteer/issues/6214
    • 我尝试了几种解决方案,这是唯一真正完全有效的解决方案。谢谢你,@AnandMahajan
    【解决方案3】:

    在某些情况下,对我来说最好的解决方案是:

    await page.goto(url, { waitUntil: 'domcontentloaded' });
    

    您可以尝试的其他一些选项是:

    await page.goto(url, { waitUntil: 'load' });
    await page.goto(url, { waitUntil: 'domcontentloaded' });
    await page.goto(url, { waitUntil: 'networkidle0' });
    await page.goto(url, { waitUntil: 'networkidle2' });
    

    您可以在 puppeteer 文档中查看此内容: https://pptr.dev/#?product=Puppeteer&version=v11.0.0&show=api-pagewaitfornavigationoptions

    【讨论】:

    • 这并不能确保加载的任何脚本都已完成执行。因此 HTML 仍然可以呈现,这将继续进行。
    • 文档链接已损坏
    • 链接已更新,谢谢@chovy
    【解决方案4】:

    我总是喜欢等待选择器,因为其中许多是页面已完全加载的重要指标:

    await page.waitForSelector('#blue-button');
    

    【讨论】:

    • 你真是个天才,这是一个如此明显的解决方案,尤其是当你在等待特定元素时,我没有猜到自己,谢谢!
    • @Arch4Arts 你应该创建你自己的点击函数来等待你以及点击
    【解决方案5】:

    page.clickpage.waitForNavigation 包装在 Promise.all 中

      await Promise.all([
        page.click('#submit_button'),
        page.waitForNavigation({ waitUntil: 'networkidle0' })
      ]);
    

    【讨论】:

    • page.waitForNavigation({ waitUntil: 'networkidle0' })page .waitForNetworkIdle() 一样吗?
    【解决方案6】:

    在最新的 Puppeteer 版本中,networkidle2 为我工作:

    await page.goto(url, { waitUntil: 'networkidle2' });
    

    【讨论】:

      【解决方案7】:

      您也可以使用来确保所有元素都已渲染

      await page.waitFor('*')
      

      参考:https://github.com/puppeteer/puppeteer/issues/1875

      【讨论】:

      【解决方案8】:

      至于 2020 年 12 月,waitFor 函数已被弃用,正如代码中的警告所示:

      waitFor 已弃用,将在未来的版本中删除。看 https://github.com/puppeteer/puppeteer/issues/6214 了解详情和方法 迁移您的代码。

      你可以使用:

      sleep(millisecondsCount) {
          if (!millisecondsCount) {
              return;
          }
          return new Promise(resolve => setTimeout(resolve, millisecondsCount)).catch();
      }
      

      并使用它:

      (async () => {
          await sleep(1000);
      })();
      

      【讨论】:

      • 只使用 page.waitForTimeout(1000)
      • 会检查的。谢谢。
      • github 问题指出他们刚刚弃用了“神奇”的 waitFor 函数。您仍然可以使用特定的 waitFor*() 函数之一。因此,您的 sleep() 代码是不必要的。 (更不用说它的功能过于复杂了,而且通过编程超时来解决并发问题通常是个坏主意。)
      【解决方案9】:

      我在使用屏幕外渲染器时遇到了与networkidle 相同的问题。我需要一个基于 WebGL 的引擎来完成渲染,然后才能制作屏幕截图。对我有用的是page.waitForFunction() 方法。就我而言,用法如下:

      await page.goto(url);
      await page.waitForFunction("renderingCompleted === true")
      const imageBuffer = await page.screenshot({});
      

      在渲染代码中,我只是在完成后将renderingCompleted 变量设置为true。如果您无权访问页面代码,则可以使用其他现有标识符。

      【讨论】:

        【解决方案10】:

        我不能离开 cmets,但我为任何认为有用的人(即如果他们使用 pyppeteer)制作了 Anand 答案的 python 版本。

        async def waitTillHTMLRendered(page: Page, timeout: int = 30000): 
            check_duration_m_secs = 1000
            max_checks = timeout / check_duration_m_secs
            last_HTML_size = 0
            check_counts = 1
            count_stable_size_iterations = 0
            min_stabe_size_iterations = 3
        
            while check_counts <= max_checks:
                check_counts += 1
                html = await page.content()
                currentHTMLSize = len(html); 
        
                if(last_HTML_size != 0 and currentHTMLSize == last_HTML_size):
                    count_stable_size_iterations += 1
                else:
                    count_stable_size_iterations = 0 # reset the counter
        
                if(count_stable_size_iterations >= min_stabe_size_iterations):
                    break
            
        
                last_HTML_size = currentHTMLSize
                await page.waitFor(check_duration_m_secs)
        

        【讨论】:

          猜你喜欢
          • 2023-03-14
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-05-16
          • 2019-03-17
          • 1970-01-01
          相关资源
          最近更新 更多