按类名称收集元素,然后单击每个元素 - Puppeteer

2024-04-18

使用 Puppeteer,我想获取页面上具有特定类名的所有元素,然后循环并单击每个元素。

使用 jQuery,我可以通过以下方式实现此目的:

var elements = $("a.showGoals").toArray();

for (i = 0; i < elements.length; i++) {
  $(elements[i]).click();
}

我如何使用 Puppeteer 来实现这一目标?

Update

尝试了下面 Chridam 的答案,但我无法让它发挥作用(尽管答案很有帮助,所以谢谢),所以我尝试了以下方法,这有效:

 await page.evaluate(() => {
   let elements = $('a.showGoals').toArray();
   for (i = 0; i < elements.length; i++) {
     $(elements[i]).click();
   }
});

迭代 puppeteer 异步方法for循环对比Array.map()/Array.forEach()

由于所有 puppeteer 方法都是异步的,因此我们如何迭代它们并不重要。我对最常推荐和使用的选项进行了比较和评级。

为此,我创建了一个包含大量 React 按钮的 React.Js 示例页面here https://thedavidbarton.github.io/react-examples/#/lot-of-react-buttons(我只是称之为很多反应按钮). Here (1)我们可以设置在页面上呈现多少个按钮;(2)我们可以通过单击黑色按钮将其激活为绿色。我认为它是与 OP 相同的用例,而且它也是浏览器自动化的一般情况(如果我们在页面上执行某些操作,我们预计会发生一些事情)。 假设我们的用例是:

Scenario outline: click all the buttons with the same selector
  Given I have <no.> black buttons on the page
  When I click on all of them
  Then I should have <no.> green buttons on the page

有一种保守的情况,也有一种相当极端的情况。点击no. = 132按钮并不是一个巨大的CPU任务,no. = 1320可能需要一些时间。


一、Array.map

一般来说,如果我们只想执行异步方法,例如在迭代中,但我们不想返回一个新数组:使用它是一个不好的做法Array.map。 Map 方法的执行将在所有 iteratee 执行完毕之前完成,因为 Array 迭代方法同步执行 iteratee,但 puppeteer 方法、iteratee 是:异步的。

代码示例

const elHandleArray = await page.$$('button')

elHandleArray.map(async el => {
  await el.click()
})

await page.screenshot({ path: 'clicks_map.png' })
await browser.close()

特产

  • 返回另一个数组
  • .map 方法内并行执行
  • fast

132 个按钮场景结果:❌

持续时间:891 毫秒

通过在 headful 模式下观察浏览器,它看起来好像可以工作,但是如果我们检查何时page.screenshot发生了:我们可以看到点击仍在进行中。这是由于以下事实Array.map默认情况下不能等待。幸运的是,脚本有足够的时间来解析所有元素上的所有点击,直到浏览器没有关闭。

1320 个按钮场景结果:❌

持续时间:6868 毫秒

如果我们增加同一选择器的元素数量,我们将遇到以下错误:UnhandledPromiseRejectionWarning: Error: Node is either not visible or not an HTMLElement,因为我们已经达到了await page.screenshot() and await browser.close():浏览器已关闭时,异步点击仍在进行中。


二. Array.forEach

所有迭代器都将被执行,但 forEach 将在所有迭代器完成执行之前返回,这在许多情况下对于异步函数来说并不是理想的行为。就傀儡师而言,这是一个非常相似的情况Array.map, 除了Array.forEach不返回新数组。

代码示例

const elHandleArray = await page.$$('button')

elHandleArray.forEach(async el => {
  await el.click()
})

await page.screenshot({ path: 'clicks_foreach.png' })
await browser.close()

特产

  • .forEach 方法内并行执行
  • fast

132 个按钮场景结果:❌

持续时间:1058 毫秒

通过在 headful 模式下观察浏览器,它看起来好像可以工作,但是如果我们检查何时page.screenshot发生了:我们可以看到点击仍在进行中。

1320 个按钮场景结果:❌

持续时间:5111 毫秒

如果我们增加具有相同选择器的元素数量,我们将遇到以下错误:UnhandledPromiseRejectionWarning: Error: Node is either not visible or not an HTMLElement,因为我们已经达到了await page.screenshot() and await browser.close():浏览器已关闭时,异步点击仍在进行中。


三.页面.$$eval + forEach

性能最好的解决方案是稍加修改的版本bside's answer https://stackoverflow.com/a/54712599/12412595. The 页面.$$评估 () runs Array.from(document.querySelectorAll(selector))在页面内并将其作为第一个参数传递给pageFunction。它充当 forEach 的包装器,因此可以完美地等待它。

