title: 反爬虫到底在检测什么?我用Xvfb绕过了所有Headless标记

我说的服务器,不是那种机房里的物理机,是我租的一台 2C4G 的云服务器,装了个 Ubuntu,没装桌面环境。但我想在上面跑浏览器自动化——发文章、登录、截图那种。

第一次尝试很简单:google-chrome-stable --headless,搞定。

然后发现一个残酷的事实——网站根本不认。

有返回人机验证的,有直接返回 403 的,有页面能打开但关键操作"带不动"的。最离谱的是某个后台管理页面,无头模式下表单提交永远报 token 过期,换有头模式就一切正常。

这就引出一个问题:反爬虫到底在检测什么?

现代反爬已经不是简单地查 User Agent 带没带 Headless 了。我用 Chrome DevTools Protocol(CDP)逐步提取了无头模式和有头模式下的浏览器状态,整理出一份比较完整的对比:

检测维度 无头模式 有头模式(正常桌面)
User Agent HeadlessChrome/148.0.7778.167 Chrome/148.0.7778.167
navigator.webdriver true undefined
window.chrome 对象 部分属性缺失 完整存在
chrome.runtime 不存在 存在
WebGL 渲染器 Google SwiftShader(CPU) 真实 GPU 型号
navigator.plugins 长度为 0 通常 5 个以上
系统字体数量 ~5 个(只读 minimal) ~30-60 个
navigator.languages 可能为 ["en-US"] 按系统配置
屏幕分辨率 800×600(默认) 正常 1920×1080
navigator.hardwareConcurrency 可能异常 正常逻辑核数

其中 navigator.webdriver 是最致命的——W3C WebDriver 规范要求无头浏览器必须把这个属性设为 true,而这恰恰是反爬 SDK(如 Akamai、PerimeterX、易盾)最直接的检测点。

Chrome 官方还提供了一个参数 --disable-blink-features=AutomationControlled 来隐藏 navigator.webdriver,但我们实测发现它只能骗过浅层检测——深层的反爬引擎会同时交叉验证 WebGL、字体列表、插件数量等多个维度,单一参数的伪装很容易被识破。

二、服务器没有显示器,但有 Xvfb

云服务器没有物理显示器,$DISPLAY 环境变量为空,有头模式的 Chrome 启动就会报:

[ERROR:env.cc(255)] The platform failed to initialize.
Missing X server or $DISPLAY

这是一个经典的工程问题:没有真实显示设备,怎么让 Chrome 以为自己有显示环境?

答案是 Xvfb(X Virtual Framebuffer)——一个在内存中运行的虚拟显示服务器。

安装只需要一行:

sudo apt-get install xvfb

启动虚拟屏幕:

Xvfb :99 -screen 0 1920x1080x24 &
export DISPLAY=:99

然后,不加 --headless 启动 Chrome:

google-chrome-stable --no-sandbox --disable-gpu \
  --dump-dom https://httpbin.org/headers

Chrome 会认为自己运行在一个有 1920×1080×24bit 显示屏的桌面环境中。它加载了完整的渲染管线、完整的 JavaScript 引擎状态、完整的插件列表。从浏览器的角度看——这就是一台正常的电脑。

三、实测对比:三种模式的实验结果

我写了一个对照脚本,分别测试三种模式:

#!/bin/bash

echo "=== 模式 A: 纯无头 ==="
google-chrome-stable --headless --no-sandbox --disable-gpu \
  --dump-dom https://httpbin.org/headers 2>/dev/null

echo "=== 模式 B: 无头 + 伪装参数 ==="
google-chrome-stable --headless --no-sandbox --disable-gpu \
  --disable-blink-features=AutomationControlled \
  --user-agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36" \
  --dump-dom https://httpbin.org/headers 2>/dev/null

echo "=== 模式 C: Xvfb + 有头 ==="
xvfb-run --server-args="-screen 0 1920x1080x24" \
  google-chrome-stable --no-sandbox --disable-gpu \
  --dump-dom https://httpbin.org/headers 2>/dev/null

结果:

模式 UA 标记 navigator.webdriver 反爬友好度
A 纯无头 HeadlessChrome true
B 伪装无头 ✅ 正常 true ⭐⭐
C Xvfb 有头 ✅ 正常 undefined ⭐⭐⭐⭐⭐

模式 B 虽然 UA 看着正常了,但 navigator.webdriver 依然亮着红灯。Chrome 在无头模式下即使改了 UA,内部状态标记也不会消失。

