Chromium 大約有 280MB,而 AWS Lambda 的未解壓套件上限是 250MB。只要你曾經試過直接 npm install puppeteer 然後部署到 Lambda,你就知道這筆帳怎麼算——根本算不通。
我已經熬過太多個凌晨兩點,debug「Failed to launch the browser process」這種錯誤,所以很清楚這個主題值得好好比較,而不是再來一篇只教你單一做法、卻讓你對另外兩種方式一頭霧水的教學。所以這篇文章就是要把三種方案攤開來講:Layers、Container Images、以及直接 ZIP 上傳,還會附上對 2026 仍然 適用 的版本相容矩陣,以及最可能踩到的五個錯誤排查。
什麼是 AWS Lambda 上的 Puppeteer?為什麼要用它?
Puppeteer 是一個 Node.js 函式庫,能透過 Chrome DevTools Protocol 控制無頭 Chromium。Lambda 則是 AWS 的無伺服器運算服務——你按次付費、系統自動擴展,而且完全不用碰伺服器。把兩者結合起來,你就能做出一套瀏覽器自動化流程,輕鬆橫向擴充到數百個平行執行,不必先配置一台 EC2。
這類用途在各團隊之間其實很一致:網頁爬取、截圖與 PDF 生成、合成監控、為 SEO 預先渲染單頁應用,以及自動化 UI 測試。問題也始終如一——就是前面提到的 Chromium 體積與 Lambda 套件上限之間的衝突。所以沒有人會把完整的 puppeteer(裡面還包著自己的 Chromium 下載)直接部署到 Lambda。通常的做法是改用 puppeteer-core(不附瀏覽器),再搭配一個為 Lambda 優化過的 Chromium 二進位檔,最常見的是 @sparticuz/chromium。
這個替換——用 puppeteer-core 取代 puppeteer——在你寫任何部署設定之前,就能先解掉 80% 的容量問題。
Layers、Container Image、ZIP:先選對路線
我在研究時最在意的一件事是:幾乎所有現有指南都只教一種部署方式。AWS SAM 教程用 Layers,CDK 範例走 Docker,某篇 Substack 文章則是用把 Chromium binary 放在 S3 的原始 ZIP。沒有人把三者放在一起比較,結果你真正最需要先做的決策——「哪種部署方式最適合我?」——反而被直接跳過了。
所以我們現在補上。
| 比較項目 | Lambda Layers | Container Image(Docker) | 直接 ZIP 上傳 |
|---|---|---|---|
| 最大套件大小 | 250 MB 未解壓(所有 layers 合計) | 10 GB 映像檔 | 250 MB 未解壓 |
| 部署複雜度 | 中等(需管理 layer ARN) | 較高(Dockerfile + 推送到 ECR) | 最低(打包 ZIP 並上傳) |
| 冷啟動影響 | 中等 | 稍高(映像檔較大,拉取時間更久) | 中等 |
| Chromium 更新流程 | 重新發佈 layer 版本 | 重新建置映像 | 重新上傳 ZIP |
| 適合情境 | 快速原型、Serverless Framework 使用者 | 正式環境、團隊已有 Docker CI | 簡單的一次性函式 |
| IaC 支援 | SAM、Serverless Framework | CDK、SAM、Terraform | Console、任何 IaC |
250MB 與 10GB 這兩個限制,都是直接來自 AWS 的 Lambda 配額文件——這不是什麼近期才變動的數字,但它正是會在一開始就決定你整個部署策略的關鍵限制。
如果要給一個實用的判斷準則:如果你是在做原型,或本來就用 Serverless Framework,先從 Layers 開始。如果你要上正式環境,而且團隊已經有 Docker CI/CD,選 Container Image——10GB 的空間會讓你寬鬆很多。如果你只是偶爾要讓單一函式截圖,直接 ZIP 是最省事的做法。
不管走哪一條路,底層核心依賴都一樣:puppeteer-core + @sparticuz/chromium。差別只在你怎麼包裝它們,不在於你包的是什麼。

