如何在Puppeteer并发任务中关闭标签后自动打开新标签?
问题:Puppeteer并发页面处理中手动关闭页面后的自动补位问题
我用Puppeteer实现并发页面处理逻辑时遇到了问题:当前executingFunctions中的页面会持续重载,一旦某页面加载到符合要求的HTML,就会与之交互、关闭标签并打开新标签。现在需要实现:如果手动关闭某个还没满足条件的页面,要自动打开新标签,同时不影响其他页面的重载和任务队列的新增。
相关代码
concurrentOrders.js
export default async function concurrentOrders(limit, links) { const executingFunctions = []; let currentIndex = 0; while (currentIndex < links.length) { while (executingFunctions.length < limit && currentIndex < links.length) { const link = links[currentIndex]; currentIndex++; const promise = createBuyOrderPromise(...link).finally(() => { const index = executingFunctions.indexOf(promise); if (index !== -1) executingFunctions.splice(index, 1); }); executingFunctions.push(promise); } await Promise.race(executingFunctions); } }
createBuyOrderPromise.js
export default async function createBuyOrderPromise(link) { return new Promise(async (resolve, reject) => { try { const page = await makeConnection(); // 通过webSocketUrl连接已有浏览器 await gotoMarketItemPage(page, link); await closeTabPage(page); // 关闭标签页 resolve(); } catch () { reject(); } }); }
gotoMarketItemPage.js
export default async function gotoMarketItemPage(page) { try { await page.goto(page, { waitUntil: "domcontentloaded", timeout: 0, } ); const image = await page.$(itemImageSelector); if (image) elementFound = true; if (!image) await page.reload(); } }
之前的尝试及问题
我曾在gotoMarketItemPage中添加以下代码并传入reject函数:
await page.on("close", () => { reject(); });
但这么做会导致:手动关闭一个未加载完成的页面时,其他正在重载的页面也会停止,而且任务队列不再新增新页面。
解决方案
1. 修正页面关闭事件的监听逻辑
首先,page.on是事件绑定,不需要await,而且要确保页面关闭时只触发当前Promise的reject,不影响其他任务。用once代替on避免重复绑定,同时在任务正常完成后移除监听:
export default async function createBuyOrderPromise(link) { return new Promise(async (resolve, reject) => { let page; let handleClose; try { page = await makeConnection(); // 绑定页面关闭事件,仅触发一次 handleClose = () => reject(new Error("页面被手动关闭")); page.once("close", handleClose); await gotoMarketItemPage(page, link); // 任务正常完成,移除关闭监听 page.off("close", handleClose); await closeTabPage(page); resolve(); } catch (err) { reject(err); } finally { // 确保清理监听,避免内存泄漏 if (page && handleClose) { page.off("close", handleClose); } } }); }
2. 完善页面循环重载逻辑
原代码中elementFound未定义,且没有持续重载的循环逻辑,修改后让页面持续重载直到找到目标元素或被手动关闭:
export default async function gotoMarketItemPage(page, link) { while (true) { try { await page.goto(link, { waitUntil: "domcontentloaded", timeout: 0, }); const image = await page.$(itemImageSelector); if (image) break; // 找到元素,退出循环 // 未找到元素,重载页面 await page.reload({ waitUntil: "domcontentloaded", timeout: 0 }); } catch (err) { // 页面被手动关闭时,抛出错误让上层处理 if (err.message.includes("页面被手动关闭")) throw err; // 其他错误(如网络问题),继续重试 continue; } } }
3. 优化并发任务补充逻辑
修改concurrentOrders的循环条件,确保所有任务(包括异常结束的)都处理完毕,且异常结束的任务会自动被替换:
export default async function concurrentOrders(limit, links) { const executingFunctions = []; let currentIndex = 0; // 持续运行直到所有链接处理完成且无正在执行的任务 while (currentIndex < links.length || executingFunctions.length > 0) { // 补充任务到并发上限 while (executingFunctions.length < limit && currentIndex < links.length) { const link = links[currentIndex]; currentIndex++; const promise = createBuyOrderPromise(link) .finally(() => { const index = executingFunctions.indexOf(promise); if (index !== -1) executingFunctions.splice(index, 1); }); executingFunctions.push(promise); } // 等待任意一个任务完成(无论成功或失败) await Promise.race(executingFunctions); } }
关键说明
- 用
page.once绑定关闭事件,避免重复触发导致的异常。 - 任务正常完成后移除监听,防止后续页面关闭误触发当前Promise的reject。
- 循环重载逻辑确保页面持续尝试直到找到目标元素或被手动关闭。
- 并发逻辑中,无论任务是resolve还是reject,
finally块都会将其从执行队列移除,Promise.race触发后会自动补充新任务,不影响其他正在运行的页面。
内容的提问来源于stack exchange,提问作者Mini elephant
相关产品推荐
相关产品推荐

