news 2026/10/7 4:41:17

Serverless 实战:用函数计算部署 Puppeteer 截图服务

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Serverless 实战:用函数计算部署 Puppeteer 截图服务

简介:这份PPT资源面向希望快速上手Serverless架构的开发者与运维人员,以“快速开发一个分布式Puppeteer网页截图服务”为主线,系统讲解函数计算的核心概念与落地方法。内容涵盖函数计算介绍、Web应用迁移函数计算的实操体验,以及将Puppeteer网页截图服务部署到函数计算平台的完整流程,并延伸至使用Rendertron搭建Headless Chrome渲染解决方案,帮助读者理解无服务器、弹性伸缩、高可用与低成本等特性在真实项目中的价值。资源包共1个文件,为pptx演示文稿,大小约3.42MB,结构清晰、图文并茂,适合作为技术分享或自学课件。目前已有152人学习,读者可从中获得从概念到部署的完整知识链路,掌握yarn dev、fun deploy等命令的使用思路,并了解分布式截图与网页渲染服务的实现要点,适合具备一定Web开发基础、想切入Serverless领域的工程师参考。

1. 从一台永远在线的截图服务器说起:这份 PPT 到底能帮你省下什么

很多团队第一次做网页截图,都是先开一台 2 核 4G 的云主机,装 Chrome、装 Puppeteer、写个 Express 接口,然后祈祷它别在半夜 OOM。流量小的时候机器闲着烧钱,流量一上来 Chrome 进程排队排到超时,运维还得盯着磁盘和内存。这份《Serverless 技术开发实战.pptx》讲的正是另一条路:把 Puppeteer 截图服务拆成函数,交给函数计算托管,用fun deploy一条命令推上去,按调用次数计费,没人访问时成本趋近于零。它适合正在做自动化测试、SEO 预渲染、报表导出或者数据抓取的工程师,也适合想搞明白 Serverless 到底能不能扛住真实业务的人。PPT 本身是课程讲义,核心内容围绕函数计算介绍、Web 应用迁移体验、Puppeteer 截图服务部署三块展开,还附了 Rendertron 搭建 Headless Chrome 渲染方案的延伸阅读。下面我按自己拆项目的习惯,把它还原成一份能照着复现的实战笔记。

2. 函数计算与 Puppeteer 的选型逻辑:为什么不是 ECS 加 Docker

2.1 函数计算到底替你管了什么

先把概念对齐。函数计算(Function Compute)是一种事件驱动的计算服务,你上传的是一段代码而不是一台机器。平台负责运行环境、扩缩容、负载均衡和故障恢复,你只为代码实际执行的那几百毫秒到几秒付费。PPT 里列了四个关键词:无需管理基础设施、实时弹性伸缩、高可用、低成本。这四个词不是宣传语,落到 Puppeteer 场景里各有具体含义。

无需管理基础设施,意味着你不用再维护 Chrome 的依赖库。Puppeteer 在 Linux 上跑起来需要一堆共享库,字体、libnss、libatk 缺一个就启动失败,自己装机器时这些坑要踩一遍。函数计算提供官方或社区维护的运行时镜像,Chrome 依赖已经打包好,你只需要把业务代码和 node_modules 一起传上去。

实时弹性伸缩,对应的是截图请求的突发性。一个内容平台做分享卡片,可能白天每分钟几十次、凌晨几乎为零,也可能一条推送带来瞬时几百并发。固定机器要么浪费要么被打爆,函数计算按并发自动拉起实例,请求结束实例回收。

高可用和低成本是一体两面。多可用区部署由平台保证,你不用自己搭主从;计费按实际执行时间和调用次数,空闲不花钱。对截图这种典型的短时、突发、无状态任务,这个模型比常驻服务更贴合。

2.2 Puppeteer 在函数环境里的三个约束

选型讲完,得说清楚 Puppeteer 放进函数计算后有哪些硬约束,不然部署上去也是白搭。

第一是冷启动。函数实例首次被调用或长时间空闲后重建时,需要加载运行时、启动 Chrome,这个过程可能几百毫秒到几秒。截图本身可能只要 1 秒,冷启动却占了大头。常见做法是设置预留实例,或者接受首屏稍慢、后续请求走热实例。

第二是执行时长上限。函数计算对单次调用有超时限制,具体数值以你所用平台的配置为准。截图任务要设合理的timeout,页面加载慢的站点要提前判断,不能让函数一直挂着。

第三是无状态。函数实例随时可能被回收,本地磁盘写入不保证保留。截图结果要么直接返回二进制流,要么上传到对象存储再返回 URL,不能指望存在本地。

2.3 从 Web 应用到函数的迁移路径