代码示例

await page.$$eval('button', elHandles => elHandles.forEach(el => el.click()))

await page.screenshot({ path: 'clicks_eval_foreach.png' })
await browser.close()

特产

  • 在 .forEach 方法中使用异步 puppeteer 方法没有副作用
  • .forEach 方法内并行执行
  • 极快

132 个按钮场景结果:✅

持续时间:711 毫秒

通过在 headful 模式下观察浏览器,我们看到效果是立竿见影的,而且只有在单击每个元素、每个承诺都得到解决后才会截取屏幕截图。

1320 个按钮场景结果:✅

持续时间:3445 毫秒

工作原理与 132 个按钮的情况一样,速度极快。


四. for...of 循环

最简单的选项,不是那么快并且按顺序执行。脚本不会去page.screenshot直到循环未完成。

代码示例

const elHandleArray = await page.$$('button')

for (const el of elHandleArray) {
  await el.click()
}

await page.screenshot({ path: 'clicks_for_of.png' })
await browser.close()

特产

  • 乍一看,异步行为按预期工作
  • 循环内按顺序执行
  • slow

132 个按钮场景结果:✅

持续时间:2957 毫秒

通过在 headful 模式下观察浏览器,我们可以看到页面点击是严格按顺序发生的,而且只有在点击每个元素后才会截取屏幕截图。

1320 个按钮场景结果:✅

持续时间:25 396 毫秒

工作原理与 132 个按钮的情况类似(但需要更多时间)。


Summary

  • 避免使用Array.map如果您只想执行异步事件并且不使用返回的数组,请改用 forEach 或 for-of。 ❌
  • Array.forEach是一个选项,但您需要包装它,以便下一个异步方法仅在 forEach 内解决所有 Promise 后才启动。 ❌
  • Combine Array.forEach with $$eval如果异步事件的顺序在迭代内并不重要,则可以获得最佳性能。 ✅
  • Use a for/for...of如果速度并不重要并且异步事件的顺序在迭代中确实很重要,则循环。 ✅

来源/推荐材料

  • 塞巴斯蒂安·肖邦:JavaScript:使用 forEach() 进行异步/等待(codeburst.io) https://codeburst.io/javascript-async-await-with-foreach-b6ba62bbf404
  • 安东尼奥·瓦尔:使用 async/await 时使数组迭代变得容易(中等的) https://medium.com/@antonioval/making-array-iteration-easy-when-using-async-await-6315c3225838
  • 将 async/await 与 forEach 循环结合使用(堆栈溢出) https://stackoverflow.com/questions/37576685/using-async-await-with-a-foreach-loop
  • 使用包含异步等待的数组 foreach 进行等待(堆栈溢出) https://stackoverflow.com/questions/51738684/await-with-array-foreach-containing-async-await
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

按类名称收集元素,然后单击每个元素 - Puppeteer 的相关文章