2026 版本相容矩陣(別再瞎猜)
真正讓人花上數月排錯的,往往不是「怎麼部署」,而是這一段。Stack Overflow 和 GitHub issue 裡最常見的抱怨,不是「我要怎麼部署這個」,而是「為什麼我原本能跑的部署,在一次 npm 更新後默默壞掉了?」元兇通常都是 @sparticuz/chromium、puppeteer-core 和 Node.js runtime 之間的版本不相容。
先說最重要的一點:chrome-aws-lambda(原始的 alixaxel 套件)已經停止維護。 它在 Node 18 以上會出問題,而且也跟不上 Chromium 的更新。如果你看到教學還在引用它,直接關掉那個分頁——那已經過時了。現在所有正式的指南都應該改指向 @sparticuz/chromium。
不要靠目測去配套件主版本,請改用下面這個相容原則:
| 元件 | 版本規則 | 部署前要確認什麼 |
|---|---|---|
puppeteer-core | 選擇你的應用程式需要的 Puppeteer 版本 | 查出該 Puppeteer 版本支援的 Chromium build |
@sparticuz/chromium | 其主版本對應的是 Chromium 主版本,不是 Puppeteer 主版本 | 對照 Puppeteer 支援表中的 Chromium build,並閱讀 Sparticuz 的 release notes |
| AWS Lambda Node.js runtime | 使用目前仍受支援的 Lambda runtime | 每次 runtime 或套件更新後都跑一次 invocation 測試 |
| 架構 | npm 套件包含 x64 binary;arm64 支援則從 Chromium v135 開始,需透過 arm64 layer 或 remote pack | Lambda 架構、layer/pack 成品、Chromium 版本必須完全一致 |
我特別不在這裡直接寫死套件配對,因為 @sparticuz/chromium 跟的是 Chromium 的發佈週期,不是一般語意化版本號。請先看 官方 Puppeteer Chromium Support 頁面,記下你所選 Puppeteer 版本支援的 Chromium 主版本,接著再選對應主版本的 @sparticuz/chromium。最後,還要再讀 Sparticuz release notes,確認 patch 級別的破壞性變更與架構細節。不要只因為兩個套件主版本數字一樣,就假設它們一定能搭配,除非這兩份來源都明確證實了這個對應關係。

