81

我正在从网页创建 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 因为页面有计算后呈现的图表和图形。

帮助将不胜感激。

4

10 回答 10

100

在生成 PDF 之前,您可以使用page.waitForNavigation()等待新页面完全加载:

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,
});
于 2018-09-25T15:35:48.250 回答
71

有时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()
于 2020-04-19T12:19:22.523 回答
42

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

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

于 2019-08-27T14:53:08.190 回答
28

我总是喜欢等待选择器,因为它们中的许多是页面已完全加载的一个很好的指标:

await page.waitForSelector('#blue-button');
于 2018-09-25T15:01:32.840 回答
10

page.click将and包裹page.waitForNavigation在 Promise.all 中

  await Promise.all([
    page.click('#submit_button'),
    page.waitForNavigation({ waitUntil: 'networkidle0' })
  ]);
于 2019-02-20T08:02:17.393 回答
8

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

await page.goto(url, { waitUntil: 'networkidle2' });
于 2020-02-06T22:49:29.040 回答
5

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

await page.waitFor('*')

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

于 2020-05-15T08:52:59.427 回答
5

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

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

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

于 2021-05-17T06:56:37.940 回答
5

至于 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);
})();
于 2020-12-09T06:20:06.663 回答
0

我不能发表评论,但我为任何认为有用的人(即如果他们使用 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)
于 2021-11-24T00:04:12.950 回答