5月28日 07:16

Puppeteer 无头模式和有头模式有什么区别?

Puppeteer 的无头模式(Headless)和有头模式(Headful)是两种浏览器运行方式,核心差异在于是否渲染图形界面,这直接决定了它们的性能表现、调试能力和适用场景。

核心区别

无头模式下浏览器不创建可视化窗口,所有页面渲染和脚本执行在内存中完成;有头模式则启动完整的 Chrome GUI 窗口,每一步操作都可以肉眼观察。

这个看似简单的差异会引发一系列连锁影响:

  • 资源消耗:无头模式省去了 GUI 渲染的开销,内存占用通常低 30%-50%,启动速度快 20% 左右
  • User Agent 差异:旧版无头模式的 UA 包含 HeadlessChrome 标识,网站可据此识别并拒绝请求;有头模式的 UA 与普通 Chrome 完全一致
  • 渲染一致性:部分网站在无头模式下的渲染结果与有头模式不同,原因包括 GPU 加速差异、字体渲染路径不同、视口默认值不一致等
  • 反爬检测:无头模式缺少 navigator.pluginswindow.chrome 等浏览器特征,更容易被反爬系统检测

三种无头模式的演进

Puppeteer 的无头模式并非一成不变,Chrome 的迭代带来了三种变体:

旧版无头模式(headless: true)

默认值,基于独立的 HeadlessChrome 实现,与正常 Chrome 共享极少代码。问题在于它的行为与真实浏览器差异较大,容易被网站检测。

javascript
const browser = await puppeteer.launch({ headless: true });

新版无头模式(headless: "new")

Chrome 112+ 引入,使用与有头模式完全相同的 Chrome 代码库,仅跳过可视化输出。渲染结果与有头模式几乎一致,推荐在新项目中优先使用。

javascript
const browser = await puppeteer.launch({ headless: "new" });

chrome-headless-shell(headless: "shell")

Puppeteer 21+ 提供,是专为自动化设计的精简二进制文件,体积更小、启动更快,但不支持扩展和部分 Chrome 特性,适合纯服务端批处理场景。

javascript
const browser = await puppeteer.launch({ headless: "shell" });

有头模式的使用方式

有头模式需要显式关闭 headless,同时可以配合 DevTools 和慢放模式辅助调试:

javascript
const browser = await puppeteer.launch({ headless: false, devtools: true, // 自动打开开发者工具 slowMo: 250 // 每步操作延迟 250ms,便于观察 });

关键配置项:slowMo 让操作可追踪,devtools 提供完整调试面板,defaultViewport 可设置视口大小。

性能对比

指标旧版 headless新版 headlessheadless shell有头模式
内存占用最低
启动速度最快
渲染一致性基准
反检测能力较强
扩展支持不支持支持不支持支持

各模式适用场景

无头模式适用于:

  • CI/CD 流水线中的自动化测试——服务器通常没有显示器
  • 大规模网页抓取——资源占用低,可并发更多实例
  • 定时任务和批量处理——截图、PDF 生成、数据采集
  • 性能基准测试——减少 GUI 对测试结果的干扰

有头模式适用于:

  • 脚本开发调试阶段——实时观察页面行为,快速定位问题
  • 复杂交互场景调试——如动画、拖拽、弹窗等需要视觉确认的操作
  • 反爬对抗——部分网站检测到无头特征后拒绝服务,有头模式可以绕过
  • 教学演示——展示自动化流程的每一步

无头模式被检测的常见原因及应对

实际项目中,无头模式最常见的坑就是被网站识别。以下是被检测的主要原因和解决思路:

User Agent 泄露:旧版 headless 的 UA 包含 HeadlessChrome,解决方法是手动覆盖:

javascript
await page.setUserAgent( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" );

浏览器特征缺失:无头模式下 navigator.plugins 为空、navigator.languages 仅含 "en-US"、缺少 window.chrome 对象。可使用 puppeteer-extra-plugin-stealth 自动修补:

javascript
const puppeteer = require("puppeteer-extra"); const StealthPlugin = require("puppeteer-extra-plugin-stealth"); puppeteer.use(StealthPlugin()); const browser = await puppeteer.launch({ headless: "new" });

WebGL 和 Canvas 指纹:无头模式下 GPU 加速不可用,Canvas 指纹与有头模式不同。新版 headless 模式已大幅改善此问题。

最佳实践:优先使用新版无头模式(headless: "new")+ stealth 插件,绝大多数场景下可获得与有头模式一致的渲染和反检测效果。

环境切换的工程实践

在实际项目中,通常需要根据运行环境动态切换模式:

javascript
const puppeteer = require("puppeteer"); const isDev = process.env.NODE_ENV === "development"; const browser = await puppeteer.launch({ headless: isDev ? false : "new", devtools: isDev, slowMo: isDev ? 100 : 0, args: isDev ? [] : ["--no-sandbox", "--disable-setuid-sandbox"] });

开发环境用有头模式便于调试,生产环境用新版无头模式兼顾性能和一致性。--no-sandbox 参数在 Docker 等容器环境中通常必需,因为默认的沙箱机制需要特定内核权限。

面试追问方向

  • Puppeteer 新版无头模式与旧版的核心实现差异是什么?(共享 Chrome 代码库 vs 独立实现)
  • 如何让无头模式通过反爬检测?(stealth 插件 + 新版 headless + UA 覆盖)
  • chrome-headless-shell 适合什么场景?有什么限制?(纯服务端批处理,不支持扩展)
  • 为什么同样的代码在无头和有头模式下渲染结果不同?(GPU 加速、字体渲染、视口默认值差异)
标签:Puppeteer