2023年1月30日
这个问题出现的场景是我想要把puppeteer文档转为pdf文档方便阅读和做笔记,所以程序思路是:
前往这个网页
获取到目录上的所有链接
循环前往链接对应的网页并保存为pdf
但当程序运行时却出现错误:

根据经验就是querySelectorAll前面的dom没有找到,然后我通过设置headless: false取消无头模式来查看程序运行的具体过程,结果发现一打开网页出现的并不是目标的网页内容,而是:

所以我就需要等到真正的网页出现再执行核心的程序。
我首先想到的是page.waitForNavigation,因为之前我接触了解过:

await page.goto(basePage);
console.log(1)
await page.waitForNavigation({ waitUntil: "networkidle0" });
console.log(2)
结果发现出现了真正的内容,确实没有报错,但当内容出现后程序却停住了,最后超时报错:

然后我就尝试改变waitUntil的参数,但结果都一样。
然后我又想page.waitForNavigation是否是这样用的,根据网上查阅Are waitUntil of page.goto and page.waitForNavigation the same?发现:
Usually waitForNavigation is used with a click, where clicking might cause a navigation in the browser.
而且puppeteer文档里的示例就是用的click:
const [response] = await Promise.all([
page.waitForNavigation(), // The promise resolves after navigation has finished
page.click('a.my-link'), // 点击该链接将间接导致导航(跳转)
]);
然后我又运行了程序,在程序停止时随便点击网页上的一个链接,然后程序又正常运行了:

我想page.waitForNavigation应该是等待网页上的Navigation的触发,可以简单认为是网址改变,然后等待网址改变后网站加载到什么程度就是设置的waitUntil了。然而我的代码是在page.goto后执行page.waitForNavigation,这时并不存在navigation,所以由于是await就一直等待(测试如果没有await就会出现最开始的querySelectorAll的错误)直到超时报错。
那么我们还要再考虑使用page.waitForNavigation吗?我们就要分析是否有使用的场景。可以知道首先前往网站,然后有一个缓冲的过程,紧接着在相同的网址下加载出真正的内容。同时page.waitForNavigation并没有与链接有关的选项,那么就只能是网页上的navigation来触发,但本项目的过程中没有这个navigation过程而是等待这个链接加载完成。同时通过Are waitUntil of page.goto and page.waitForNavigation the same?可以了解到page.goto可以通过waitUntil来设置满足什么条件认为页面跳转完成,而我们的需要正好就是那个缓冲过程结束就是完成了。

于是我采用了networkidle0,等待所有请求结束:
await page.goto(basePage, { waitUntil: 'networkidle0' });
结果满足需求。但我此时又有一个想法:puppeteer可以等到某个元素出现再执行相关的程序吗?
我想要的就是等到网站真正内容中的目录元素出现,也就是querySelectorAll的dom出现后再执行,那么就不会报出最开始的错误了。
通过查阅文档Please give us a few seconds to download Puppeteer releases for the first time. Next time we'll do i发现有相关的方法——page.waitForSelector(selector[, options])

于是我以querySelectorAll的dom为选择器:
await page.goto(basePage);
await page.waitForSelector("sidebar-component");
结果也能满足需求。最终我选择采用page.waitForSelector,我觉得它更加准确和适合这个场景。
还有一个方法就是设置一个等待时间,在这个时间内缓冲过程结束而真正的内容出现:
await page.goto(basePage);
await new Promise(r => setTimeout(r, 6000));
但缺点很大,就是该设置什么时间呢?也许在某个时刻自己的机器和网络情况下适用,但任一条件改变后都可能失效。你可能会想设置一个很长的时间就是了,但还是有可能不够长,同时效率也很低,所以不是一个很好的方法。