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

时间:2018-02-07 21:54:16

标签: javascript node.js puppeteer

使用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();
   }
});

4 个答案:

答案 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 可能会花费一些时间。


I。 Array.map

通常,如果我们只想在迭代中执行诸如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()

特色

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

132个按钮的方案结果:❌

持续时间:891毫秒

通过以正常模式观看浏览器,看起来好像可以正常运行,但是如果我们检查page.screenshot发生的时间:我们可以看到点击仍在进行中。这是由于默认情况下Array.map无法等待。幸运的是,脚本有足够的时间来解决所有元素上的所有单击,直到关闭浏览器为止。

1320按钮的场景结果:❌

持续时间:6868毫秒

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


II。 Array.forEach

所有迭代器都将被执行,但是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()

特色

  • .forEach方法内部的并行执行
  • 快速

132个按钮的方案结果:❌

持续时间:1058毫秒

通过以头顶模式观看浏览器似乎可以正常工作,但是如果我们检查page.screenshot发生的时间:我们可以看到点击仍在进行中。

1320按钮的场景结果:❌

持续时间:5111毫秒

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


III。页面。$$ eval + forEach

效果最好的解决方案是 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()

特色

  • 在.forEach方法中使用异步伪造者方法没有副作用
  • .forEach方法内部的并行执行
  • 极快

132个按钮的方案结果:✅

持续时间:711毫秒

通过以头顶模式观看浏览器,我们看到效果是立竿见影的,并且仅在单击每个元素,解决了所有诺言之后,才截取屏幕截图。

1320按钮的场景结果:✅

持续时间:3445 ms

就像132个按钮一样工作,非常快。


IV。 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()

特色

  • 异步行为按预期的方式工作
  • 在循环内按顺序执行

132个按钮的方案结果:✅

持续时间:2957毫秒

通过以正常模式观看浏览器,我们可以看到页面单击是按严格顺序进行的,并且仅在单击每个元素之后才截取屏幕截图。

1320按钮的场景结果:✅

持续时间:25396毫秒

就像使用132个按钮一样工作(但是需要更多时间)。


摘要

  • 如果只想执行异步事件并且不使用返回的数组,请避免使用Array.map,而应使用forEach或for-of。 ❌
  • Array.forEach是一个选项,但是您需要包装它,以便下一个异步方法仅在将所有promise在forEach内部解析后才开始。 ❌
  • 如果异步事件的顺序在迭代内无关紧要,则将Array.forEach$$eval组合在一起以获得最佳性能。 ✅
  • 如果速度不是至关重要的,并且异步事件的顺序在迭代内很重要,请使用for / for...of循环。 ✅

来源/推荐材料

答案 2 :(得分:4)

page。$$()/ elementHandle.click()

您可以使用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,那里有很好的例子。