You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

设置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;
}

问题根源

  1. 重试函数未正确终止Promise:当重试次数耗尽时,只打印日志但未调用reject,导致await retry(...)一直处于pending状态,程序彻底卡住。
  2. 重试计数作用域错误:retry函数的retrycount是参数,递归调用时未传递更新后的计数,导致计数无法正确累加,可能出现无限重试。
  3. 未捕获加载失败的异常:重试函数未抛出错误,后续代码会继续执行表单提交逻辑,但此时页面根本没加载成功,进一步引发无效操作。

修复后的代码

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 01:07:02