Puppeteer脚本Block 2失效求助:点击未触发页面直接关闭
Puppeteer Block 2's Click Events Ignored, Page Closes Before Media Requests Are Captured
我写了一个Puppeteer脚本,Block 1能正常获取数据并写入文件,但Block 2的元素点击事件完全没执行——本该在page.on('request')里捕获的media请求根本没出现,脚本跑完Block 1后直接关闭页面,Node.js也没返回任何错误。这是怎么回事?
脚本代码:
async function main() { const browser = await puppeteer.launch({headless: false}); const page = await browser.newPage(); await page.setViewport({width: 1200, height: 720}) await page.goto('https://site.local', { waitUntil: 'networkidle0' }); // wait until page load await page.type("input[name='UserName']", "myusername"); await page.type("input[name='Password']", "mypassworduser"); // click and wait for navigation await Promise.all([ page.click("body > main > button"), page.waitForNavigation({ waitUntil: 'networkidle0' }), ]); await page.goto(urlformation); await page.setRequestInterception(true); await page.on('request', (request) => { if (request.resourceType() === 'media') { var CurrentRequest = request.url(); console.log(CurrentRequest); fs.appendFileSync(fichiernurlaudio, request.url()+"\r\n"); } request.continue(); }); //START BLOC 1 ------------------IT WORK const Titresaudios = await page.evaluate(() => { let names = document.querySelectorAll( "td.cursor.audio" ); let arr = Array.prototype.slice.call(names); let text_arr = []; for (let i = 0; i < arr.length; i += 1) { text_arr.push($vartraited+"\r\n"); } return text_arr; }) fs.appendFileSync(fichiernomaudio, Titresaudios); //END BLOCK 1------------------IT WORK- i got data in my file //START BLOCK 2-------seems to ignore-----------NOT WORKING await page.evaluate(()=>{ let elements = document.querySelectorAll("td.cursor.audio"); elements.forEach((element, index) => { setTimeout(() => { element.click(); }, index * 1000); }) }) //END BLOCK 2---------seems to ignore---------NO WORKING //i should see some console.log in page.on('request' (request) => { but instant close after works of bloc 1 await page.close(); await browser.close(); } main();
问题根源
核心问题是Node.js主流程没有等待Block 2里的异步点击操作完成,就直接执行了page.close()和browser.close():
page.evaluate()里的setTimeout是在浏览器的JavaScript上下文中执行的异步操作,但page.evaluate()本身在完成forEach循环后就立即返回并resolve了——它不会等待内部所有setTimeout触发的点击完成。- 主流程在
page.evaluate()返回后,立刻走到关闭页面的代码,导致浏览器还没来得及触发那些延迟的点击,页面就被销毁了,自然也捕获不到media请求。
另外还有个小细节:page.on('request')是事件监听器注册,不需要加await(它不返回Promise),虽然这不会导致功能故障,但属于冗余代码。
解决方案
我们需要让Node.js主流程等待所有点击操作和对应的media请求完成后,再关闭页面。这里提供两种可行方案:
方案1:在浏览器上下文返回Promise,等待所有点击完成
修改Block 2的page.evaluate(),让它返回一个Promise,这个Promise会在最后一个点击触发并完成后resolve:
//START BLOCK 2-------FIXED await page.evaluate(async ()=>{ let elements = document.querySelectorAll("td.cursor.audio"); // 将forEach转为Promise数组,等待所有延迟点击完成 await Promise.all(Array.from(elements).map((element, index) => { return new Promise(resolve => { setTimeout(() => { element.click(); resolve(); }, index * 1000); }); })); // 额外等待1秒,确保最后一个点击触发的media请求完成(可根据实际情况调整时长) await new Promise(resolve => setTimeout(resolve, 1000)); }) //END BLOCK 2---------FIXED
方案2:在Node.js侧监听media请求,等待所有预期请求完成
如果你知道预期要捕获的media请求数量,可以在Node.js侧设置计数器,等所有请求都捕获到后再继续流程:
// 在注册request监听器前初始化计数器和等待Promise const expectedMediaRequests = (await page.$$eval("td.cursor.audio", els => els.length)); let mediaRequestCount = 0; const mediaRequestsDone = new Promise(resolve => { if (expectedMediaRequests === 0) resolve(); }); page.on('request', (request) => { if (request.resourceType() === 'media') { const currentRequest = request.url(); console.log(currentRequest); fs.appendFileSync(fichiernurlaudio, `${currentRequest}\r\n`); mediaRequestCount++; // 所有预期请求都捕获到后,触发resolve if (mediaRequestCount === expectedMediaRequests) { mediaRequestsDone.resolve(); } } request.continue(); }); // ... Block 1代码保持不变 ... //START BLOCK 2-------FIXED await page.evaluate(()=>{ let elements = document.querySelectorAll("td.cursor.audio"); elements.forEach((element, index) => { setTimeout(() => { element.click(); }, index * 1000); }) }) //END BLOCK 2---------FIXED // 等待所有media请求完成 await mediaRequestsDone; // 额外等待500ms确保请求处理完成 await new Promise(resolve => setTimeout(resolve, 500)); // 现在再关闭页面 await page.close(); await browser.close();
额外优化建议
- 尽量避免使用
fs.appendFileSync这种同步IO操作,它会阻塞Node.js事件循环,建议改用异步的fs.promises.appendFile。 - 注意Block 1里的
$vartraited变量:如果它是Node.js侧的变量,需要通过参数传递到浏览器上下文,比如page.evaluate((vartraited) => { ... }, $vartraited),否则浏览器侧会找不到这个变量。
内容的提问来源于stack exchange,提问作者VeilleurTryToFix
相关产品推荐
相关产品推荐

