设置Puppeteer page.goto重试次数限制时程序挂起问题求助
问题:Puppeteer page.goto重试限制触发后程序挂起
我写了一段用Puppeteer加载网页表单、提交并处理响应的代码。初始加载表单时page.goto偶尔会卡住,推测是网络或网站问题,于是加了重试逻辑,多数时候重试几次能成功,但偶尔会无限重试或卡住(连'retrying...'日志都看不到)。后来加了重试次数限制,结果触发次数限制后程序直接挂起,我预期它会返回[],但实际没反应。
具体问题:给page.goto加重试次数限制后出现异常,按代码逻辑try/catch应该捕获失败,输出'Browser Error'后函数返回,但程序却挂住了。processURL会被用来处理一组URL。
async function processURL(url) { const browser = await puppeteer.launch(...); const page = await browser.newPage(); let results = null; let retrycount = 0; const retry = (fn, ms, retrycount) => new Promise(resolve => { fn() .then(resolve) .catch(() => { if (retrycount++ < 5) { setTimeout(() => { console.log(new Date().toLocaleTimeString('en-GB'), 'retrying...'); retry(fn, ms).then(resolve); }, ms); } else { console.log(new Date().toLocaleTimeString('en-GB'), 'retry failed'); } }); }); //Load url form await retry(() => page.goto(url, { waitUntil: 'networkidle0' }), 500, retrycount); page.on('response', async (msg) => { //process response from form submission results = process_response(msg); }); try { //Fill out form and submit await page.evaluate(() => { document.querySelector("#select").click(); }); await page.click('#button'); //Wait for response, timeout after 60sec retries = 0; while (results === null && retries < 120) { await timeout(500); retries++; } } catch (error) { console.log(new Date().toLocaleTimeString('en-GB'), 'Browser error', error) }; // HANG? await page.close(); await browser.close(); if (results === null) { return []; } return results; }
问题根源
- 重试函数未正确终止Promise:当重试次数耗尽时,只打印日志但未调用
reject,导致await retry(...)一直处于pending状态,程序彻底卡住。 - 重试计数作用域错误:
retry函数的retrycount是参数,递归调用时未传递更新后的计数,导致计数无法正确累加,可能出现无限重试。 - 未捕获加载失败的异常:重试函数未抛出错误,后续代码会继续执行表单提交逻辑,但此时页面根本没加载成功,进一步引发无效操作。
修复后的代码
async function processURL(url) { const browser = await puppeteer.launch(...); const page = await browser.newPage(); let results = null; // 重构重试函数:支持计数传递,次数耗尽时主动抛出错误 const retry = async (fn, ms, maxRetries, currentRetry = 0) => { try { return await fn(); } catch (err) { if (currentRetry < maxRetries) { console.log(new Date().toLocaleTimeString('en-GB'), `retrying... (${currentRetry + 1}/${maxRetries})`); await new Promise(resolve => setTimeout(resolve, ms)); return retry(fn, ms, maxRetries, currentRetry + 1); } else { console.log(new Date().toLocaleTimeString('en-GB'), 'retry failed'); throw new Error('Max retries reached for page.goto'); } } }; try { // 加载页面,最多重试5次 await retry(() => page.goto(url, { waitUntil: 'networkidle0' }), 500, 5); page.on('response', (msg) => { // 处理响应(若process_response是异步函数,再添加async) results = process_response(msg); }); // 填写并提交表单 await page.evaluate(() => { document.querySelector("#select").click(); }); await page.click('#button'); // 等待响应,超时60秒 let retries = 0; while (results === null && retries < 120) { await new Promise(resolve => setTimeout(resolve, 500)); retries++; } } catch (error) { console.log(new Date().toLocaleTimeString('en-GB'), 'Browser error', error); } finally { // 确保无论成功失败,都关闭页面和浏览器,避免资源泄漏 await page.close(); await browser.close(); } return results === null ? [] : results; }
关键修复点
- 改用async/await风格重试函数:避免Promise嵌套陷阱,逻辑更直观,次数耗尽时主动抛出错误,让外层
try/catch能捕获。 - 正确传递重试计数:通过
currentRetry参数跟踪当前重试次数,确保计数准确累加。 - 添加finally块:强制关闭页面和浏览器,防止资源泄漏。
- 移除冗余的async响应监听:若
process_response是同步函数,无需在response事件中使用async,避免不必要的Promise。 - 显式抛出重试失败错误:让外层捕获加载失败的情况,直接进入catch块处理,不会执行后续无效的表单操作。
内容的提问来源于stack exchange,提问作者eng3
相关产品推荐
相关产品推荐

