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

Puppeteer部署至Heroku后仅运行三次即终止问题求助

Heroku上Puppeteer爬取超时(内存不足)的解决方案

你遇到的核心问题是每次爬取都新建浏览器实例,Heroku免费/基础dyno内存有限(512MB),连续创建3个浏览器后内存耗尽,导致第4次启动浏览器时触发超时错误。以下是针对性的解决步骤:

1. 复用浏览器实例(最关键优化)

不要在scrapeData函数里每次调用puppeteer.launch(),改为全局初始化一个浏览器实例,每次爬取仅创建新页面,用完立即关闭页面释放资源。

修改后的爬虫模块示例:

// 全局维护一个浏览器实例
let browser;

// 初始化浏览器(服务启动时调用一次)
async function initBrowser() {
  if (!browser || !browser.isConnected()) {
    browser = await puppeteer.launch({
      args: [
        '--no-sandbox',
        '--disable-setuid-sandbox',
        '--disable-dev-shm-usage', // 避免Heroku的/dev/shm内存限制
        '--disable-gpu',
        '--single-process', // 单进程模式大幅降低内存占用
        '--no-zygote',
        '--headless=new' // 新版无头模式更高效
      ],
      headless: 'new',
      timeout: 60000 // 延长浏览器启动超时时间
    });
  }
  return browser;
}

// 修改后的scrapeData函数
async function scrapeData(code) {
  await initBrowser(); // 确保浏览器已启动
  const page = await browser.newPage();
  
  try {
    // 禁用图片、CSS加载,减少内存占用
    await page.setRequestInterception(true);
    page.on('request', (req) => {
      if (req.resourceType() === 'image' || req.resourceType() === 'stylesheet') {
        req.abort();
      } else {
        req.continue();
      }
    });

    await page.goto("website url", { timeout: 60000 });
    await page.type('#crit-keyword', code, { delay: 50 }); // 模拟真实输入速度,避免反爬
    await page.click('#search-button');

    await page.waitForSelector(".result__headline", { timeout: 30000 });
    await page.click(".result__headline");

    await page.waitForSelector("div.text:nth-child(2)", { timeout: 30000 });

    const data = await page.evaluate(() => {
        let classTitle = document.querySelector("div.text:nth-child(2)").textContent
            .toLowerCase().split(' ')
            .map((s) => s.charAt(0).toUpperCase() + s.substring(1)).join(' ').replace('Ii', "II");
        let classDesc = document.querySelector(".section--description > div:nth-child(2)").textContent.replace('Lec/lab/rec.', '').trim();

        return {
            title: classTitle,
            desc: classDesc
        };
    });

    console.log(`== Finished grabbing ${code}`);
    return data;
  } catch (err) {
    console.error(`Failed to grab ${code}:`, err);
    throw err; // 抛出错误以便上层处理
  } finally {
    await page.close(); // 必须关闭页面释放内存
  }
}

// 服务关闭时清理浏览器实例
process.on('exit', async () => {
  if (browser) await browser.close();
});

2. 添加内存监控与重试机制

如果仍遇到内存不足问题,可以在爬取前检查内存状态,超过阈值则等待内存释放,同时添加超时重试逻辑:

// 检查内存是否可用(Heroku免费dyno总内存512MB,预留100MB缓冲)
function canProceed() {
  const memory = process.memoryUsage();
  return memory.heapUsed < 400 * 1024 * 1024; // 堆内存使用不超过400MB
}

// 等待内存释放的函数
async function waitForMemory() {
  while (!canProceed()) {
    console.log("Memory usage high, waiting for release...");
    await new Promise(resolve => setTimeout(resolve, 5000)); // 每5秒检查一次
  }
}

// 修改爬取调用逻辑
async function getClassData(classesTaken) {
  const results = [];
  for (let i = 0; i < classesTaken.length; i++) {
    await waitForMemory(); // 确保内存可用再爬取
    try {
      const data = await scrapeData(classesTaken[i].code);
      results.push(data);
    } catch (err) {
      // 针对超时错误重试一次
      if (err.name === 'TimeoutError') {
        console.log(`Retrying ${classesTaken[i].code}...`);
        await waitForMemory();
        const data = await scrapeData(classesTaken[i].code);
        results.push(data);
      } else {
        throw err;
      }
    }
  }
  return results;
}

3. 其他补充建议

  • 升级dyno:如果爬虫任务较重,考虑升级到Heroku标准dyno(1GB内存),能从根本上缓解内存压力。
  • 限制并发:保持串行爬取,避免同时启动多个页面导致内存暴涨。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 08:30:02