随机推荐

  • Rails:特定 HTTP 错误代码的自定义行为

    我正在开发一个 RoR 网站 希望单独处理服务器错误 400 404 500 等 另外 由于网站是动态的 我想在 Rails 环境中处理错误 而不是在服务器级别 我想做的一个例子是 当用户遇到无法加载或根本不存在的页面或模板时 向用户提供可
  • 从Excel VBA中的单元格范围中删除重复项

    我正在尝试删除 Excel 2013 VBA 中的重复项 但我收到错误 对象不支持此属性或方法 问题是我没有静态范围可供选择 我想从列标题 abcd 中删除重复项 Cells Find what abcd Activate ActiveCe
  • 检查数组中是否存在值(Laravel 或 Php)

    我有这个数组 list desings ids array hc1wXBL7zCsdfMu dhdsfHddfD otheridshere 使用 die var dump 这个数组返回我 array 2 0 gt hc1wXBL7zCsdf
  • Socket.IO中的跨域连接

    是否可以跨域方式使用Socket IO 如果是这样 怎么办 网络上提到了这种可能性 但没有给出任何代码示例 引用socket io 常见问题解答 http socket io faq Socket IO支持跨域连接吗 当然 在每个浏览器上
  • 创建一个加载器,其中线在中心形成一个圆

    这是我的代码 circle border 1px solid transparent border radius 50 width 100px overflow hidden div7 width 100px height 10px bac
  • 正确使用 zend 框架中的语言

    我有一个带有两个模块 管理和公共 的 Zend 应用程序 对于公共 我有以下插件来解析我的友好 URL class Custom Controller Plugin Initializer extends Zend Controller P
  • seq 和 list 之间的区别

    Clojure 语言中的 seq 和列表有什么区别 list 1 2 3 gt 1 2 3 seq 1 2 3 gt 1 2 3 这两种形式似乎被评估为相同的结果 首先 它们可能看起来相同 但实际上并非如此 class list 1 2 3
  • Android 中的文件浏览

    是否有特定的视图 小部件可用于浏览设备上的文件 或者还有其他已被接受的解决方案吗 搜索对我来说有用的信息很少 不幸的是 没有用于浏览文件的特殊视图 小部件 不过自己写也不是很难 谷歌搜索可以找到许多用于文件浏览的公共资源 意图 总体思路很简
  • Episerver - 为什么 BlockData 不实现 IContent

    有谁知道为什么 BlockData 类不直接实现 IContent 我知道在从数据库检索 BlockData 期间 Castle 创建的代理实现了 IContent 如果 StackOverflow 不适合此类问题 请移走它 EPiServ
  • 如何将内容安全策略与本地主机文件一起使用

    我的页面上出现以下错误 Refused to load the script http 127 0 0 1 35729 livereload js because it violates the following Content Secu
  • 在 JTable 中创建鼠标悬停信息面板?工具提示可能还不够

    我想在鼠标悬停时显示一个信息框JTable细胞使用Java 摇摆 所以有多个部分 如何捕获表格单元格中的鼠标悬停事件 我必须能够设置单元格内容 然后获取其数据 如何将鼠标悬停在该单元格上时显示带有动态服务器数据的面板 框 如何缓存信息面板
  • 在 R 中绘制顺序(时间序列)数据的子集

    我有一个类似于下面的数据框 称为 df 我想绘制这些数据的子集 例如May 2012 to June 2014 我一直在使用绘图函数来绘制整个数据框 但是 当我将其分成绘图的子集时 无论我选择数据的哪一部分 我都会得到相同的绘图 Jan F
  • 在 AngularJS 中发送 FormData 和其他字段

    我有一个表格有两个input text和一个upload 我必须将其发送到服务器 但在将文件与文本连接时遇到一些问题 服务器期望这个答案 title first input text second input file my file pd
  • 如何停止线程 - Qthread

    我必须在按下两个不同的按钮时启动 停止线程 请指出我的代码是否正确 我是否错过了 connect 调用中的某些内容 Problem我面临的是 在我的线程上调用 quit 后 然后我等待我的线程完成 但在线程上调用 wait never re
  • ARM Cortex A8 PMNC 读取在启用后也给出 0.. 有什么想法/建议吗?

    MODULE LICENSE GPL MODULE DESCRIPTION user mode access to performance registers int init arm init void unsigned int valu
  • 当自动为 WIX 安装程序收集文件时,我的目录结构有多灵活?

    请原谅我的无知 我一直在阅读一些书 但还没有准备好尝试任何东西 目前 我们有一个 wxs 文件 该文件无法轻松维护 每当从 SVN 提交 删除新文件时 都会手动添加 删除所有文件 由于在创建新文件和更新 wxs 安装文件之间发生了失误 我们
  • CSS 填充高度的 100%。不同的浏览器尺寸

    我有一个侧边栏 蓝色 设置为向左浮动 我已将高度设置为 100 将 body 和 html 高度设置为 100 它工作正常 问题是 当浏览器小于内容窗格 div 红色 时 侧边栏的高度将变得与浏览器的高度相同 因此 如果我向下滚动 侧边栏比
  • cmake中如何连接字符串

    有没有办法在cmake中连接字符串 我有一个仅包含具有主要方法的 cpp 文件的文件夹 我认为只需在所有 src 文件中使用 foreach 就很容易了 这就是我到目前为止所得到的 project opengl tutorial cmake
  • 将浮点值从大端转换为小端

    是否可以转换float是从大端到小端吗 我有来自 PowerPC 平台的大端值 我通过 TCP 将其发送到 Windows 进程 小端值 这个值是一个float 但是当我memcpy将值转换为 Win32 float 类型 然后调用 byt
  • 按类名称收集元素,然后单击每个元素 - Puppeteer

    使用 Puppeteer 我想获取页面上具有特定类名的所有元素 然后循环并单击每个元素 使用 jQuery 我可以通过以下方式实现此目的 var elements a showGoals toArray for i 0 i lt eleme