PPT 里给了一条迁移体验路径,命令是yarn dev和fun deploy。这套流程对应的是把本地 Web 应用改造成函数入口,再用工具链部署。我一般会这样组织目录:

# 项目结构示例 puppeteer-shot/ ├── index.js # 函数入口,导出 handler ├── package.json # 依赖声明,含 puppeteer-core 或 puppeteer ├── template.yml # 函数计算资源描述文件 └── node_modules/ # 本地安装的依赖,部署时一并上传

yarn dev用于本地调试,模拟函数被事件触发时的行为;fun deploy读取template.yml,把代码和配置打包上传到函数计算。这里的关键是入口函数签名要符合平台约定,比如:

// index.js const puppeteer = require('puppeteer-core'); exports.handler = async (event, context) => { // 从事件中解析目标 URL 和截图参数 const url = event.url || 'https://example.com'; const width = event.width || 1280; const height = event.height || 800; // 启动无头 Chrome,args 针对容器环境做适配 const browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage'] }); try { const page = await browser.newPage(); await page.setViewport({ width, height }); await page.goto(url, { waitUntil: 'networkidle2', timeout: 15000 }); const buffer = await page.screenshot({ type: 'png' }); return { statusCode: 200, headers: { 'Content-Type': 'image/png' }, body: buffer.toString('base64'), isBase64Encoded: true }; } finally { await browser.close(); // 必须关闭,否则实例资源泄漏 } };

这段代码里几个参数值得展开。--no-sandbox和--disable-setuid-sandbox是因为容器内通常没有完整的沙箱权限,不加会直接启动失败。--disable-dev-shm-usage解决的是容器/dev/shm分区过小导致 Chrome 崩溃的问题,这个坑在本地开发时往往遇不到,一上云就翻车。waitUntil: 'networkidle2'表示等网络基本空闲再截图,适合大多数页面,但对有长轮询或持续动画的站点可能永远等不到,这时要换成domcontentloaded加固定延时。timeout要小于函数本身的超时时间,留出余量。

template.yml是部署的描述文件,常见字段包括服务名、函数名、运行时、入口、内存和超时:

# template.yml ROSTemplateFormatVersion: '2015-09-01' Transform: 'Aliyun::Serverless-2018-04-03' Resources: screenshot-service: Type: 'Aliyun::Serverless::Service' Properties: Description: 'Puppeteer 截图服务' screenshot-func: Type: 'Aliyun::Serverless::Function' Properties: Handler: index.handler Runtime: nodejs14 MemorySize: 1024 Timeout: 30 CodeUri: ./

内存给 1024MB 是因为 Chrome 渲染吃内存,给太小容易在复杂页面上被杀。超时 30 秒是上限保护,实际业务里截图超过 15 秒基本可以判定页面有问题。CodeUri指向当前目录,fun deploy会把整个目录打包,所以node_modules要在本地装好,且注意不要把无关的大文件带进去。

3. 部署 Puppeteer 截图服务:从本地跑通到线上调用

3.1 本地验证与依赖处理

在fun deploy之前,先在本地把函数逻辑跑通。最省事的办法是写一个本地调用脚本,直接 require 入口文件并传入模拟事件:

// local-test.js const { handler } = require('./index'); (async () => { const event = { url: 'https://example.com', width: 1200, height: 900 }; const context = {}; const result = await handler(event, context); // 把 base64 结果写到本地文件,肉眼确认截图是否正确 const fs = require('fs'); fs.writeFileSync('shot.png', Buffer.from(result.body, 'base64')); console.log('截图已保存,状态码:', result.statusCode); })();

跑node local-test.js,打开生成的shot.png检查。这一步能提前暴露 Chrome 启动参数、页面加载策略、视口设置的问题,比部署上去再看日志快得多。依赖方面,如果函数计算环境已内置 Chromium,可以用puppeteer-core减小包体积;如果需要自带浏览器,就用完整puppeteer,但要注意包大小和下载源。国内环境安装时常见做法是设置PUPPETEER_DOWNLOAD_HOST或使用镜像源,避免下载超时。

3.2 fun deploy 的完整流程与参数

本地验证通过后,执行部署。典型流程是:

# 安装函数计算工具链 npm install -g @alicloud/fun # 配置账号信息,按提示输入 AccessKey 等 fun config # 本地调试,模拟事件触发 fun local invoke screenshot-func # 部署到云端 fun deploy

fun config会引导你填写账号、区域和默认服务名,这些信息保存在本地配置文件里,后续命令直接读取。fun local invoke用于在本地模拟函数执行,适合调试入口逻辑。fun deploy读取template.yml,创建或更新服务和函数,输出部署结果和调用入口。

