Puppeteer网页爬虫偶发FrameManager断言报错问题求助
问题根因
你遇到的_FrameManager_onFrameAttached断言错误是Puppeteer与Chrome DevTools Protocol(CDP)通信时的竞态问题,触发原因主要有3个:
- 你在循环内反复启停浏览器进程,进程启动/销毁阶段的帧挂载事件时序不稳定,CDP消息收发容易出现「帧已触发挂载但Puppeteer内部未记录对应帧ID」的匹配错误,直接触发断言崩溃
- 页面加载策略用了
networkidle0,要求连续500ms无任何网络请求,目标站点的广告、第三方统计iframe、动态埋点请求很容易打破这个等待条件,同时触发跨iframe加载的时序错乱 - 代码存在显性bug+异常捕获漏洞:按钮选择器缺少闭合的
]符号,且puppeteer.launch逻辑在try/catch块外,启动阶段的崩溃完全无法被捕获,固定时长的waitForTimeout也无法解决时序竞态问题。
修复方案
按优先级依次调整:
- 把浏览器实例移到循环外初始化,整个采集任务只启动/关闭一次浏览器,循环内仅新建/销毁页面,大幅减少进程启停带来的不稳定因素
- 修正错误的CSS选择器,将不完整的按钮选择器补全
- 调整页面加载等待策略,替换严格的
networkidle0为更适配动态站点的组合策略,避免第三方资源阻塞 - 给浏览器启动、页面跳转增加重试逻辑,遇到偶发CDP错误自动重试,不用手动重启脚本
- 扩大异常捕获范围,把浏览器启动逻辑也纳入try/catch覆盖范围
- 元素操作前增加存在性判断,避免操作不存在/已销毁的元素触发帧错误
- 给Puppeteer加稳定启动参数,关闭沙箱、扩展、硬件加速等非必要特性,减少环境干扰
修复后完整代码
import puppeteer from 'puppeteer'; const urls = [ "https://octopart.com/search?q=SI7020-A20-GM1¤cy=USD&specs=0", "https://octopart.com/search?q=RN41N-I%2FRM¤cy=USD&specs=0", "https://octopart.com/search?autosugg_idx=1¤cy=USD&oq=adxl1004&q=adxl1004bcpz&specs=1", "https://octopart.com/search?q=SI7021-A20-GM1¤cy=USD&specs=0" ]; // 带重试的浏览器启动函数 async function launchBrowser(retries = 3) { for (let i = 0; i < retries; i++) { try { return await puppeteer.launch({ headless: false, args: [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-extensions', '--disable-gpu', '--disable-dev-shm-usage' ] }); } catch (e) { if (i === retries - 1) throw e; console.log(`浏览器启动失败,第${i+2}次重试...`); await new Promise(resolve => setTimeout(resolve, 1000)); } } } (async () => { let browser; try { browser = await launchBrowser(); // 全局设置默认超时为30秒 browser.defaultPageTimeout = 30000; for (let i = 0; i < urls.length; i++) { const url = urls[i]; let page; try { page = await browser.newPage(); // 拦截不必要的资源提升加载速度,减少第三方资源干扰 await page.setRequestInterception(true); page.on('request', (req) => { const resourceType = req.resourceType(); if (['image', 'media', 'font', 'stylesheet'].includes(resourceType)) { return req.abort(); } req.continue(); }); await page.goto(url, { waitUntil: ['domcontentloaded', 'networkidle2'], timeout: 30000 }); await page.waitForTimeout(2000); // 修正后的按钮选择器,操作前先判断元素存在 const buttons = await page.$$('button[class="jsx-3623225293"]'); for (const btn of buttons) { try { // 等待元素可点击再操作 await btn.waitForElementState('enabled', {timeout: 2000}); await btn.click(); } catch (e) { // 单个按钮点击失败不中断整个流程 continue; } } await page.waitForTimeout(2000); await page.waitForSelector('tbody', {timeout: 10000}); await page.waitForTimeout(2000); const data = await page.evaluate(() => { const d = new Date(); const date = `${d.getFullYear()}-${d.getMonth()+1}-${d.getDate()}`; const root = Array.from(document.querySelectorAll("tbody > tr")); return root.map(component => { const priceEl = component.querySelector("td:nth-child(8)"); const authEl = component.querySelector("td:nth-child(1) > a"); const linkEl = component.querySelector("td:nth-child(2) > div > a"); const distEl = component.querySelector("td:nth-child(2)"); const stockEl = component.querySelector("td:nth-child(4)"); return { distributor: distEl?.innerText || "missing", link: linkEl?.href || "missing", stock: stockEl?.innerText || "missing", price: priceEl?.innerText || "missing", date: date, autorized: authEl?.title || "missing" } }); }); console.log(`第${i+1}个链接采集完成,数据量:${data.length}`, data); } catch (error) { console.log(`第${i+1}个链接采集失败:`, error.message); } finally { if (page) await page.close(); } } } catch (error) { console.log('全局任务错误:', error); } finally { if (browser) await browser.close(); } })();
额外优化说明
- 增加了资源拦截逻辑,屏蔽图片、样式、字体等非必要资源加载,既提升采集速度,也减少第三方iframe、广告资源带来的帧加载异常
- 所有DOM查询增加了可选链
?.判断,避免页面结构变动时查询null值触发JS错误 - 单个页面、单个元素的操作失败不会中断整个采集任务,容错性更高
- 所有等待操作都增加了明确的超时时间,避免无限等待
内容的提问来源于stack exchange,提问作者pada69
相关产品推荐
相关产品推荐

