0

0

解决 Puppeteer 在 Heroku 上运行中断:内存泄漏与浏览器资源管理

碧海醫心

碧海醫心

发布时间:2025-09-15 21:06:13

|

596人浏览过

|

来源于php中文网

原创

解决 puppeteer 在 heroku 上运行中断:内存泄漏与浏览器资源管理

本教程探讨 Puppeteer 在 Heroku 等云平台运行时,在执行少量任务后停止并抛出超时错误的问题。核心原因在于未正确关闭 Puppeteer 浏览器实例导致的内存泄漏。文章将详细解释这一现象,并提供通过在每次数据抓取后显式调用 browser.close() 来有效管理资源、防止内存耗尽的解决方案,确保 Puppeteer 脚本在生产环境中稳定运行。

问题现象:Puppeteer 在 Heroku 上的运行瓶颈

许多开发者在使用 Puppeteer 进行网页抓取或自动化时,可能会遇到一个常见且令人困惑的问题:脚本在本地开发环境中运行良好,但在部署到 Heroku 等云平台后,仅执行少量任务便会中断,并抛出 TimeoutError。

例如,一个旨在循环抓取多项数据的 Puppeteer 脚本,在本地可以顺利完成所有任务,但在 Heroku 的部署日志中,可能会在成功抓取几项数据后,出现类似以下的错误信息:

2023-05-22T17:29:18.421015+00:00 app[web.1]: == Finished grabbing CS 475
2023-05-22T17:29:19.098698+00:00 app[web.1]: == Finished grabbing CS 331
2023-05-22T17:29:19.783377+00:00 app[web.1]: == Finished grabbing CS 370

2023-05-22T17:29:49.992190+00:00 app[web.1]: /app/node_modules/puppeteer/lib/cjs/puppeteer/common/util.js:317
2023-05-22T17:29:49.992208+00:00 app[web.1]:     const timeoutError = new Errors_js_1.TimeoutError(`waiting for ${taskName} failed: timeout ${timeout}ms exceeded`);
2023-05-22T17:29:49.992209+00:00 app[web.1]:                          ^
2023-05-22T17:29:49.992210+00:00 app[web.1]: TimeoutError: waiting for target failed: timeout 30000ms exceeded

这个 TimeoutError,特别是 waiting for target failed,通常意味着 Puppeteer 尝试与浏览器实例建立连接或等待某个目标(如页面)时,浏览器本身已变得无响应或崩溃。这种现象往往指向资源耗尽,尤其是在 Heroku 这种具有严格内存限制的平台上。

深层原因:未关闭的浏览器实例导致的内存泄漏

Puppeteer 在执行 puppeteer.launch() 时,会启动一个全新的 Chromium 浏览器进程。这个浏览器进程及其打开的所有页面(page 对象)会占用相当可观的系统资源,包括内存和 CPU。如果在一个循环中反复调用 puppeteer.launch() 来执行独立的抓取任务,但每次任务完成后都没有显式地关闭浏览器实例,那么每个被启动的浏览器进程将持续占用资源而不会被释放。

在本地开发环境中,由于通常拥有更充足的系统资源,这种内存泄漏可能不会立即显现。然而,在 Heroku 的 Dyno 环境中,可用内存通常只有 512MB(对于免费或标准 Dyno),几个未关闭的浏览器实例就可能迅速耗尽所有可用内存。当内存耗尽时,操作系统可能会杀死进程,或者浏览器进程变得无响应,导致后续的 Puppeteer 操作超时失败。即使将 Puppeteer 运行在无头模式(headless mode),也仅仅是节省了图形界面的资源,并不能解决未关闭浏览器实例造成的内存泄漏问题。

解决方案:显式关闭 Puppeteer 浏览器

解决 Puppeteer 在云端部署时内存泄漏问题的关键在于,每次完成抓取任务后,必须显式地关闭浏览器实例。通过调用 await browser.close() 方法,可以确保所有相关的浏览器进程和资源被正确释放。

Rytr写作助手
Rytr写作助手

Rytr 是一款AI内容生成和写作助手,可帮助您在短短几秒钟内以极低的成本创建高质量的内容!

下载

代码示例与改进

以下是原始的 scrapeData 函数及其改进版本,展示了如何正确地关闭浏览器:

原始 scrapeData 函数(存在内存泄漏风险):

async function scrapeData(code) {
    const browser = await puppeteer.launch({
      args: [
        '--no-sandbox',
        '--disable-setuid-sandbox'
      ]
    })
    const page = await browser.newPage()

    await page.goto("website url") // 替换为实际网址
    await page.type('#crit-keyword', code)
    await page.click('#search-button')

    await page.waitForSelector(".result__headline")
    await page.click(".result__headline")
    await page.waitForSelector("div.text:nth-child(2)")

    let 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 // 浏览器实例在此处未被关闭
}

改进后的 scrapeData 函数(加入 browser.close() 和 try...finally):