如何用 Lambda Layers 在 AWS Lambda 部署 Puppeteer
Lambda Layer 可以把 Chromium 與函式程式碼分開打包,這樣你的 handler 會更精簡,而且同一個 Chromium layer 也能重複給多個函式共用。放在這個領域裡,它算是最接近「快速上手」的方式。
步驟 1:安裝 puppeteer-core 與 -min 套件
當 Chromium 檔案放在 Lambda Layer 裡時,請改用 @sparticuz/chromium-min,讓函式套件保持精簡。把下面的佔位符換成你前面確認好的相容版本:
npm install puppeteer-core@$PUPPETEER_VERSION \
@sparticuz/chromium-min@$CHROMIUM_VERSION
你安裝的是 puppeteer-core,不是 puppeteer,因為它不會自動下載瀏覽器。-min 套件提供啟動輔助工具,而 layer 則在 /opt/chromium 下提供 Brotli 壓縮過的 Chromium 檔案。
步驟 2:建立或引用 Chromium Lambda Layer
你可以使用官方 Sparticuz release 內附的、對應架構的 layer 壓縮檔,或者直接從官方 repo 自行建置。對於 x86_64 Lambda,文件中建議的建置方式如下:
git clone --depth=1 https://github.com/sparticuz/chromium.git
cd chromium
make chromium.x64.zip
這會產生 chromium.x64.zip。把它上傳到 S3,並以你實際使用的 runtime 與 architecture 發佈成 Lambda Layer。若是 arm64,請使用對應的 arm64 release 成品或建置目標;不要把 x64 壓縮檔掛到 arm64 function 上。
如果你用的是 SAM,可以直接在 template.yaml 裡綁定 layer ARN:
Resources:
PuppeteerFunction:
Type: AWS::Serverless::Function
Properties:
Layers:
- arn:aws:lambda:us-east-1:XXXXXXXXXXXX:layer:chromium-layer:1
步驟 3:撰寫 Lambda Handler
下面是一個可正常運作的 handler 範例,它會前往一個網址並回傳頁面標題:
import puppeteer from "puppeteer-core";
import chromium from "@sparticuz/chromium-min";
export const handler = async () => {
const browser = await puppeteer.launch({
args: puppeteer.defaultArgs({ args: chromium.args, headless: "shell" }),
executablePath: await chromium.executablePath("/opt/chromium"),
headless: "shell",
});
try {
const page = await browser.newPage();
await page.goto("https://example.com", { waitUntil: "domcontentloaded" });
return { title: await page.title() };
} finally {
await browser.close();
}
};
注意 finally 區塊。瀏覽器一定要在這裡關閉——如果不關,Lambda 的 warm environment 會在多次 invocation 間累積殭屍瀏覽器程序,最後你會看到一些跟原始程式碼無關、卻很奇怪的記憶體錯誤。
步驟 4:設定 Memory、Timeout 與 Architecture
記憶體至少設為 1024 MB——如果不是只做很簡單的截圖工作,我會建議直接設 1536–2048 MB。Timeout 至少設 60 秒。架構先鎖定 x86_64,除非你已經針對你所使用的 Chromium 版本明確驗證過 arm64 支援(這會隨版本而變)。
步驟 5:部署與測試
sam build && sam deploy --guided
先用測試事件觸發它;如果有任何異常,立刻去看 CloudWatch Logs——下面 troubleshooting 區塊裡 90% 的錯誤都會在那裡顯示得很清楚。
如何用 Container Images(Docker)在 AWS Lambda 部署 Puppeteer
Container image 把 250MB 的麻煩一次解掉,因為它直接把上限拉到 10GB。對正式環境來說,這通常是更好的選擇,尤其是當團隊本來就已經在 CI 流程中使用 Docker 時。
步驟 1:建立 Dockerfile
先從官方 AWS Lambda Node.js base image 開始,安裝依賴並指定 handler:
FROM public.ecr.aws/lambda/nodejs:20
COPY package*.json ./
RUN npm install --production
COPY . .
CMD ["index.handler"]
依照你的 Chromium 套件不同,你可能還需要用 yum install 補幾個共享函式庫(這部分在 troubleshooting 章節會再談)——比起手動安裝完整 Chrome,@sparticuz/chromium 已經幫你打包了大多數必要元件,所以摩擦少很多。
步驟 2:建置並推送到 Amazon ECR
aws ecr create-repository --repository-name puppeteer-lambda
docker build -t puppeteer-lambda .
docker tag puppeteer-lambda:latest <account-id>.dkr.ecr.<region>.amazonaws.com/puppeteer-lambda:latest
aws ecr get-login-password | docker login --username AWS --password-stdin <account-id>.dkr.ecr.<region>.amazonaws.com
docker push <account-id>.dkr.ecr.<region>.amazonaws.com/puppeteer-lambda:latest
記得把 image 放在跟 Lambda function 相同的 region,跨區拉取 image 只會增加不必要的延遲。
步驟 3:用 Container Image 建立 Lambda Function
透過 CLI 或 CDK 把 function 指向 ECR image URI,並像 layer 方案一樣設定 memory(1536–2048 MB)和 timeout(60–120 秒)。
步驟 4:部署與測試
用測試事件觸發,確認輸出正確。和 Layers 相比,主要差異是 cold start 會因為拉取更大的 image 而稍微變慢,但你會換來更充裕的依賴空間。
如何用直接 ZIP 上傳在 AWS Lambda 部署 Puppeteer
這是最不花俏的方式——不用管理 layers,也不用建 Docker。很適合原型,或是只需要單一函式偶爾做瀏覽器工作的情境。
步驟 1:在本機安裝依賴
如果要做成獨立 ZIP,請使用 puppeteer-core + @sparticuz/chromium,並把版本固定住。完整套件包含壓縮過的 Chromium 檔案,執行時會解壓到 /tmp。只有在這些檔案是透過 Lambda Layer 或高速 remote pack URL 另外提供時,才使用 @sparticuz/chromium-min;-min 套件本身並不包含 Brotli 檔案。
步驟 2:打包並壓縮函式
npm install --production
zip -r function.zip . -x "*.git*"
這裡 --production 很重要——dev dependencies 會白白吃掉你的 250MB 額度。
步驟 3:上傳並設定 Lambda Function
aws lambda update-function-code --function-name my-puppeteer-fn --zip-file fileb://function.zip
如果你的 ZIP 超過 50MB,就不能直接透過 Console 或簡單的 CLI 呼叫上傳——你必須先上傳到 S3,再改用 S3 URI。記憶體、timeout 和 architecture 的設定,則跟前兩種方式相同。
步驟 4:部署與測試
同樣使用觸發與查看 logs 的流程。若是完整套件,chromium.executablePath() 不需要帶參數。若是 chromium-min,則要傳入確切的 layer 目錄或 remote pack URL,例如前面 layer 架構的 chromium.executablePath("/opt/chromium")。remote pack 會把下載工作加到第一次 cold start,因此務必把它放在接近 function 的位置,並確認成品版本與架構正確。
在 Lambda 上真正可用的 puppeteer.launch() 參數
這段是大家最常直接複製貼上的,我們就把它弄對。Lambda 的執行環境沒有 /dev/shm、不能用 GPU,且權限受限——這表示你在筆電上可以正常運作的預設 puppeteer.launch(),到了這裡就只是……不能用。
const viewport = {
width: 1920,
height: 1080,
deviceScaleFactor: 1,
isMobile: false,
hasTouch: false,
isLandscape: true,
};
const browser = await puppeteer.launch({
args: await puppeteer.defaultArgs({ args: chromium.args, headless: "shell" }),
executablePath: await chromium.executablePath(),
headless: "shell",
defaultViewport: viewport,
});
@sparticuz/chromium 裡的 chromium.args 已經預先包含了無伺服器環境需要的旗標——像是 --no-sandbox、--disable-gpu、--disable-dev-shm-usage 等等。這就是你該用這個套件而不是自己手動拼旗標清單的原因:它會跟著 Chromium 的需求更新,不用你自己盯著。

