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

Puppeteer运行触发EventEmitter内存泄漏 11个exit监听器绑定process问题

问题产生原因
  • 核心触发原因:代码一次性并行启动了超过10个Puppeteer浏览器实例。Puppeteer每次执行puppeteer.launch()时,都会自动给process对象注册exit、SIGINT、SIGTERM、SIGHUP四类事件的监听器,用于Node进程异常退出时自动清理残留的浏览器进程,避免孤儿进程占用资源。Node.js的EventEmitter默认单事件最多允许注册10个监听器,你启动了11个浏览器实例就触发了阈值警告。
  • 代码写法缺陷放大了问题:
    1. await products?.map(async ilan => {})写法无效,map返回的是Promise数组,直接await不会等待所有异步任务执行完成,导致所有浏览器实例几乎同时启动,没有先后顺序
    2. 每个遍历逻辑中套了多余的立即执行async函数,进一步脱离了外层的异步管控,即使任务抛出错误也可能导致await browser.close()没有执行,已经注册的监听器无法被移除,出现永久内存泄漏
    3. 没有做并发数控制,产品数量越多,启动的浏览器实例越多,监听器泄漏的问题会越严重
解决方法

最优方案:复用浏览器实例(推荐)

不要每次处理产品都新建浏览器,全局只启动1个浏览器实例,每个产品任务仅新建Page标签页即可,从根源上避免重复注册process监听器,同时大幅降低CPU、内存资源消耗。

修复异步逻辑+控制并发

如果业务场景确实需要多浏览器实例,要做好异步等待和并发限制:

  1. 把无效的map+await写法换成Promise.all或者for...of串行执行,保证前一个任务完成、浏览器正常关闭后再启动下一个
  2. 把browser.close()放到finally块中,不管任务成功还是报错都能正常关闭浏览器、卸载监听器
  3. 控制同时运行的浏览器实例数不超过10个,避免触发默认阈值

临时规避方案(不推荐)

如果只是临时调试需要启动大量实例,可以手动调高Node事件监听器的阈值,在代码最顶部加入:

require('events').EventEmitter.defaultMaxListeners = 50 // 按实际需要调整数值

该方案仅屏蔽警告,没有解决根本的资源泄漏问题,不建议生产环境使用。

优化后的代码示例
try {
  // 全局仅启动一个浏览器实例
  const browser = await puppeteer.launch({
    // devtools: true
    // defaultViewport: null,
    // headless: true,
    // executablePath: '/usr/bin/chromium-browser',
    // args: ['--no-sandbox'],
  });

  // 用for...of串行执行任务,如需控制并发可以使用p-limit等工具库
  for (const ilan of products || []) {
    const page = await browser.newPage();
    try {
      const email = "";
      const password = "";
      const title = ilan.title;
      const location = ilan.Country[random].country;
      const categories = ilan.category;
      const link = ilan.link;
      const description = ilan.description;
      const manufacturer = ilan.manufacturer;
      const model = ilan.model;
      const year = ilan.year;
      console.log('title', title)

      await page.goto('你的目标提交地址');

      async function input(focus, deger) {
        await page.focus(focus)
        await page.keyboard.type(deger)
      }
      await input('#user_email', email)
      await input('#user_password', password)
      // 提交登录
      await page.click('input[type="submit"]')
      await page.waitForTimeout(10000)
      // 填充表单
      await input('input[id="api_listing_title"]', title)
      await input('input[id="api_listing_location"]', location)
      await input('input[type="search"]', categories)
      await page.click('li[role="option"]')
      await input('input[id="youtube_link"]', link)
      await input('textarea[name="api_listing[description]"]', description)
      await input('input[name="api_listing[manufacturer]"]', manufacturer)
      await input('input[name="api_listing[model]"]', model)
      await input('input[name="api_listing[year]"]', year)
      await page.select('#api_listing_condition', 'new')
      // 上传图片
      const inputUploadHandle = await page.$('input[type=file]');
      // 修复图片上传的异步等待逻辑
      for (const image of ilan.images || []) {
        await inputUploadHandle.uploadFile(image.path)
      }

      await page.waitForTimeout(10000)
      await page.click('input[name="commit"]')
      await page.waitForTimeout(15000)
    } catch (error) {
      console.log("🌵💜🐢 error", error)
      res.json({ message: 'error' })
    } finally {
      // 无论任务成功失败都关闭当前页面,释放资源
      await page.close();
    }
  }

  // 所有任务执行完成后关闭浏览器
  await browser.close();
} catch (err) {
  console.error('全局执行错误', err)
}

内容的提问来源于stack exchange,提问作者Yunus Emre Acar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 17:09:03