强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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 生成参数速查

参数类型说明
landscapebooleantrue 为横向
displayHeaderFooterboolean是否显示页眉页脚
printBackgroundboolean是否打印背景色/图片
scalenumber缩放比例 (0.1 - 2.0)
paperWidthnumber纸张宽度 (英寸, A4=8.27)
paperHeightnumber纸张高度 (英寸, A4=11.69)
marginTopnumber上边距 (英寸)
pageRangesstring页码范围, 如 "1-3,5"
headerTemplatestring页眉 HTML 模板
footerTemplatestring页脚 HTML 模板
preferCSSPageSizeboolean优先 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)

典型结果:

操作HeadlessHeaded差异
启动0.8s1.2sHeadless 快 33%
页面加载1.5s1.6s基本一致
截图0.1s0.1s一致
内存占用~100MB~200MBHeadless 省 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=newChrome 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.captureScreenshothttps://chromedevtools.github.io/devtools-protocol/tot/Page/#method-captureScreenshot
CDP Page.printToPDFhttps://chromedevtools.github.io/devtools-protocol/tot/Page/#method-printToPDF
浏览器指纹检测https://bot.sannysoft.com/
Selenium Docker 镜像https://github.com/SeleniumHQ/docker-selenium