Chromium / ChromeDriver 完全指南 / 05 - 无头模式与输出
05 - 无头模式与输出
掌握 Headless 模式的使用与优化,实现网页截图、PDF 生成、资源节省等生产级能力。
5.1 什么是无头模式
无头模式 (Headless Mode) 是指不显示浏览器 GUI 界面运行浏览器。浏览器在后台执行所有正常的渲染和 JavaScript,但不打开可见窗口。
┌──────────────────────────────────────────────┐
│ 有头模式 (Headed) │
│ ┌────────────────────────────────────────┐ │
│ │ 浏览器窗口 (可见) │ │
│ │ ┌──────────────────────────────────┐ │ │
│ │ │ 网页内容 │ │ │
│ │ └──────────────────────────────────┘ │ │
│ └────────────────────────────────────────┘ │
│ 需要显示器/X11/桌面环境 │
└──────────────────────────────────────────────┘
┌──────────────────────────────────────────────┐
│ 无头模式 (Headless) │
│ Chrome 进程 (后台) │
│ ├── 渲染引擎 (Blink) ✅ 正常工作 │
│ ├── JavaScript (V8) ✅ 正常执行 │
│ ├── 网络栈 ✅ 正常请求 │
│ ├── GPU 加速 ⚠️ 可能受限 │
│ └── 窗口管理 ❌ 不渲染 │
│ 不需要显示器,资源占用更低 │
└──────────────────────────────────────────────┘
有头 vs 无头对比
| 维度 | 有头模式 (Headed) | 无头模式 (Headless) |
|---|---|---|
| 显示器需求 | 需要 (或虚拟显示器) | 不需要 |
| 内存占用 | 较高 (150-300MB/实例) | 较低 (80-150MB/实例) |
| CPU 占用 | 较高 (渲染 GUI) | 较低 |
| 执行速度 | 正常 | 略快 (无 GUI 渲染) |
| 截图 | ✅ | ✅ |
| PDF 生成 | ✅ | ✅ |
| 扩展支持 | ✅ | 旧版不支持,--headless=new 支持 |
| 指纹差异 | 与普通浏览器一致 | 可能被检测到 |
| 适用场景 | 开发调试、录制演示 | CI/CD、爬虫、服务器 |
5.2 启用无头模式
Chrome 112+ — 新版无头 (推荐)
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
options = Options()
options.add_argument("--headless=new") # 推荐: Chrome 112+ 新版无头
driver = webdriver.Chrome(options=options)
driver.get("https://example.com")
print(driver.title)
driver.quit()
旧版无头 (兼容模式)
options = Options()
options.add_argument("--headless") # 旧版无头模式
新旧无头模式对比
| 特性 | --headless (旧) | --headless=new (新) |
|---|---|---|
| Chrome 版本 | 所有版本 | 112+ |
| 实现方式 | 独立无头实现 | 与有头模式共享代码 |
| 行为一致性 | 与有头模式有差异 | 与有头模式高度一致 |
| 扩展支持 | ❌ | ✅ |
| 网络行为 | 可能有差异 | 完全一致 |
| UserAgent | 包含 “HeadlessChrome” | 与正常 Chrome 一致 |
| Canvas 指纹 | 不同 | 与正常 Chrome 一致 |
| 推荐程度 | 仅在旧版本中使用 | ✅ 推荐 |
关键区别: 旧版无头会在 UserAgent 中暴露
HeadlessChrome,容易被网站检测。新版无头不会。
5.3 无头模式截图
全页面截图
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
options = Options()
options.add_argument("--headless=new")
options.add_argument("--window-size=1920,1080")
driver = webdriver.Chrome(options=options)
driver.get("https://example.com")
# 方法 1: 保存为文件
driver.save_screenshot("/tmp/page.png")
# 方法 2: 获取二进制数据
png_data = driver.get_screenshot_as_png()
with open("/tmp/page.png", "wb") as f:
f.write(png_data)
# 方法 3: 获取 Base64
b64_data = driver.get_screenshot_as_base64()
driver.quit()
元素截图
element = driver.find_element(By.CSS_SELECTOR, ".main-content")
element.screenshot("/tmp/element.png")
长页面滚动截图
import time
def full_page_screenshot(driver, output_path):
"""截取完整长页面"""
# 获取页面总高度
total_height = driver.execute_script(
"return Math.max(document.body.scrollHeight, "
"document.documentElement.scrollHeight);"
)
viewport_height = driver.execute_script("return window.innerHeight;")
# 设置窗口高度为页面总高度
driver.set_window_size(1920, total_height)
time.sleep(0.5)
# 截图
driver.save_screenshot(output_path)
# 恢复窗口大小
driver.set_window_size(1920, 1080)
# 使用
full_page_screenshot(driver, "/tmp/full_page.png")
自定义截图参数 (CDP)
# 使用 CDP 精确控制截图
result = driver.execute_cdp_cmd("Page.captureScreenshot", {
"format": "png", # png, jpeg, webp
"quality": 90, # jpeg/webp 质量 (0-100)
"clip": { # 裁剪区域
"x": 0,
"y": 0,
"width": 1920,
"height": 1080,
"scale": 1
},
"captureBeyondViewport": True, # 截取视口外内容
"fromSurface": True
})
import base64
with open("/tmp/custom.png", "wb") as f:
f.write(base64.b64decode(result["data"]))
5.4 PDF 生成
基本 PDF 导出
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
options = Options()
options.add_argument("--headless=new")
driver = webdriver.Chrome(options=options)
driver.get("https://example.com")
# 方法 1: 使用 CDP 打印为 PDF
result = driver.execute_cdp_cmd("Page.printToPDF", {
"landscape": False, # 横向
"displayHeaderFooter": True, # 显示页眉页脚
"printBackground": True, # 打印背景色
"scale": 1.0, # 缩放
"paperWidth": 8.27, # A4 宽度 (英寸)
"paperHeight": 11.69, # A4 高度
"marginTop": 0.4, # 上边距 (英寸)
"marginBottom": 0.4,
"marginLeft": 0.4,
"marginRight": 0.4,
"pageRanges": "1-5", # 页码范围
"headerTemplate": "<div style='font-size:8px;text-align:center;width:100%'>"
"<span class='title'></span></div>",
"footerTemplate": "<div style='font-size:8px;text-align:center;width:100%'>"
"Page <span class='pageNumber'></span> of "
"<span class='totalPages'></span></div>",
"preferCSSPageSize": True # 优先使用 CSS @page 尺寸
})
import base64
with open("/tmp/page.pdf", "wb") as f:
f.write(base64.b64decode(result["data"]))
print("✅ PDF 生成完成: /tmp/page.pdf")
driver.quit()
PDF 生成参数速查
| 参数 | 类型 | 说明 |
|---|---|---|
landscape | boolean | true 为横向 |
displayHeaderFooter | boolean | 是否显示页眉页脚 |
printBackground | boolean | 是否打印背景色/图片 |
scale | number | 缩放比例 (0.1 - 2.0) |
paperWidth | number | 纸张宽度 (英寸, A4=8.27) |
paperHeight | number | 纸张高度 (英寸, A4=11.69) |
marginTop | number | 上边距 (英寸) |
pageRanges | string | 页码范围, 如 "1-3,5" |
headerTemplate | string | 页眉 HTML 模板 |
footerTemplate | string | 页脚 HTML 模板 |
preferCSSPageSize | boolean | 优先 CSS @page 大小 |
PDF 封面页技巧
# 生成带封面的 PDF 报告
driver.get("https://example.com/dashboard")
# 注入封面样式
driver.execute_script("""
var style = document.createElement('style');
style.textContent = '@media print { body { page-break-after: always; } }';
document.head.appendChild(style);
""")
result = driver.execute_cdp_cmd("Page.printToPDF", {
"printBackground": True,
"paperWidth": 8.27,
"paperHeight": 11.69
})
5.5 资源优化
禁用不必要的资源
options = Options()
options.add_argument("--headless=new")
# 禁用图片加载 (通过 CDP)
# 需要在页面加载前设置
driver = webdriver.Chrome(options=options)
# 启用网络拦截
driver.execute_cdp_cmd("Network.enable", {})
driver.execute_cdp_cmd("Network.setBlockedURLs", {
"urls": [
"*.png", "*.jpg", "*.jpeg", "*.gif", "*.webp", # 图片
"*.css", # 样式表
"*.woff", "*.woff2", "*.ttf", "*.eot", # 字体
"*google-analytics.com*", # 分析
"*doubleclick.net*", # 广告
]
})
driver.get("https://example.com")
降低内存占用
options = Options()
options.add_argument("--headless=new")
options.add_argument("--disable-gpu")
options.add_argument("--disable-dev-shm-usage")
options.add_argument("--no-sandbox")
# 禁用渲染器相关
options.add_argument("--disable-software-rasterizer")
options.add_argument("--disable-extensions")
options.add_argument("--disable-background-networking")
# 限制进程数
options.add_argument("--renderer-process-limit=2")
# 使用共享内存
options.add_argument("--single-process") # 单进程模式 (不推荐生产)
# 降低渲染质量
options.add_argument("--disable-smooth-scrolling")
options.add_argument("--disable-low-res-tiling")
内存占用对比
| 配置 | 单实例内存 | 说明 |
|---|---|---|
| 有头模式 (默认) | 150-300 MB | 完整渲染 |
| 无头模式 (默认) | 80-150 MB | 无 GUI |
| 无头 + 禁用图片 | 50-100 MB | 跳过图片解码 |
| 无头 + 禁用 CSS/图片 | 40-80 MB | 最小化渲染 |
| 无头 + 单进程 | 30-60 MB | 不推荐,稳定性差 |
5.6 设备模拟与打印模拟
设备模拟
# 模拟移动设备 (iPhone 14)
driver.execute_cdp_cmd("Emulation.setDeviceMetricsOverride", {
"width": 390,
"height": 844,
"deviceScaleFactor": 3,
"mobile": True
})
# 设置 UserAgent
driver.execute_cdp_cmd("Network.setUserAgentOverride", {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) "
"AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 "
"Mobile/15E148 Safari/604.1"
})
# 模拟地理位置
driver.execute_cdp_cmd("Emulation.setGeolocationOverride", {
"latitude": 39.9042,
"longitude": 116.4074,
"accuracy": 100
})
# 模拟网络条件
driver.execute_cdp_cmd("Network.emulateNetworkConditions", {
"offline": False,
"latency": 100, # 延迟 ms
"downloadThroughput": 1024 * 1024 * 1.5, # 1.5 Mbps
"uploadThroughput": 1024 * 512 # 512 Kbps
})
打印媒体模拟
# 模拟打印媒体 (用于 PDF 生成)
driver.execute_cdp_cmd("Emulation.setEmulatedMedia", {
"media": "print"
})
# 模拟暗色模式
driver.execute_cdp_cmd("Emulation.setEmulatedMedia", {
"colorScheme": "dark"
})
# 模拟色彩偏好
driver.execute_cdp_cmd("Emulation.setEmulatedMedia", {
"features": [{"name": "prefers-reduced-motion", "value": "reduce"}]
})
5.7 容器中运行无头浏览器
Docker 中的 Chrome
# 使用官方 Selenium Chrome 镜像
docker run -d --name chrome \
-p 4444:4444 \
-p 7900:7900 \
selenium/standalone-chrome:latest
# 通过 noVNC 查看浏览器 (调试用)
# 访问 http://localhost:7900 密码: secret
容器内必要参数
options = Options()
options.add_argument("--headless=new")
options.add_argument("--no-sandbox") # Docker 中必须
options.add_argument("--disable-dev-shm-usage") # Docker 中必须
options.add_argument("--disable-gpu")
options.add_argument("--window-size=1920,1080")
# Docker 中 /dev/shm 默认只有 64MB,Chrome 容易 OOM
# 解决方案 1: 添加 --disable-dev-shm-usage
# 解决方案 2: 启动容器时 --shm-size=2g
5.8 性能对比基准
import time
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
def benchmark(headless=True):
options = Options()
if headless:
options.add_argument("--headless=new")
options.add_argument("--no-sandbox")
options.add_argument("--disable-dev-shm-usage")
# 启动时间
start = time.time()
driver = webdriver.Chrome(options=options)
startup = time.time() - start
# 页面加载
start = time.time()
driver.get("https://example.com")
page_load = time.time() - start
# 截图
start = time.time()
driver.save_screenshot("/tmp/bench.png")
screenshot = time.time() - start
driver.quit()
print(f"模式: {'Headless' if headless else 'Headed'}")
print(f"启动: {startup:.2f}s")
print(f"加载: {page_load:.2f}s")
print(f"截图: {screenshot:.2f}s")
benchmark(headless=True)
benchmark(headless=False)
典型结果:
| 操作 | Headless | Headed | 差异 |
|---|---|---|---|
| 启动 | 0.8s | 1.2s | Headless 快 33% |
| 页面加载 | 1.5s | 1.6s | 基本一致 |
| 截图 | 0.1s | 0.1s | 一致 |
| 内存占用 | ~100MB | ~200MB | Headless 省 50% |
5.9 无头模式检测与规避
部分网站会检测无头模式并拒绝服务。以下是常见检测手段和应对方法。
常见检测方式
// 1. 检测 UserAgent (旧版无头)
navigator.userAgent.includes("HeadlessChrome") // --headless 旧版
// 2. 检测插件数量 (无头模式插件为 0)
navigator.plugins.length === 0
// 3. 检测语言 (无头可能返回空)
navigator.languages.length === 0
// 4. WebGL 渲染器
canvas.getContext('webgl').getExtension('WEBGL_debug_renderer_info')
// 5. 检测 Permissions API
Notification.permission === "denied"
规避方法
options = Options()
options.add_argument("--headless=new") # 新版无头已修复大部分检测
# 额外措施
options.add_argument("--disable-blink-features=AutomationControlled")
# 移除 webdriver 标志
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": """
Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3] });
Object.defineProperty(navigator, 'languages', { get: () => ['zh-CN', 'zh', 'en'] });
window.chrome = { runtime: {} };
"""
})
5.10 要点回顾
| 要点 | 说明 |
|---|---|
使用 --headless=new | Chrome 112+ 新版无头,与有头模式行为一致 |
| 截图用 CDP 更灵活 | Page.captureScreenshot 支持裁剪、格式、质量控制 |
PDF 用 Page.printToPDF | 支持页面大小、边距、页眉页脚自定义 |
| 禁用不必要资源 | 图片、CSS、字体可按需禁用以节省资源 |
| Docker 必加参数 | --no-sandbox + --disable-dev-shm-usage |
| 新版无头难以检测 | --headless=new 的 UserAgent 和指纹与正常 Chrome 一致 |
5.11 注意事项
⚠️
/dev/shm不足: Docker 默认/dev/shm仅 64MB,Chrome 渲染大型页面会 OOM,务必添加--disable-dev-shm-usage或--shm-size=2g。⚠️ 无头模式 Canvas 指纹: 旧版无头的 Canvas 指纹与有头模式不同,新版已修复,但部分 WebGL 渲染仍有差异。
⚠️ PDF 生成需要字体: 服务器环境可能缺少中文字体,需安装
fonts-noto-cjk等字体包。⚠️ 截图分辨率: 无头模式默认视口可能与预期不同,建议显式设置
--window-size。
5.12 扩展阅读
| 资源 | 链接 |
|---|---|
| Chrome Headless 模式 | https://developer.chrome.com/blog/headless-chrome |
| CDP Page.captureScreenshot | https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-captureScreenshot |
| CDP Page.printToPDF | https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-printToPDF |
| 浏览器指纹检测 | https://bot.sannysoft.com/ |
| Selenium Docker 镜像 | https://github.com/SeleniumHQ/docker-selenium |