使用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();
}
});
答案 0 :(得分:9)
使用 page.evaluate
执行JS:
const puppeteer = require('puppeteer');
puppeteer.launch().then(async browser => {
const page = await browser.newPage();
await page.evaluate(() => {
let elements = document.getElementsByClassName('showGoals');
for (let element of elements)
element.click();
});
// browser.close();
});
答案 1 :(得分:9)
for
循环中与Array.map()/Array.forEach()
迭代木偶异步方法由于所有伪操纵者方法都是异步的,因此我们如何遍历它们都无关紧要。我对最常用和最常用的选项进行了比较和评分。
为此,我创建了一个React.Js示例页面,其中包含很多React按钮here(我简称为很多React按钮)。在这里(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
可能会花费一些时间。
通常,如果我们只想在迭代中执行诸如elementHandle.click
之类的异步方法,但又不想返回一个新数组:使用Array.map
是一种不好的做法。 Map方法的执行将在所有迭代器完全执行之前完成,因为Array迭代方法是同步执行迭代器的,但是puppeteer方法的迭代器是:异步的。
const elHandleArray = await page.$$('button')
elHandleArray.map(async el => {
await el.click()
})
await page.screenshot({ path: 'clicks_map.png' })
await browser.close()
持续时间:891毫秒
通过以正常模式观看浏览器,看起来好像可以正常运行,但是如果我们检查page.screenshot
发生的时间:我们可以看到点击仍在进行中。这是由于默认情况下Array.map
无法等待。幸运的是,脚本有足够的时间来解决所有元素上的所有单击,直到关闭浏览器为止。
持续时间:6868毫秒
如果增加同一选择器的元素数量,则会遇到以下错误:
UnhandledPromiseRejectionWarning: Error: Node is either not visible or not an HTMLElement
,因为我们已经到达await page.screenshot()
和await browser.close()
:浏览器已经关闭时,异步点击仍在进行中。
所有迭代器都将被执行,但是forEach将在所有迭代器完成执行之前返回,这在许多情况下对于异步函数而言并不是理想的行为。就伪操纵者而言,它与Array.map
非常相似,除了:Array.forEach
不会返回新数组。
const elHandleArray = await page.$$('button')
elHandleArray.forEach(async el => {
await element.click()
})
await page.screenshot({ path: 'clicks_foreach.png' })
await browser.close()
持续时间:1058毫秒
通过以头顶模式观看浏览器似乎可以正常工作,但是如果我们检查page.screenshot
发生的时间:我们可以看到点击仍在进行中。
持续时间:5111毫秒
如果使用相同的选择器增加元素数量,则会遇到以下错误:
UnhandledPromiseRejectionWarning: Error: Node is either not visible or not an HTMLElement
,因为我们已经到达await page.screenshot()
和await browser.close()
:浏览器已经关闭时,异步点击仍在进行中。
效果最好的解决方案是 bide 的answer的略微修改版本。 page。$$ eval (page.$$eval(selector, pageFunction[, ...args])
)在页面内运行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()
持续时间:711毫秒
通过以头顶模式观看浏览器,我们看到效果是立竿见影的,并且仅在单击每个元素,解决了所有诺言之后,才截取屏幕截图。
持续时间:3445 ms
就像132个按钮一样工作,非常快。
最简单的选项,不是那么快,而是按顺序执行。直到循环未完成,脚本才会转到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()
持续时间:2957毫秒
通过以正常模式观看浏览器,我们可以看到页面单击是按严格顺序进行的,并且仅在单击每个元素之后才截取屏幕截图。
持续时间:25396毫秒
就像使用132个按钮一样工作(但是需要更多时间)。
Array.map
,而应使用forEach或for-of。 ❌Array.forEach
是一个选项,但是您需要包装它,以便下一个异步方法仅在将所有promise在forEach内部解析后才开始。 ❌Array.forEach
与$$eval
组合在一起以获得最佳性能。 ✅for
/ for...of
循环。 ✅答案 2 :(得分:4)
您可以使用page.$$()
根据给定的选择器创建一个ElementHandle
数组,然后可以使用elementHandle.click()
单击每个元素:
const elements = await page.$$('a.showGoals');
elements.forEach(async element => {
await element.click();
});
注意:请记住
await
async
函数中的点击。否则,您将收到以下错误:SyntaxError:等待仅在异步功能中有效
答案 3 :(得分:0)
要获取所有元素,您应该使用page.$$
方法,该方法与来自必需的浏览器API的[...document.querySelectorAll]
(分布在数组中)相同。
然后,您可以遍历它(针对您想要的内容进行映射)并评估每个链接:
const getThemAll = await page.$$('a.showGoals')
getThemAll.map(link => {
await page.evaluate(() => link.click())
})
由于您还想对所得到的东西进行操作,因此建议您使用page.$$eval
,该操作与上面的操作相同,然后对数组中的每个元素一行一行地运行评估函数。例如:
const clickThemAll = await page.$$eval('a.showGoals', links => link.map(link => link.click())
为了更好地解释上面的行,$$eval
返回一个链接数组,然后执行一个以链接为参数的函数,然后通过map方法遍历每个链接。
也检查official documentation,那里有很好的例子。