使用 Puppeteer,我想获取页面上具有特定类名的所有元素,然后遍历并单击每个元素。
使用 jQuery,我可以通过以下方式实现:
var elements = $("a.showGoals").toArray();
for (i = 0; i < elements.length; i++) {
$(elements[i]).click();
}
我将如何使用 Puppeteer 实现这一目标?
更新
在下面尝试了 Chridam 的回答,但我无法使其正常工作(尽管答案很有帮助,所以在此表示感谢),所以我尝试了以下方法并且有效:
await page.evaluate(() => {
let elements = $('a.showGoals').toArray();
for (i = 0; i < elements.length; i++) {
$(elements[i]).click();
}
});
原文由 Richlewis 发布,翻译遵循 CC BY-SA 4.0 许可协议
在
for
循环与Array.map()/Array.forEach()
由于所有 puppeteer 方法都是异步的,因此我们如何迭代它们并不重要。我对最常推荐和使用的选项进行了比较和评级。
为此,我在 这里 创建了一个包含大量 React 按钮的 React.Js 示例页面(我将其称为 Lot Of React Buttons )。这里 (1) 我们可以设置在页面上渲染多少个按钮; (2) 我们可以通过点击激活黑色按钮变成绿色。我认为它是与 OP 相同的用例,它也是浏览器自动化的一般情况(如果我们在页面上做某事,我们期望会发生什么)。假设我们的用例是:
有一种保守且相当极端的情况。单击
no. = 132
按钮不是一项巨大的 CPU 任务,no. = 1320
可能需要一些时间。一、Array.map
一般来说,如果我们只想在迭代中执行像
elementHandle.click
这样的异步方法,但我们不想返回一个新数组:使用Array.map
是一种不好的做法。 Map 方法执行将在所有迭代器完全执行之前完成,因为 Array 迭代方法同步执行迭代器,但是 puppeteer 方法,迭代器是:异步的。代码示例
特产
132 个按钮场景结果:❌
持续时间:891 毫秒
通过在 headful 模式下观察浏览器,它看起来好像可以正常工作,但如果我们检查
page.screenshot
发生:我们可以看到点击仍在进行中。这是因为默认情况下无法等待Array.map
。幸运的是脚本有足够的时间来解决对所有元素的所有点击,直到浏览器没有关闭。1320 个按钮场景结果:❌
持续时间:6868 毫秒
如果我们增加同一选择器的元素数量,我们将遇到以下错误:
UnhandledPromiseRejectionWarning: Error: Node is either not visible or not an HTMLElement
,因为我们已经达到了await page.screenshot()
和await browser.close()
:2ed—点击浏览器已经关闭时仍在进行中。二。数组.forEach
所有迭代器都将被执行,但 forEach 将在所有迭代器完成执行之前返回,这在许多异步函数的情况下不是理想的行为。就木偶而言,它与
Array.map
非常相似,除了:对于Array.forEach
不返回新数组。代码示例
特产
132 个按钮场景结果:❌
持续时间:1058 毫秒
通过在 headful 模式下观察浏览器,它看起来像工作,但如果我们检查
page.screenshot
发生的时间:我们可以看到点击仍在进行中。1320 个按钮场景结果:❌
持续时间:5111 毫秒
如果我们增加具有相同选择器的元素数量,我们将遇到以下错误:
UnhandledPromiseRejectionWarning: Error: Node is either not visible or not an HTMLElement
,因为我们已经达到await page.screenshot()
和await browser.close()
的点击:浏览器已经关闭时仍在进行中。三、 page.$$eval + forEach
性能最佳的解决方案是对 bside 的 回答 稍加修改的版本。 page.$$eval (
page.$$eval(selector, pageFunction[, ...args])
) 在页面内运行Array.from(document.querySelectorAll(selector))
并将其作为第一个参数传递给pageFunction
它作为 forEach 的包装器,因此可以完美地等待它。代码示例
特产
132 个按钮场景结果:✅
持续时间:711 毫秒
通过以 headful 模式观察浏览器,我们看到效果是立竿见影的,而且只有在每个元素都被点击、每个 promise 都被解决后才会截取屏幕截图。
1320 个按钮场景结果:✅
持续时间:3445 毫秒
就像 132 个按钮一样工作,速度非常快。
四、 for…of 循环
最简单的选项,不是那么快并且按顺序执行。在循环未完成之前,脚本不会转到
page.screenshot
。代码示例
特产
132 个按钮场景结果:✅
持续时间:2957 毫秒
通过以 headful 模式观察浏览器,我们可以看到页面点击是按严格顺序进行的,而且只有在每个元素都被点击后才会截取屏幕截图。
1320 个按钮场景结果:✅
持续时间:25 396 毫秒
就像 132 个按钮的情况一样工作(但需要更多时间)。
概括
Array.map
如果您只想执行异步事件而不使用返回的数组,请改用 forEach 或 for-of。 ❌Array.forEach
是一个选项,但您需要包装它,以便下一个异步方法仅在 forEach 内解决所有承诺后才启动。 ❌Array.forEach
与$$eval
结合以获得最佳性能。 ✅for
/for...of
循环如果速度不重要并且异步事件的顺序在迭代中确实很重要。 ✅来源/推荐材料