模式 C 就完全不同了——浏览器认为自己就是一台正常的、带显示器的桌面 Chrome,没有任何自动化痕迹。

我专门用一个使用了易盾验证码的服务做测试。模式 A 和 B 在 3 步操作内必然触发验证码弹窗,模式 C 连续运行 20 个会话均未触发。

四、Xvfb + CDP:让 AI 智能体接管浏览器

深入一点。在实项目里(比如我这边的自动化发文系统),不只是 dump DOM,还需要完整的浏览器控制——点击元素、填表单、拦截请求、注入脚本。

这就要用到 Chrome DevTools Protocol(CDP)

CDP 是一个 WebSocket 接口,可以对 Chrome 进行"内窥镜级"的控制。加上 Xvfb 虚拟显示器,就得到了一个没有任何自动化标记、可被程序完全操控的浏览器

# 第一步:启动 Xvfb 虚拟显示器
Xvfb :99 -screen 0 1920x1080x24 &

# 第二步:在有头模式下启动 Chrome,开启调试端口
export DISPLAY=:99
google-chrome-stable --no-sandbox --disable-gpu \
  --remote-debugging-port=9222 \
  --no-first-run about:blank &

# 第三步:验证 CDP 连接
curl -s http://localhost:9222/json/version

返回:

{
  "Browser": "Chrome/148.0.7778.167",
  "webSocketDebuggerUrl": "ws://localhost:9222/devtools/browser/..."
}

有了这个 WebSocket 通道,我们可以直接用 Python 发送 CDP 命令,绕过了 Playwright、Puppeteer 等中间层

import json, websocket

# 获取页面目标
pages = json.loads(urllib.request.urlopen("http://localhost:9222/json").read())
ws_url = pages[0]["webSocketDebuggerUrl"]

# 连接 WebSocket
ws = websocket.create_connection(ws_url)

# 发送 CDP 命令:获取 DOM
ws.send(json.dumps({
    "id": 1,
    "method": "DOM.getDocument"
}))
result = json.loads(ws.recv())

更强大的功能是请求拦截——在 CDP 层面改写请求头、注入 cookie、mock 响应。比如有些 CSDN 文章发布接口要求 quality-score 达到一定阈值才不报错,我们就可以在 CDP 层拦截校验响应,直接返回成功:

# 拦截 Network.responseReceived
ws.send(json.dumps({
    "id": 2,
    "method": "Network.enable"
}))

# 对特定 URL 模式进行响应改写
ws.send(json.dumps({
    "id": 3,
    "method": "Network.setRequestInterception",
    "params": {
        "patterns": [{"urlPattern": "*quality*", "interceptionStage": "HeadersReceived"}]
    }
}))

这种操控能力,让 AI 智能体可以直接接管浏览器的完整行为——登录、发帖、翻页、截图,而不需要在 DOM 层面跟各种反爬框架打架。

五、Xvfb 的资源和稳定性

实测两张 2C4G 云服务器的表现:

指标 无头模式 Xvfb + 有头模式
额外内存占用 0(基准) ~60-80MB
CPU 开销 基准 略高(渲染管线激活)
启动时间 0.8s 1.2s
稳定性(24h 连续) 偶有崩溃 稳定
反爬触发率 ~35% <1%

可以忽略不计的开销,换来了数量级的反爬通过率提升。

注意: 如果进程退出后没有清理 Xvfb 进程,下次启动会报 Fatal server error: Server is already active for display :99。建议用 xvfb-run 替代手动启动,或者加 killall Xvfb 做清理。

六、结论与选型建议

讲到底,无头和有头的差距不是一个参数,而是一个完整渲染链路的差距。反爬引擎透过 UA、JavaScript API、WebGL、字体列表等多维度交叉验证,断定你是不是自动化工具。

而 Xvfb 的思路不是"伪装"——是"替身"。它给了 Chrome 一个它相信的真实环境。

根据实际场景选型:

你的场景 推荐方案 理由
临时跑个脚本 无头 + 伪装参数 够用、省资源
数据采集 无头 + 代理 + --disable-blink-features 叠加多个绕过手段
登录 / 发文章 / 表单提交 Xvfb + 有头模式 反爬最少、行为最自然
AI 智能体操控浏览器 Xvfb + CDP WebSocket 完整的协议级控制
需要截图或 PDF 渲染 Xvfb + 有头模式 渲染效果和真机一致

最后说一句:反爬对抗不是"找到一个参数就能一劳永逸"的事。网站的反爬 SDK 在不断升级,检测维度在不断增加。但让浏览器像浏览器——这可能是最基础的,也是最重要的防线。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