DieselChen

个人博客,记录一下学习的点滴~

0%

Puppeteer Docker 部署踩坑笔记

在资源受限的服务器环境下部署Puppeteer自动化项目时遇到的各种问题和解决方案总结,包含Docker配置优化、超时处理、内存管理等实践经验。

项目背景

在2核2G的阿里云ECS上部署两个爬虫项目:

  • Python项目: Selenium + Chrome WebDriver
  • Node.js项目: Puppeteer + Chromium

核心问题分析

🎯 主要矛盾:资源限制 vs 浏览器资源消耗

环境 可用内存 Chrome需求 结果
阿里云ECS 2GB 1-1.5GB 频繁OOM崩溃
本地开发 8GB+ 1-1.5GB 运行正常

技术选型对比

Puppeteer vs Selenium

特性 Puppeteer Selenium
内存占用 512MB-1GB 1GB-1.5GB
协议层 Chrome DevTools Protocol WebDriver Protocol
启动速度 快(1-2s) 慢(3-5s)
稳定性 高(直接控制) 中等(多层抽象)
Docker支持 友好 复杂

结论: 在资源受限环境下,Puppeteer优势明显

关键踩坑点

1. 超时配置误区 🔴

❌ 错误配置

1
2
TIMEOUT=10000              // 10秒操作超时
PUPPETEER_PROTOCOL_TIMEOUT=60000 // 60秒协议超时

✅ 正确配置

1
2
TIMEOUT=10000              // 10秒操作超时
PUPPETEER_PROTOCOL_TIMEOUT=120000 // 120秒协议超时 (至少6-10倍)

经验法则: PROTOCOL_TIMEOUT ≥ TIMEOUT × 6

2. Docker Chromium启动参数缺失

🔴 必须的核心参数

1
2
3
4
5
6
7
8
9
await puppeteer.launch({
args: [
'--no-sandbox', // Docker环境下必须
'--disable-setuid-sandbox', // 禁用UID沙盒
'--disable-dev-shm-usage', // 避免共享内存问题
'--disable-gpu', // 禁用GPU加速
'--disable-software-rasterizer', // ⭐ 关键!避免图形渲染问题
]
});

🔴 常见的性能优化参数

1
2
3
4
5
6
7
8
9
10
args: [
'--no-first-run', // 避免首次运行配置
'--disable-default-apps', // 减少资源占用
'--disable-extensions', // 禁用扩展
'--disable-background-networking', // 禁用后台网络
'--disable-sync', // 禁用同步
'--mute-audio', // 静音
'--hide-scrollbars', // 隐藏滚动条
'--metrics-recording-only', // 仅记录指标
]

3. Docker容器配置问题

🔴 共享内存不足

1
2
3
4
# docker-compose.yml
services:
puppeteer:
shm_size: 2g # 增加共享内存,避免crash

🔴 权限配置

1
2
3
4
5
6
services:
puppeteer:
cap_add:
- SYS_ADMIN # Chrome sandbox权限
security_opt:
- seccomp:unconfined # 禁用seccomp限制

4. 基础镜像选择

❌ 错误选择

1
FROM node:16  # 完整版镜像,包含无用组件

✅ 正确选择

1
2
3
4
5
6
7
8
9
FROM node:16-alpine  # 轻量级Alpine镜像
# 安装Chromium依赖
RUN apk add --no-cache \
chromium \
nss \
freetype \
harfbuzz \
ca-certificates \
ttf-freefont

资源管理最佳实践

1. 浏览器复用策略

1
2
3
4
5
6
7
8
9
10
let browser;
let lastTask = Promise.resolve();

function enqueue(task) {
const run = lastTask.catch(() => {}).then(() => task());
lastTask = run;
return run;
}

// 避免重复启动浏览器,节省内存和启动时间

2. 会话持久化

1
2
3
4
5
// 保存用户数据目录,避免重复登录
const USER_DATA_DIR = path.join(__dirname, 'puppeteer-profile');

// WebSocket端点持久化
storeBrowserEndpoint(browser.wsEndpoint());

3. 资源监控

1
2
3
4
5
6
7
8
function checkMemoryUsage() {
const used = process.memoryUsage();
if (used.heapUsed > 800 * 1024 * 1024) { // 800MB阈值
if (global.gc) global.gc(); // 强制垃圾回收
// 必要时重启浏览器
closeBrowser(true);
}
}

错误排查流程

1. Docker环境检查清单

1
2
3
4
5
6
7
8
9
10
11
# 1. 检查容器状态
docker ps -a

# 2. 查看容器日志
docker logs container_name

# 3. 进入容器调试
docker exec -it container_name sh

# 4. 检查内存使用
docker stats

2. 问题定位优先级

  1. 超时配置 → 检查TIMEOUT和PROTOCOL_TIMEOUT比例
  2. 启动参数 → 确认必需的Docker参数是否存在
  3. 权限问题 → no-sandbox和权限配置
  4. 内存不足 → 监控容器内存使用情况

性能优化建议

1. 无头模式

1
2
3
await puppeteer.launch({
headless: "new", // 生产环境使用新无头模式
});

2. 页面复用

1
2
3
4
5
6
7
8
9
// 复用页面实例,避免重复创建
let page;
async function getPage() {
if (!page) {
const browser = await ensureBrowser();
page = await browser.newPage();
}
return page;
}

3. 内存限制

1
2
// 限制V8引擎内存
process.env.NODE_OPTIONS = '--max-old-space-size=512';

最终推荐配置

Docker Compose

1
2
3
4
5
6
7
8
9
10
11
12
version: '3.8'
services:
puppeteer:
build: .
environment:
- NODE_ENV=production
- TIMEOUT=10000
- PUPPETEER_PROTOCOL_TIMEOUT=120000
shm_size: 2g
cap_add:
- SYS_ADMIN
restart: unless-stopped

启动配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
await puppeteer.launch({
headless: "new",
userDataDir: './puppeteer-profile',
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--disable-gpu',
'--disable-software-rasterizer',
'--no-first-run',
'--disable-default-apps',
'--disable-extensions',
'--disable-background-networking',
'--disable-sync',
'--mute-audio',
'--hide-scrollbars',
'--metrics-recording-only'
]
});

总结

在资源受限环境下部署Puppeteer,关键在于:

  1. 合理配置超时参数 - 避免无意义的超时失败
  2. 优化Chromium启动参数 - 减少资源消耗,提高稳定性
  3. Docker环境适配 - 正确处理权限和共享内存问题
  4. 资源监控和管理 - 防止OOM导致的容器崩溃
  5. 浏览器实例复用 - 避免重复启动的开销

记住:简单问题不要复杂化,90%的Puppeteer Docker问题都是超时、参数缺失、权限这三类原因造成的。

欢迎关注我的其它发布渠道

-----------本文结束感谢您的阅读-----------