Javascript 多个选择器上的木偶演员等待选择器
我有一个傀儡控制一个网站,它有一个查找表单,可以返回一个结果,也可以返回一条“找不到记录”的消息。我怎么知道哪个被退回了? waitForSelector似乎一次只等待一个,而waitForNavigation似乎不起作用,因为它是使用Ajax返回的。 我正在尝试接球,但要想正确接球是很困难的,而且会让一切都慢下来Javascript 多个选择器上的木偶演员等待选择器,javascript,puppeteer,screen-scraping,Javascript,Puppeteer,Screen Scraping,我有一个傀儡控制一个网站,它有一个查找表单,可以返回一个结果,也可以返回一条“找不到记录”的消息。我怎么知道哪个被退回了? waitForSelector似乎一次只等待一个,而waitForNavigation似乎不起作用,因为它是使用Ajax返回的。 我正在尝试接球,但要想正确接球是很困难的,而且会让一切都慢下来 try { await page.waitForSelector(SELECTOR1,{timeout:1000}); } catch(err) { await
try {
await page.waitForSelector(SELECTOR1,{timeout:1000});
}
catch(err) {
await page.waitForSelector(SELECTOR2);
}
使任何元素都存在
您可以同时使用querySelectorAll
和waitFor
来解决此问题。使用带有逗号的所有选择器将返回与任何选择器匹配的所有节点
await page.waitFor(() =>
document.querySelectorAll('Selector1, Selector2, Selector3').length
);
现在只返回
true
如果有某个元素,它不会返回哪个选择器匹配哪个元素 根据Abu Taher博士的建议,我得出以下结论:
// One of these SELECTORs should appear, we don't know which
await page.waitForFunction((sel) => {
return document.querySelectorAll(sel).length;
},{timeout:10000},SELECTOR1 + ", " + SELECTOR2);
// Now see which one appeared:
try {
await page.waitForSelector(SELECTOR1,{timeout:10});
}
catch(err) {
//check for "not found"
let ErrMsg = await page.evaluate((sel) => {
let element = document.querySelector(sel);
return element? element.innerHTML: null;
},SELECTOR2);
if(ErrMsg){
//SELECTOR2 found
}else{
//Neither found, try adjusting timeouts until you never get this...
}
};
//SELECTOR1 found
如果木偶程序方法无法完成请求,它们可能会抛出错误。例如,如果选择器在给定时间段内与任何节点不匹配,page.waitForSelector(选择器[,options])可能会失败 对于某些类型的错误,Puppeter使用特定的错误类。这些类可通过require(“木偶演员/错误”)获得 支持的类列表: 处理超时错误的示例如下:
const {TimeoutError} = require('puppeteer/Errors');
// ...
try {
await page.waitForSelector('.foo');
} catch (e) {
if (e instanceof TimeoutError) {
// Do something if this is a timeout.
}
}
将上面的一些元素组合到一个helper方法中,我构建了一个命令,允许我创建多个可能的选择器结果,并处理第一个要解决的问题
/**
*@typedef{import('puppeter').ElementHandle}puppetereelementhandle
*@typedef{import('puppeter').Page}puppeterpage
*/
/**功能说明
@回调结果处理程序
@异步的
@param{puppetereerementhandle}元素匹配元素
@返回{Promise}可以返回任何内容,将被发送到HandlePossibleResults
*/
/**
*@typedef{Object}possibleoutcom
*@property{string}选择器触发此结果的选择器
*如果存在选择器,则将调用@property{OutcomeHandler}处理程序
*/
/**
*等待木偶演员页面上的多个选择器(结果),并在第一个出现时调用处理程序,
*结果处理程序应该按优先顺序排列,就像存在多个一样,只有第一个出现的处理程序
*将被调用。
*@param{puppeterpage}page puppeter页面对象
*@param{[possibleoutcom]}生成每个可能的选择器,以及要调用的处理程序。
*@returns{Promise}从结果处理程序返回结果
*/
异步函数处理可能的结果(第页,结果)
{
var outcomeSelectors=结果.map(结果=>{
返回结果选择器;
})。加入(‘,’);
返回页面。等待(结果选择器)
.然后(=>{
让等待项=[];
结果。forEach(结果=>{
让我们等待=第页。$(output.selector)
.然后(元素=>{
if(元素){
返回[结果,要素];
}
返回null;
});
等待。推(等待);
});
返回承诺。全部(可等待);
})
。然后(选中=>{
let found=null;
checked.forEach(check=>{
如果(!检查)返回;
如果(发现)返回;
让结果=检查[0];
让元素=检查[1];
设p=output.handler(元素);
发现=p;
});
发现退货;
});
}
我遇到了类似的问题,于是选择了这个简单的解决方案:
helpers.waitForAnySelector = (page, selectors) => new Promise((resolve, reject) => {
let hasFound = false
selectors.forEach(selector => {
page.waitFor(selector)
.then(() => {
if (!hasFound) {
hasFound = true
resolve(selector)
}
})
.catch((error) => {
// console.log('Error while looking up selector ' + selector, error.message)
})
})
})
然后使用它:
const selector = await helpers.waitForAnySelector(page, [
'#inputSmsCode',
'#buttonLogOut'
])
if (selector === '#inputSmsCode') {
// We need to enter the 2FA sms code.
} else if (selector === '#buttonLogOut') {
// We successfully logged in
}
使用
Promise.race()
就像我在下面的代码片段中所做的那样,不要忘记page.waitForSelector()中的{visible:true}
选项
public async enterUsername(username:string) : Promise<void> {
const un = await Promise.race([
this.page.waitForSelector(selector_1, { timeout: 4000, visible: true })
.catch(),
this.page.waitForSelector(selector_2, { timeout: 4000, visible: true })
.catch(),
]);
await un.focus();
await un.type(username);
}
公共异步enterUsername(用户名:字符串):承诺{
const un=等待承诺([
this.page.waitForSelector(选择器_1,{timeout:4000,visible:true})
.catch(),
this.page.waitForSelector(选择器_2,{timeout:4000,visible:true})
.catch(),
]);
等待联合国的关注();
等待联合国类型(用户名);
}
进一步使用Promise.race()
包装它,只需检查索引以了解更多逻辑:
//类型脚本
导出异步函数(承诺:承诺[]):承诺{
const indexedPromises:Array=promises.map((promise,index)=>newpromise((resolve)=>promise.then(()=>resolve(index)));
回报承诺。种族(indexedPromises);
}
//Javascript
导出异步函数(承诺){
const indexedPromises=promises.map((promise,index)=>newpromise((resolve)=>promise.then(()=>resolve(index)));
回报承诺。种族(indexedPromises);
}
用法:
const navOutcome=等待([
page.waitForSelector('SELECTOR1'),
page.waitForSelector('SELECTOR2')
]);
如果(导航输出===0){
//“选择器1”的逻辑
}else if(navigationOutcome==1){
//“选择器2”的逻辑
}
另一种简单的解决方案是从更CSS的角度来解决这个问题waitForSelector
似乎遵循以下步骤。因此,本质上,您可以通过使用逗号来选择多个CSS元素
try {
await page.waitForSelector('.selector1, .selector2',{timeout:1000})
} catch (error) {
// handle error
}
在Puppeter中,您可以简单地使用多个选择器,这些选择器由coma分隔,如下所示:
const foundElement = await page.waitForSelector('.class_1, .class_2');
返回的元素将是页面中找到的第一个元素的elementHandle
下一步,如果您想知道找到了哪个元素,您可以获得类名,如下所示:
const className = await page.evaluate(el => el.className, foundElement);
在您的情况下,类似于此的代码应该可以工作:
const foundElement = await page.waitForSelector([SELECTOR1,SELECTOR2].join(','));
const responseMsg = await page.evaluate(el => el.innerText, foundElement);
if (responseMsg == "No records found"){ // Your code here }
有趣的。。。所以,一旦我知道其中一个在那里,我就可以在很短的超时时间内使用try块了?是的。没错,这是一个绝妙的解决方案。事实上,如果promise.all(或allsetted)是“AND”逻辑的自然解决方案,那么promise.race应该被视为“or”的自然解决方案。