排錯:每個開發者幾乎都會遇到的 5 種錯誤
我找到的現有指南幾乎都沒有認真的 troubleshooting 章節,這讓人有點困惑,因為你八成就是因為遇到錯誤,才會來看這篇文章。
"Failed to launch the browser process"
根本原因: 缺少共享函式庫(像 libnss3.so、libatk 等),或 executablePath 設錯。
解法: @sparticuz/chromium 已經包了大多數必要依賴,這也是它比自己手工編 Chromium binary 更推薦的原因。如果你是用 Docker,還是碰到這問題,那就直接在 Dockerfile 裡用 yum install 明確補齊缺少的 libs。
"Unzipped size must be smaller than 262144000 bytes"
根本原因: 你安裝了完整的 puppeteer,它會自帶自己的 Chromium 下載(約 400MB)。
解法: 改成 puppeteer-core + @sparticuz/chromium。如果你真的需要更多空間,就改走 Container Image,利用 10GB 上限。
"Browser disconnected" 或 browser.newPage() Timeout
根本原因: Lambda 記憶體不足,或你的 launch 參數少了像 --disable-gpu 這類旗標。
解法: 記憶體至少設 1024MB(我建議更高——請看下面的 benchmark),並確認你傳的是 chromium.args,而不是精簡過頭的自訂清單。
原本能跑的程式,在 Lambda runtime 更新後壞掉了
根本原因: AWS 會定期修補底層 runtime,這可能會改變共享函式庫版本,或把 Node.js patch 版本換掉。
解法: 明確鎖定 @sparticuz/chromium 版本、在 function 設定裡固定 Node runtime 版本,而且——這是很多人會跳過的部分——每次 AWS 發布 runtime 更新公告後都要重新測試,不要等到壞了才測。
約 30 秒後出現 "Protocol error: Connection closed"
根本原因: 你的 Lambda timeout 比頁面真正載入與渲染所需的時間還短。
解法: 把 timeout 提高到 60–120 秒,明確設定 page.setDefaultNavigationTimeout(),如果你不需要等所有網路請求都完全結束,就把 waitUntil: 'networkidle0' 改成 waitUntil: 'domcontentloaded'。
正式環境強化:記憶體、冷啟動與成本
大多數指南只會叫你「增加 memory」,然後就沒了。這不是可執行的建議——下面才是記憶體往上調時,實際會改變什麼。
Memory 與效能
Lambda 會按照 memory 比例分配 CPU,這一點最容易讓人忽略。更多 memory 不只是「有更多 RAM 可用」而已,它也代表更快的 CPU,會直接加速 Chromium 的渲染。實務上,跑 Puppeteer benchmark 的團隊通常會發現,從 512MB 提高到 1536–2048MB 之間,執行時間會有明顯改善,但你的實際數字還是會 很大程度上 取決於你要渲染的頁面。與其引用一張很快就過時的 benchmark 表,不如直接拿你的目標頁面,在 512MB、1024MB、1536MB 和 2048MB 下各跑一次測試——十分鐘就能知道你的成本/效能甜蜜點在哪。
用 Provisioned Concurrency 處理冷啟動
如果你的情境對延遲很敏感——像合成監控、即時截圖 API——cold start 就是敵人。Provisioned Concurrency 會保持一定數量的執行環境處於溫熱待命狀態,消除冷啟動成本,但代價是你要為這些閒置容量付費。當延遲比成本更重要時,這筆投資就很值得。
用 arm64(Graviton)降低成本
使用 Graviton 的 Lambda function,成本大約比 x86_64 便宜 20%。但要注意的是:@sparticuz/chromium 對 arm64 的支援歷來比 x86_64 限制更多,所以在正式切到 Graviton 之前,務必先針對你鎖定的版本明確驗證。
VPC 與非 VPC
把 function 放進 VPC,以前會顯著拉高冷啟動延遲;AWS 近年已經縮小了這個差距,但它還沒完全消失。只有在 function 必須連到私有資源,例如 RDS 或 ElastiCache 時,才把它放進 VPC;否則就不要。
什麼時候該完全離開 Lambda
如果你的瀏覽器任務經常超過 15 分鐘、需要超過 10GB 記憶體,或是必須在多次請求之間保留持久瀏覽器 session,那 Lambda 就開始跟你作對了。這種情況下,ECS Fargate 才是為此而生——長時間執行、資源可配置、按秒計費。Lambda 很適合短、突發、可平行化的瀏覽器任務;一旦你的工作負載開始像一個持久服務,它就不是對的工具。
什麼時候不該用 Lambda 來部署 Puppeteer
有一件事值得誠實面對:很多搜尋「Puppeteer + Lambda」教學的人,其實真正想解的是資料擷取問題,而不是瀏覽器自動化問題。如果你真正需要的是從網頁中拿到結構化資料——商品列表、聯絡資訊、頁面內容——那上面那些 Chromium 打包、版本鎖定、layer 管理,對你來說很可能都是多餘負擔。
適合留在 Lambda + Puppeteer 的情況:你需要真正操控瀏覽器,例如自訂表單互動、截圖/PDF 流程、合成監控,或是用程式實際操作 DOM 的瀏覽器測試。
如果你要的是爬取 API,請考慮直接用 scraping API:當你的目標是把網頁轉成結構化 JSON,而不是自己駕駛一個瀏覽器 session 時,Thunderbit 的 Open API 可以在單一 HTTP 呼叫背後處理 JS 渲染、反機器人措施與 CAPTCHA。POST /extract 搭配 JSON Schema 就能拿到結構化資料,POST /distill 則能輸出乾淨的 Markdown。如果你正在打造需要在工作流程中途抓資料的 AI agent,也可以用 MCP server(thunderbit_extract、thunderbit_distill),不用自己啟動瀏覽器。
| 比較面向 | Lambda + Puppeteer(自建) | 擷取 API(例如 Thunderbit) |
|---|---|---|
| 建置時間 | 數小時(打包、layers、除錯) | 數分鐘(API key + HTTP 呼叫) |
| 維護成本 | 持續性(版本鎖定、runtime 更新) | 由供應商處理 |
| 反機器人處理 | 手動(stealth plugin、proxy) | 內建 |
| 輸出格式 | 原始 HTML/截圖,再自行解析 | 透過 schema 輸出結構化 JSON |
| 最適合 | 完整瀏覽器自動化、測試、自訂流程 | 資料擷取、爬取、內容匯入 |
我說得直接一點:如果你只是為了從商品頁抓 JSON,卻花了好幾個小時在 debug Chromium binary,那代表你可能解錯題了。只有在你真的需要操控瀏覽器時,才值得走 DIY Lambda 路線;如果目標是擷取資料,應該有更直接的方法。若你正在替某個特定專案評估這個取捨,可以參考我們的 AI 網頁爬蟲指南,裡面會更完整地整理這個領域;如果你想先測試「先擷取再說」的做法,Thunderbit Chrome Extension 也很值得看一看。
總結
三種部署方式,一個共同主題:鎖定版本、給 Chromium 足夠的記憶體,並依照你的真實限制選擇部署方式,而不是照你第一個看到的教學照抄。需要快速迭代就用 Layers;要正式上線就用 Container Image;只是簡單一次性任務就用 ZIP。而如果你真正做的是資料擷取,不是瀏覽器自動化,也許直接用專用的擷取 API,能幫你把打包的麻煩整個省掉。
常見問題
2026 年還能在 AWS Lambda 上跑 Puppeteer 嗎?
可以——只要使用 puppeteer-core 搭配 @sparticuz/chromium,並透過 Layers、Container Image 或直接 ZIP 部署即可。完整的 puppeteer 套件,以及已停用的 chrome-aws-lambda 套件,在目前的 Lambda runtime 上都已不再可靠。
AWS Lambda 的最大套件大小是多少?
Layers 與 ZIP 部署的未解壓大小上限是 250MB;Container Image 部署則是 10GB,這是依據 AWS Lambda quotas 的規定。
chrome-aws-lambda 還有在維護嗎?
沒有。原始的 chrome-aws-lambda 套件(由 alixaxel 維護)已停止維護,在 Node 18 以上會失效。請改用 @sparticuz/chromium——目前它才是持續維護的標準方案。
Puppeteer 在 AWS Lambda 上需要多少記憶體?
1024MB 是實務上的最低門檻;1536–2048MB 會是效能開始變得舒適的區間。低於 1024MB 時,因為 Lambda 的 CPU 配額跟 memory 綁在一起,執行速度通常會明顯偏慢。
我要怎麼降低 Puppeteer 在 Lambda 上的冷啟動時間?
提高 memory(也會帶來更多 CPU)、如果你的情境對延遲很敏感就考慮 Provisioned Concurrency,並盡量讓部署套件保持精簡——每多一個依賴,冷啟動時間就多一點。


