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

Puppeteer并发请求处理:内存泄漏与超时问题排查

问题分析与修复方案

核心问题

你当前的代码确实存在浏览器实例创建过多+资源未正确释放的问题,这直接导致了内存泄漏和超时,具体问题点如下:

  • 重复下载浏览器:每次调用startBrowser都会执行browserFetcher.download("901912"),不管该版本浏览器是否已经下载完成,完全没必要且浪费资源。
  • 每个任务启动一个全新浏览器:anotherFunction里每次都调用startBrowser,相当于每个任务开一个独立浏览器进程,短时间大量任务会把系统内存占满,触发内存泄漏,同时系统资源不足也会导致waitForSelector超时。
  • work函数的异步逻辑完全失效:stuff.map里返回的是setTimeout的ID,不是Promise,await Promise.all(workTodo)根本不会等待所有任务完成,任务会混乱执行,资源释放完全没章法。
  • 冗余页面未关闭:startBrowser已经创建了一个page,但anotherFunction里又调用await browser.newPage(),新创建的页面没有关闭,直接导致内存泄漏。
  • 错误分支未释放资源:如果waitForSelector超时进入catch块,没有关闭browser和page,这些资源会一直占用内存。

修复后的代码示例

1. 优化startBrowser:只下载一次浏览器,复用浏览器实例

// 全局缓存浏览器下载状态和共享实例
let browserRevisionDownloaded = false;
let sharedBrowser = null;

async function startBrowser() {
  // 仅在未下载时执行下载操作
  if (!browserRevisionDownloaded) {
    await browserFetcher.download("901912");
    browserRevisionDownloaded = true;
  }

  // 复用已连接的浏览器,避免重复创建实例
  if (!sharedBrowser || !sharedBrowser.isConnected()) {
    sharedBrowser = await puppeteer.launch({
      executablePath: browserFetcher.revisionInfo("901912").executablePath,
      args: [
        "--no-sandbox",
        "--disable-gpu",
        "--disable-dev-shm-usage",
        "--disable-setuid-sandbox",
        "--no-first-run",
        "--no-zygote",
        "--single-process",
      ],
    });
  }

  // 为每个任务创建新页面
  const page = await sharedBrowser.newPage();
  return { browser: sharedBrowser, page };
}

2. 修复work函数的异步逻辑:用Promise包装超时任务

const work = async (stuff) => {
  const workTodo = stuff.map(item => {
    // 用Promise包装setTimeout,让Promise.all能正确等待任务完成
    return new Promise(resolve => {
      setTimeout(async () => {
        await anotherFunction(item);
        resolve();
      }, item.delayTime); // 假设每个item包含对应的延迟时间参数
    });
  });
  await Promise.all(workTodo);
};

3. 修复anotherFunction:确保所有分支都释放资源

async function anotherFunction(someParams) {
  let browser, page;
  try {
    ({ browser, page } = await startBrowser());
    await page.goto(someParams.targetUrl);
    // 显式设置超时时间,避免无限等待
    await page.waitForSelector(someParams.targetSelector, { timeout: 15000 });
    await page.click(someParams.targetSelector);

    // 关闭当前任务的页面,浏览器留作复用
    await page.close();
  } catch (error) {
    console.log('error', error);
    // 错误分支必须关闭页面,防止内存泄漏
    if (page) await page.close().catch(() => {});
  }
}

4. 全局清理:程序结束时关闭共享浏览器

// 在进程退出时清理共享浏览器实例
process.on('exit', async () => {
  if (sharedBrowser && sharedBrowser.isConnected()) {
    await sharedBrowser.close();
  }
});

额外建议

  • 如果任务量极大,不要用单浏览器实例,而是创建浏览器实例池(比如限制最多5个实例),避免单个浏览器开太多页面导致卡顿。
  • 根据页面加载速度调整waitForSelector的超时时间,避免过短或过长的等待。
  • 可以用puppeteer-cluster库管理并发任务,它封装了实例池和任务调度,比手动实现更稳定。

内容的提问来源于stack exchange,提问作者tryingToBeBetter

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 01:09:30