部署完成后,平台会给出一个 HTTP 触发地址或调用方式。如果配置了 HTTP 触发器,可以直接用 curl 测试:

# 通过 HTTP 触发器调用截图函数 curl -X POST https://your-trigger-url \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","width":1280,"height":800}' \ --output result.png

如果返回的是 base64 编码的 body,需要先解码再保存。这一步常见问题是触发器鉴权配置,有的平台默认开启签名校验,直接 curl 会返回 403,需要在控制台调整触发器的认证方式或带上签名头。

3.3 截图服务的参数调优与结果处理

服务跑起来之后,真正影响体验的是参数。下面这张表是我在实际项目里反复调过的几个关键项:

参数作用常见取值注意点
waitUntil页面加载完成的判定条件networkidle2/domcontentloaded长轮询页面用前者会超时
timeout页面加载超时15000ms要小于函数超时
viewport视口宽高1280x800移动端截图要设 deviceScaleFactor
fullPage是否整页截图true / false整页截图内存占用高
type图片格式png / jpegjpeg 体积小,适合分享卡片
MemorySize函数内存1024MB 起复杂页面给 2048MB

结果处理上,如果截图要长期保存,建议在函数里直接上传到对象存储,返回一个带时效的 URL,而不是把 base64 塞在响应里。base64 会让响应体积膨胀约三分之一,大图容易触发响应大小限制。上传对象存储的代码大致是:

// 在截图成功后上传到对象存储 const OSS = require('ali-oss'); const client = new OSS({ region: 'your-region', accessKeyId: '...', accessKeySecret: '...', bucket: 'your-bucket' }); const objectName = `shots/${Date.now()}.png`; await client.put(objectName, buffer); return { statusCode: 200, body: JSON.stringify({ url: client.signatureUrl(objectName, { expires: 3600 }) }) };

AccessKey 不要硬编码在代码里,用函数计算的环境变量配置,代码里通过process.env读取。这是血泪经验,硬编码的密钥一旦随代码包泄露,后果比截图失败严重得多。

4. 避坑与排查:截图服务上线后最容易翻车的五件事

4.1 现象:函数调用返回超时,日志显示 Chrome 启动失败

原因:容器环境缺少 Chrome 运行所需的共享库,或者启动参数没加--no-sandbox。本地开发机通常有完整桌面环境,不会暴露这个问题。

解决:确认使用的运行时镜像是否包含 Chromium 依赖;在puppeteer.launch的args里加上--no-sandbox、--disable-setuid-sandbox、--disable-dev-shm-usage、--disable-gpu。如果仍然失败,在函数里打印browser.process().stderr或捕获启动异常,看具体缺哪个库。

4.2 现象:截图成功但图片是白屏或只有部分内容

原因:页面是前端渲染,waitUntil设成了domcontentloaded,DOM 就绪时数据还没请求回来;或者页面有懒加载,视口外的图片没触发加载。

解决:改用networkidle2并适当增加超时;对懒加载页面,截图前执行page.evaluate滚动到底部再滚回顶部,触发所有图片加载;必要时在goto之后加page.waitForSelector等待关键元素出现。

4.3 现象:并发上来后大量请求失败,日志报内存不足

原因:每个函数实例启动一个 Chrome,Chrome 本身吃内存,复杂页面渲染时内存峰值更高。函数内存配置偏小,或者实例内没有及时关闭 browser。

解决:把MemorySize调到 2048MB 或更高;确保每次调用结束后browser.close()在finally里执行;如果单实例并发处理多个请求,考虑每个请求独立启动 browser 还是复用,复用要注意页面隔离和资源清理。

4.4 现象:部署成功但 HTTP 调用返回 403 或签名错误

原因:HTTP 触发器默认开启了鉴权,或者请求方法、Content-Type 与触发器配置不匹配。

解决:在控制台检查触发器的认证方式,测试阶段可以改为匿名调用,生产环境建议保留签名并让调用方正确计算签名。同时确认触发器支持的请求方法包含 POST,Content-Type 为application/json。

4.5 现象:本地fun local invoke正常,线上fun deploy后行为不一致

原因:本地和线上的运行时版本、环境变量、依赖版本不一致;或者node_modules里包含了本地平台特有的二进制文件。

解决:在template.yml里明确指定运行时版本;环境变量通过配置文件或控制台设置,不要依赖本地 shell;依赖安装时使用与线上一致的操作系统架构,必要时在 CI 环境重新安装依赖再部署。

5. 进阶:用 Rendertron 思路做 SEO 预渲染与定时截图

