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
循环。
希望对您有所帮助!
有一个网页,其中包含许多行不断更新的数据。
行数是固定的,所以旧行会被循环出去,不会存储在任何地方。
此页面由 "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
循环。
希望对您有所帮助!