Puppeteer 在表单提交后等待页面加载

125
我使用以下代码提交表单,希望Puppeteer在表单提交后等待页面加载。
await page.click("button[type=submit]");

//how to wait until the new page loads before taking screenshot?
// i don't want this:
// await page.waitFor(1*1000);  //← unwanted workaround
await page.screenshot({path: 'example.png'});

如何使用Puppeteer等待页面加载?
14个回答

127

您可以异步等待导航,以避免在重定向时得到 null

await Promise.all([
    page.click('button[type=submit]'),
    page.waitForNavigation({waitUntil: 'networkidle2'})
]);

如果页面点击已经触发了导航,这将有助于您。


4
这是一个竞态条件。应该使用以下代码替代:let nav = page.waitForNavigation(); await page.click("button[type=submit]"); await nav; - dQw4w9WyXcQ

77
await page.waitForNavigation();

8
这个和 await Promise.all([page.click..., page.waitForNavigation...]); 有什么区别? - Nathan Goings
1
@NathanGoings 在此只有一个Promise需要解决,而Promise.all将会解决多个Promise(不一定按顺序)。 - Mabu

46

根据官方文档,你可以使用以下方法:

page.waitForNavigation(options)

  • options <Object> 导航参数,可能具有以下属性:
    • timeout <number> 最大导航时间(以毫秒为单位),默认为30秒,传递0以禁用超时。可以使用page.setDefaultNavigationTimeout(timeout)方法更改默认值。
    • waitUntil <string|Array<string>> 导航成功的条件,默认为load。给定一个事件字符串数组,只有在所有事件都已触发后才认为导航成功。事件可以是以下之一:
      • load - 当load事件被触发时,认为导航完成。
      • domcontentloaded - 当DOMContentLoaded事件被触发时,认为导航完成。
      • networkidle0 - 当至少有500毫秒没有网络连接时,认为导航完成。
      • networkidle2 - 当不超过2个网络连接持续500毫秒时,认为导航完成。
  • 返回: <Promise<[?Response]>> 承诺将解决主资源响应。在多次重定向的情况下,导航将使用最后一个重定向的响应进行解决。在导航到不同的锚点或由于历史记录API使用而进行导航的情况下,导航将使用null进行解决。

易读性:

你可以使用page.waitForNavigation()等待页面导航:

await page.waitForNavigation();

性能:

由于page.waitForNavigation()page.mainFrame().waitForNavigation()的快捷方式,因此我们可以使用以下代码进行轻微的性能优化:

await page._frameManager._mainFrame.waitForNavigation();

1
我正在尝试等待弹出窗口中的图像加载。有没有可能暂停10秒钟?这些值对我都不起作用。 - chovy
36
性能提示是一种过早优化,对真实世界的性能没有任何实际作用。而且,它更容易在 Puppeteer 升级后出现故障,因为它使用了内部 API。 - gsouf

30

有时甚至使用 await page.waitForNavigation() 仍然会导致 Error: Execution context was destroyed, most likely because of a navigation.

在我的情况下,这是因为页面重定向了多次。 API 表示默认的 waitUntil 选项是 Load - 这要求我在每次重定向(3次)后等待导航。

在我的情况下,只使用一次 page.waitForNavigation 实例和 waitUntil 选项 networkidle2 即可解决问题:

await button.click();

await page.waitForNavigation({waitUntil: 'networkidle2'});

最后,该API建议使用Promise.all来防止竞争条件。 我还没有需要它,但为了完整性而提供

await Promise.all([button.click(), page.waitForNavigation({waitUntil:'networkidle2'})])
如果其他方法都失败了,您可以使用page.waitForSelector,这是在Puppeteer github issue上推荐的方法——或者像我一样,使用page.waitForXPath()

4
重定向时间太长,只有 networkidle0 能正常工作。 - Nathan Goings
3
一年后我回来说,“networkidle0”可能出现神秘的失败情况(心跳、webpack hmr等)。在我的当前脚本中,我使用了“page.waitForSelector”。 - Nathan Goings
您可以将多个选项组合起来,并将每个选项都包装在 try catch 块中,以确保在放弃并记录错误之前已经尝试了所有选项。 - andromeda

14

我建议将page.to放在一个包装器中,并等待所有内容加载完成。

这是我的包装器。

loadUrl: async function (page, url) {
    try {
        await page.goto(url, {
            timeout: 20000,
            waitUntil: ['load', 'domcontentloaded', 'networkidle0', 'networkidle2']
        })
    } catch (error) {
        throw new Error("url " + url + " url not loaded -> " + error)
    }
}

现在你可以使用这个

await loadUrl(page, "https://www.google.com")

等待 networkidle0 不是已经被等待 networkidle2 取代了吗? - rinogo

12

我知道回答有点晚了。对于那些在执行 waitForNavigation 时出现以下异常的人可能会有帮助。

(node:14531) UnhandledPromiseRejectionWarning: TimeoutError: Navigation Timeout Exceeded: 30000ms exceeded at Promise.then (/home/user/nodejs/node_modules/puppeteer/lib/LifecycleWatcher.js:142:21) at -- ASYNC -- at Frame. (/home/user/nodejs/node_modules/puppeteer/lib/helper.js:111:15) at Page.waitForNavigation (/home/user/nodejs/node_modules/puppeteer/lib/Page.js:649:49) at Page. (/home/user/nodejs/node_modules/puppeteer/lib/helper.js:112:23) at /home/user/nodejs/user/puppeteer/example7.js:14:12 at

对我有效的正确代码如下所示。

await page.click('button[id=start]', {waitUntil: 'domcontentloaded'});

同样地,如果你要跳转到一个新页面,代码应该像这样

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

7

以上答案都没有解决我的问题。有时候waitForNavigation就会超时。我使用了另一种解决方法,使用waitForFunction来检查文档是否处于就绪状态。

await page.waitForFunction(() => document.readyState === "complete");

5
await Promise.all([
      page.click(selectors.submit),
      page.waitForNavigation({ waitUntil: 'networkidle0' }),
]);

这应该作为首要选择使用,因为它等待所有网络调用完成,并在500ms内没有超过0个网络调用时假设完成。

你也可以使用

await page.waitForNavigation({ waitUntil: 'load' })

否则,您可以使用

await page.waitForResponse(response => response.ok())

此函数也可用于不同的场合,因为它只允许在所有调用都成功时继续执行,即当所有响应状态均为ok,即(200-299)时。


2
waitUntil: 'load'。 - Thanwa Ch.

2

这对我很有效

Puppeteer版本:19.2.2

page.click(".clickable-selector");
await page.waitForNavigation({ waitUntil: "load" });

注意: 如果您在循环内执行此操作。 (抓取页面1,转到页面2,抓取页面2等等...)
await page.waitForSelector(".clickable-selector", { visible: true });

等待此可点击选择器,然后再在页面上进行其他抓取操作。

2
如果提交表单会打开其他页面,那么您可能只需要等待该页面中的选择器。我经常在使用page.waitForNavigation()时遇到问题,因为它的选项并不能确保我们已经成功导航到另一个页面。
// login page
page.click("#login");
// homepage, after login
page.waitForSelector("#home", {visible: true}); // page.waitForXpath()

当然,您可以增加选择器的等待时间。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接