Puppeteer / Node.js 点击一个按钮,只要它存在——当它不再存在时,开始行动

Puppeteer / Node.js to click a button as long as it exists -- and when it no longer exists, commence action

有一个网页,其中包含许多行不断更新的数据。

行数是固定的,所以旧行会被循环出去,不会存储在任何地方。

此页面由 "load more" 按钮拆分,该按钮会一直显示,直到所有存储的行都显示在页面上。

我需要在 Puppeteer / Node.js 中编写一个脚本,点击那个按钮直到它不再出现在页面上...

然后

...阅读页面上的所有文本。 (我已经完成了这部分脚本。)

我是 Puppeteer 的新手,不知道如何设置。任何帮助将不胜感激。

编辑:

我添加了这个块:

  const cssSelector = await page.evaluate(() => document.cssSelector('.u-field-button Button-button-18U-i'));

  // Click the "load more" button repeatedly until it no longer appears
  const isElementVisible = async (page, cssSelector) => {
    await page.waitForSelector(cssSelector, { visible: true, timeout: 2000 })
    .catch(() => {
      return false;
    });
    return true;
  };

  let loadMoreVisible = await isElementVisible(page, cssSelector);
  while (loadMoreVisible) {
    await page.click(cssSelector);
    loadMoreVisible = await isElementVisible(page, cssSelector);
  }

但是我收到这个错误:

Error: Evaluation failed: TypeError: document.cssSelector is not a function
    at __puppeteer_evaluation_script__:1:17
    at ExecutionContext.evaluateHandle (/Users/reallymemorable/node_modules/puppeteer/lib/ExecutionContext.js:124:13)
    at process.internalTickCallback (internal/process/next_tick.js:77:7)
  -- ASYNC --
    at ExecutionContext.<anonymous> (/Users/reallymemorable/node_modules/puppeteer/lib/helper.js:144:27)
    at ExecutionContext.evaluate (/Users/reallymemorable/node_modules/puppeteer/lib/ExecutionContext.js:58:31)
    at ExecutionContext.<anonymous> (/Users/reallymemorable/node_modules/puppeteer/lib/helper.js:145:23)
    at Frame.evaluate (/Users/reallymemorable/node_modules/puppeteer/lib/FrameManager.js:439:20)
    at process.internalTickCallback (internal/process/next_tick.js:77:7)
  -- ASYNC --
    at Frame.<anonymous> (/Users/reallymemorable/node_modules/puppeteer/lib/helper.js:144:27)
    at Page.evaluate (/Users/reallymemorable/node_modules/puppeteer/lib/Page.js:736:43)
    at Page.<anonymous> (/Users/reallymemorable/node_modules/puppeteer/lib/helper.js:145:23)
    at /Users/reallymemorable/Documents/scripts.scrapers/squarespace.ip.scraper/squarespace5.js:32:34
    at process.internalTickCallback (internal/process/next_tick.js:77:7)
(node:8009) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:8009) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

好的,这就是我建议您实现此目标的方法。我要忽略你的数据总是有固定数量的行(也许这会在未来改变),而是会设置你是否有未知数量的数据行通过不断点击来显示"load more" 按钮。

因此,您要做的第一件事是设置一个方法来确定 "load more" 按钮是否显示在 UI 中。您想通过编写如下方法来做到这一点:

const isElementVisible = async (page, cssSelector) => {
  let visible = true;
  await page
    .waitForSelector(cssSelector, { visible: true, timeout: 2000 })
    .catch(() => {
      visible = false;
    });
  return visible;
};

一旦您传入所需的 css 选择器(在本例中为 "load more" 按钮的选择器),如果显示按钮,此方法将 return true如果不是,则 false

您希望超时设置为 2000,因为您希望持续检查是否显示此按钮。如果未显示,则超时将默认为 30000,这太长了,无法让您的代码等待。所以我发现 2000 是一个很好的折衷方案。 catch 块的目的是捕获当元素不再显示时将抛出的错误 - 你想忽略抛出错误的事实,因为你正试图到达按钮所在的位置不再显示。你知道它不会在 X 次点击后显示。没关系。因此,您需要 catch 错误以在发生这种情况时完全绕过。

那么,下一步就是做这样的事情,让您的代码继续点击 "load more" 按钮,直到它不再可点击(即显示)为止:

let loadMoreVisible = await isElementVisible(page, selectorForLoadMoreButton);
while (loadMoreVisible) {
  await page
    .click(selectorForLoadMoreButton)
    .catch(() => {});
  loadMoreVisible = await isElementVisible(page, selectorForLoadMoreButton);
}

这将持续检查按钮是否在您的 UI 中可见,如果显示则单击它,然后重复该过程直到按钮不再显示。这确保在您继续测试脚本的其余部分之前,所有数据行都将显示在 UI 中。

您还需要在 click 操作上添加一个 catch 块,如上所示。这样做的原因是 headless 模式移动非常快。有时 UI 太快跟不上了。通常,在 "Show More" 按钮的最后一次显示时,isElementVisible 方法将在 UI 更新以消除按钮的存在之前执行,因此它 returns true 实际上,选择器现在不再显示。然后,这会触发 click 请求的异常,因为该元素不再存在。对我来说,解决这个问题的最干净的方法是在 click 指令上添加那个空的 catch 块,这样,如果发生这种情况,click 操作仍然会干净地绕过而不会失败整个测试。

更新一:

您只是错误地使用了 css 选择器。您的选择器应该是:

const cssSelector = '.u-field-button Button-button-18U-i'; // This is your CSS selector for the element

您不需要为此使用 evaluate 方法。

更新二:

好的,我添加了一些改进,我在几个不同的站点上广泛测试了这段代码,发现我自己的逻辑对于 "one size fits all" 方法来点击这类按钮,所以这可能就是你得到这些例外的原因。我已经用所有更改更新了我的原始答案。

快速说明:我已经更新了 isElementVisible 方法 while 循环。

希望对您有所帮助!