PPT 里提到了 Rendertron,这是一个基于 Headless Chrome 的渲染服务,常用来解决 SPA 应用的 SEO 问题。它的思路和 Puppeteer 截图服务同源,但目标不同:截图是给人看图片,Rendertron 是给爬虫看渲染后的 HTML。把这两条线合起来,能做出更完整的方案。

一个具体技巧是在函数里根据请求头区分用途。爬虫请求返回渲染后的 HTML,普通请求返回截图。代码骨架如下:

exports.handler = async (event, context) => { const url = event.url; const userAgent = (event.headers && event.headers['user-agent']) || ''; const isBot = /bot|spider|crawler/i.test(userAgent); const browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox', '--disable-dev-shm-usage'] }); try { const page = await browser.newPage(); await page.goto(url, { waitUntil: 'networkidle2', timeout: 15000 }); if (isBot) { // 爬虫:返回渲染后的完整 HTML const html = await page.content(); return { statusCode: 200, headers: { 'Content-Type': 'text/html' }, body: html }; } else { // 普通用户:返回截图 const buffer = await page.screenshot({ type: 'jpeg', quality: 80 }); return { statusCode: 200, headers: { 'Content-Type': 'image/jpeg' }, body: buffer.toString('base64'), isBase64Encoded: true }; } } finally { await browser.close(); } };

这个模式的好处是一套函数同时服务两类需求,省去单独维护渲染服务的成本。验证方法也简单:用 curl 带上User-Agent: Googlebot请求,看返回的是 HTML 还是图片;再用普通 UA 请求,确认返回图片。两种结果都符合预期,说明分流逻辑生效。

另一个进阶用法是定时截图。函数计算支持定时触发器,可以配置 cron 表达式,让函数每天固定时间对指定页面截图并上传对象存储,用于监控页面变化或生成日报。配置方式是在template.yml里给函数加一个定时触发器:

Triggers: - Name: daily-shot Type: Timer Properties: CronExpression: '0 0 8 * * *' # 每天 8 点触发 Enable: true Payload: '{"url":"https://example.com","width":1280,"height":800}'

Cron 表达式各平台格式略有差异,配置前确认字段顺序。Payload 是触发时传给函数的事件内容,函数里按普通事件解析即可。定时截图要注意幂等和失败重试,如果某天截图失败,最好有告警或补跑机制,别让监控断档。

从那以后我每次上线截图服务,都会先用fun local invoke跑通本地逻辑,再部署到测试环境用真实 URL 压一遍并发,最后才切生产。这套流程帮我挡掉过 Chrome 启动失败、内存不足、触发器鉴权好几个坑。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/7 4:40:55

开源AI编码代理实战:GUI自动化、MCP接入与单文件打包

做这个 AI 编码代理,起因是我在几个编程助手之间折腾了好一阵,发现它们有一个共同的死穴:只能处理代码文件和终端命令,一旦任务里出现“打开系统设置、点几下界面、拖一个滑块”这种操作,就直接罢工。我当时想&#xf…

作者头像 李华
网站建设 2026/10/7 4:40:55

NPN与PNP传感器接线原理与工业实战指南

1. 为什么三线传感器接线总出错?——从产线停机37分钟说起上周在东莞一家汽车零部件厂做现场调试,产线突然停摆。排查半小时,发现只是光电开关信号灯不亮。换新传感器、测电源、查PLC输入点——全都没问题。最后蹲在电控柜前拿万用表一量&…

作者头像 李华
网站建设 2026/10/7 4:40:14

AI获客断流的真相:搜索引用缺失与六步破局法

很多人做AI获客做了半年,方向其实从一开始就偏了。他们盯着的还是旧时代的漏斗:投广告、铺关键词、刷排名。但当用户真正的问题从“搜索一下然后点开链接”变成了“直接问AI并等一个结论”时,原来的流量模型就失灵了。你能看到搜索量没降&…

作者头像 李华
网站建设 2026/10/7 4:39:32

Python上下文管理器与with语句:从资源管理到异常处理的完整指南

你有没有为了找一个“句柄泄漏”问题,把线上脚本翻了个底朝天,最后发现就是某个文件对象没关?我有一次排查连接数暴涨,查了半天,才发现是一个爬虫任务里每次拉数据都用open()拿个文件句柄,但有几条异常分支…

作者头像 李华
网站建设 2026/10/7 4:39:29

Matplotlib与Seaborn选型实战:从底层画布到统计图表完整指南

数据可视化这个方向,Matplotlib 和 Seaborn 称得上 Python 生态里最常用的两张脸。我做了几年数据项目,从农产品价格分析到网约车运营大屏,几乎每个项目都会用到这两个库。可说实话,很多人学完基础教程,依然画不出能真…

作者头像 李华