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

Puppeteer网页爬虫偶发FrameManager断言报错问题求助

问题根因

你遇到的_FrameManager_onFrameAttached断言错误是Puppeteer与Chrome DevTools Protocol(CDP)通信时的竞态问题,触发原因主要有3个:

  1. 你在循环内反复启停浏览器进程,进程启动/销毁阶段的帧挂载事件时序不稳定,CDP消息收发容易出现「帧已触发挂载但Puppeteer内部未记录对应帧ID」的匹配错误,直接触发断言崩溃
  2. 页面加载策略用了networkidle0,要求连续500ms无任何网络请求,目标站点的广告、第三方统计iframe、动态埋点请求很容易打破这个等待条件,同时触发跨iframe加载的时序错乱
  3. 代码存在显性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&currency=USD&specs=0",
  "https://octopart.com/search?q=RN41N-I%2FRM&currency=USD&specs=0",
  "https://octopart.com/search?autosugg_idx=1&currency=USD&oq=adxl1004&q=adxl1004bcpz&specs=1",
  "https://octopart.com/search?q=SI7021-A20-GM1&currency=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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 02:48:30