async function scrapeData(code) {
    let browser; // 将 browser 声明在 try 块外部,以便 finally 块访问
    try {
        browser = await puppeteer.launch({
            args: [
                '--no-sandbox',
                '--disable-setuid-sandbox'
            ]
            // 默认的超时时间可能需要调整,但这里的TimeoutError主要由内存引起
        });
        const page = await browser.newPage();

        await page.goto("your_website_url", { timeout: 30000 }); // 增加页面导航超时时间
        await page.type('#crit-keyword', code);
        await page.click('#search-button');

        await page.waitForSelector(".result__headline", { timeout: 15000 }); // 增加等待选择器超时时间
        await page.click(".result__headline");
        await page.waitForSelector("div.text:nth-child(2)", { timeout: 15000 }); // 增加等待选择器超时时间

        let 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 (error) {
        console.error(`Error scraping data for ${code}:`, error);
        throw error; // 重新抛出错误,以便上层调用者可以处理
    } finally {
        if (browser) {
            await browser.close(); // 确保浏览器在任何情况下都被关闭
        }
    }
}

通过在 scrapeData 函数的 finally 块中添加 await browser.close(),我们保证了无论数据抓取成功与否,浏览器实例及其占用的资源都会被妥善释放。这有效地解决了内存泄漏问题,使脚本能够在 Heroku 上稳定地处理所有预期的任务。

注意事项与最佳实践

  1. 资源管理是核心: 始终将 browser.close() 视为与 puppeteer.launch() 配对使用的关键操作。忘记关闭浏览器是 Puppeteer 内存泄漏最常见的原因。
  2. try...finally 块: 使用 try...catch...finally 结构是最佳实践。finally 块中的代码无论 try 块中是否发生错误都会执行,从而确保浏览器实例在任何情况下都能被关闭,避免因程序异常导致资源无法释放。
  3. Heroku Dyno 内存限制: 了解 Heroku Dyno 的资源限制至关重要。即使正确关闭了浏览器,如果单个抓取任务本身就需要大量内存,或者在短时间内并发启动了过多的浏览器实例,仍然可能遇到内存问题。对于大量任务,考虑使用队列机制或批处理,错峰执行,避免瞬间的资源高峰。
  4. 超时设置: 原始日志中的 TimeoutError 也可能与网络延迟或页面加载缓慢有关。虽然内存泄漏是主要原因,但适当调整 page.goto()、page.waitForSelector() 等操作的超时时间(例如,从默认的 30 秒调整为更符合实际情况的值),可以提高脚本的鲁棒性。
  5. 无头模式 (Headless Mode): 尽管无头模式能减少一些资源消耗,但它并不能替代显式关闭浏览器实例。在 Heroku 等服务器环境中,通常都应使用无头模式运行 Puppeteer。
  6. 错误处理: 在 catch 块中捕获并记录错误,可以帮助调试和理解脚本失败的原因。根据应用需求,可以选择重新抛出错误、返回默认值或执行其他恢复操作。

总结

Puppeteer 在 Heroku 等云平台部署时遇到的运行中断问题,其核心往往是由于未正确关闭浏览器实例导致的内存泄漏。通过在每次抓取任务完成后,使用 await browser.close() 显式释放资源,并结合 try...finally 块确保资源在任何情况下都能被释放,可以有效解决这一问题。良好的资源管理是构建稳定、高效且可扩展的网页抓取或自动化脚本的关键。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
go语言goto的用法
go语言goto的用法

本专题整合了go语言goto的用法,阅读专题下面的文章了解更多详细内容。

138

2025.09.05

PHP 命令行脚本与自动化任务开发
PHP 命令行脚本与自动化任务开发

本专题系统讲解 PHP 在命令行环境(CLI)下的开发与应用,内容涵盖 PHP CLI 基础、参数解析、文件与目录操作、日志输出、异常处理,以及与 Linux 定时任务(Cron)的结合使用。通过实战示例,帮助开发者掌握使用 PHP 构建 自动化脚本、批处理工具与后台任务程序 的能力。

74

2025.12.13

chatgpt使用指南
chatgpt使用指南

本专题整合了chatgpt使用教程、新手使用说明等等相关内容,阅读专题下面的文章了解更多详细内容。

0

2026.03.16

chatgpt官网入口地址合集
chatgpt官网入口地址合集

本专题整合了chatgpt官网入口地址、使用教程等内容,阅读专题下面的文章了解更多详细内容。

0

2026.03.16

minimax入口地址汇总
minimax入口地址汇总

本专题整合了minimax相关入口合集,阅读专题下面的文章了解更多详细地址。

4

2026.03.16

C++多线程并发控制与线程安全设计实践
C++多线程并发控制与线程安全设计实践

本专题围绕 C++ 在高性能系统开发中的并发控制技术展开,系统讲解多线程编程模型与线程安全设计方法。内容包括互斥锁、读写锁、条件变量、原子操作以及线程池实现机制,同时结合实际案例分析并发竞争、死锁避免与性能优化策略。通过实践讲解,帮助开发者掌握构建稳定高效并发系统的关键技术。

7

2026.03.16

TypeScript类型系统进阶与大型前端项目实践
TypeScript类型系统进阶与大型前端项目实践

本专题围绕 TypeScript 在大型前端项目中的应用展开,深入讲解类型系统设计与工程化开发方法。内容包括泛型与高级类型、类型推断机制、声明文件编写、模块化结构设计以及代码规范管理。通过真实项目案例分析,帮助开发者构建类型安全、结构清晰、易维护的前端工程体系,提高团队协作效率与代码质量。

114

2026.03.13

Python异步编程与Asyncio高并发应用实践
Python异步编程与Asyncio高并发应用实践

本专题围绕 Python 异步编程模型展开,深入讲解 Asyncio 框架的核心原理与应用实践。内容包括事件循环机制、协程任务调度、异步 IO 处理以及并发任务管理策略。通过构建高并发网络请求与异步数据处理案例,帮助开发者掌握 Python 在高并发场景中的高效开发方法,并提升系统资源利用率与整体运行性能。

141

2026.03.12

C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

396

2026.03.11

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
WEB前端教程【HTML5+CSS3+JS】
WEB前端教程【HTML5+CSS3+JS】

共101课时 | 10.3万人学习

JS进阶与BootStrap学习
JS进阶与BootStrap学习

共39课时 | 3.4万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号