Puppeteer报‘Requesting main frame too early!’错误致浏览器关闭的排查与解决
Puppeteer检查登录状态函数的「Requesting main frame too early!」问题
我用Puppeteer写了一个检查用户登录状态的函数:
export async function checkIfUserIsLoggedIn() { const logger = getLogger('checkIfUserIsLoggedIn()'); try { if (!page || !browser) { logger.info('page or browser is not open'); return false; } logger.info('page or browser is open'); await delay(randomWaitTimeSeconds1_2()); // await page.waitForSelector(selector); const btn = await page.$(selector); logger.info(`user is logged in - ${!!btn ? 'YES' : 'NO'}`); return !!btn; } catch (e) { logError('checkIfUserIsLoggedIn()', e); } }
这个函数偶尔会触发「Error: Requesting main frame too early!」错误,完整报错如下:
[2023-10-02T01:58:06.825] [ERROR] checkIfUserIsLoggedIn() - Error: Requesting main frame too early! at assert (C:\Users\simon\Desktop\tb-project-pup\node_modules\puppeteer-core\lib\cjs\puppeteer\util\assert.js:28:15) at FrameManager.mainFrame (C:\Users\simon\Desktop\tb-project-pup\node_modules\puppeteer-core\lib\cjs\puppeteer\cdp\FrameManager.js:219:32) at CdpPage.mainFrame (C:\Users\simon\Desktop\tb-project-pup\node_modules\puppeteer-core\lib\cjs\puppeteer\cdp\Page.js:407:35) at CdpPage.$ (C:\Users\simon\Desktop\tb-project-pup\node_modules\puppeteer-core\lib\cjs\puppeteer\api\Page.js:324:31) at checkIfUserIsLoggedIn (file:///C:/Users/simon/Desktop/tb-project-pup/main/checkIfUserIsLoggedIn.js:19:38)
这个错误会直接导致Puppeteer关闭浏览器,但其他错误比如TimeoutError、ProtocolError不会触发浏览器关闭。我推测是页面内置定时器触发的自动重载(带?reload=auto参数)导致的,这种情况很少发生。
我想搞清楚两个问题:
- 为什么这个错误会导致浏览器关闭?
- 怎么正确解决这个问题?
我自己考虑过两个方案:
- 在
const btn = await page.$(selector);前加await page.waitForNavigation({ waitUntil: 'domcontentloaded', });,但页面重载间隔可能超过2分钟,这样等太久不现实; - 加重试逻辑,比如下面这段代码,但不确定要不要保留原来的try/catch:
const anchorElement = await page.$(selector).catch(async () => { retryCount++; if (retryCount < 3) { await checkIfUserIsLoggedIn(); // OR await page.$(selector) } });
问题解答
1. 为什么这个错误会导致浏览器关闭?
这个错误来自Puppeteer内部的断言失败(assert函数抛出的错误)。当页面正在重载时,FrameManager还没完成主框架的初始化,此时调用page.$()会触发主框架获取操作,触发断言失败。这类内部断言错误属于Puppeteer认为的严重状态异常,会导致CDP(Chrome DevTools Protocol)连接中断,进而触发浏览器进程关闭——不同于普通的API错误(比如超时、协议错误),内部断言失败意味着Puppeteer的状态已经和浏览器不同步,无法继续正常交互,所以会直接关闭浏览器。
2. 正确的解决方案
推荐采用带重试的异常捕获+等待页面稳定的组合方案,同时保留原有try/catch来处理其他错误,针对特定错误做重试:
优化后的代码
export async function checkIfUserIsLoggedIn(retryCount = 0) { const logger = getLogger('checkIfUserIsLoggedIn()'); const MAX_RETRIES = 3; try { if (!page || !browser) { logger.info('page or browser is not open'); return false; } // 先检查页面是否处于导航状态,避免在重载时操作 if (page.isNavigationPending()) { logger.info('Page is navigating, waiting for completion...'); // 等待导航完成,设置合理超时(比如10秒,比正常重载时间短) await page.waitForNavigation({ waitUntil: 'domcontentloaded', timeout: 10000 }).catch(() => { logger.info('Navigation timed out, proceeding with check'); }); } logger.info('page or browser is open'); await delay(randomWaitTimeSeconds1_2()); const btn = await page.$(selector); logger.info(`user is logged in - ${!!btn ? 'YES' : 'NO'}`); return !!btn; } catch (e) { logError('checkIfUserIsLoggedIn()', e); // 针对特定错误做重试 if (e.message.includes('Requesting main frame too early!') && retryCount < MAX_RETRIES) { logger.info(`Retrying login check (${retryCount + 1}/${MAX_RETRIES})...`); // 等待一小段时间让页面完成重载初始化 await delay(1000); return checkIfUserIsLoggedIn(retryCount + 1); } // 其他错误直接返回false或抛出(根据业务需求) return false; } }
方案说明
- 保留原有try/catch:用来统一捕获所有错误,同时针对特定的「Requesting main frame too early!」错误做重试逻辑,其他错误按原有逻辑处理;
- 检查导航状态:通过
page.isNavigationPending()提前判断页面是否正在重载,避免在不稳定状态下执行DOM查询; - 有限重试:设置最大重试次数(比如3次),避免无限循环,每次重试前等待1秒让页面完成主框架初始化;
- 合理超时的导航等待:如果页面正在导航,等待10秒超时,避免因为超长重载时间阻塞流程。
另外,也可以考虑监听页面的framenavigated事件,提前感知页面重载,在重载完成后再执行登录状态检查,但对于偶尔发生的自动重载,上述重试方案已经足够简洁有效。
内容的提问来源于stack exchange,提问作者Adrian Kato
相关产品推荐
相关产品推荐

