隐身浏览器基准测试:Patchright、nodriver 与 Camoufox

54 次运行、五款自动化工具、两类 IP。每一项机器人判定都由浏览器层面的隐身能力决定;住宅 IP 只影响网络层的检查,而且并不总是变得更好。

Stealth Browser Benchmark: Patchright, nodriver, Camoufox (2026)
本文目录

2026 年 10 月 3 日,我们做了一次小规模的隐身浏览器(stealth browser)基准测试:以原版 Playwright 为对照组,再测试 Patchright、nodriver、Camoufox 和 Zendriver,全部访问公开的机器人检测测试页,一次使用 Hetzner 数据中心 IP,一次通过美国住宅代理。所有自动化判定都由浏览器本身决定。原版 Playwright 在两种 IP 下的每一次运行中,都被 deviceandbrowserinfo、BrowserScan、Fingerprint 的 playground 和 Pixelscan 标记。以有头模式运行时,四款隐身工具在所有给出判定的运行中都通过了前三项检测,Pixelscan 在它们 39 次有头运行中标记了 2 次(另有 2 次为 n/a)。住宅 IP 只影响了那些检查网络层的项目,而且并不总是往好的方向变。

这是在一台主机、一个代理国家上记录的 54 次运行,针对的是专门用于测试的页面,并不是对任何生产环境反爬系统的排名。下文依次是:方法、原始判定结果、数据不能支持的结论,以及复现所需的代码。

隐身浏览器基准测试:简短结论

如果浏览器泄露了自动化特征,住宅 IP 也救不了它。如果浏览器本身干净,IP 主要影响网络层的检查:代理检测、时区一致性和 WebRTC。在这些页面上,影响有正有负:

  • 自动化判定没有随 IP 改变。Playwright 在全部 10 次有头运行中、两种网络下,都在 deviceandbrowserinfo 上得到 isBot: true,在 BrowserScan 上得到 “WebDriver: Robot”,在 Fingerprint 的 playground 上得到 “bad bot (type: webdriver)”。Patchright、nodriver、Zendriver 和 Camoufox 在任何一次有头运行中都没有被打上这些标记。
  • Pixelscan 在意的是数据中心 IP。来自 Hetzner IP 的 20 次运行中,它 20 次都显示 “Proxy detected”,尽管这些运行根本没用代理。通过住宅代理时,34 次运行中只有 3 次出现该提示。
  • Fingerprint 的 playground 在意的是住宅 IP。它的代理信号在 34 次住宅运行中标记了 33 次,还经常点名代理网络(“residential proxy from SOAX”“from INFATICA”等)。对于基于 Chrome 的工具,它没有标记 Hetzner IP。三款基于 Chrome 的隐身工具,可疑分数在数据中心下为 0–6,通过粘性住宅代理时为 8–24。
  • 五款工具中有三款在走代理时通过 WebRTC 暴露了服务器的真实 IP。Playwright、Patchright 和 nodriver 在每一次走代理的运行中都暴露了。Zendriver 默认屏蔽 WebRTC,Camoufox 则会伪装它。
  • 无头模式比 IP 影响更大。在无头模式下,Patchright 和 nodriver 在有头时能通过的同一批页面上被标记了。

测试内容:版本、网络条件、目标页面

所有测试都在一个带 Xvfb 的 Debian 12 容器中进行,每次只运行一个浏览器,宿主机为 4 核 Linux。每款工具只使用其官方文档推荐的启动配置,别无其他:不自定义 User-Agent、不注入指纹、不模拟鼠标移动、不屏蔽资源。对 Patchright 来说,就是 README 中的“Best Practice”配置:持久化 context、真实 Chrome、有头模式、no_viewport=True。

工具包浏览器启动方式
Playwright(对照组)playwright 1.62.0Playwright Chromium 151.0.7922.34chromium.launch(headless=False),默认设置
Patchrightpatchright 1.63.0Google Chrome 154.0.8037.97launch_persistent_context(channel="chrome", headless=False, no_viewport=True)
nodrivernodriver 0.50.3Google Chrome 154.0.8037.97start(headless=False, sandbox=False)
Zendriverzendriver 0.17.1Google Chrome 154.0.8037.97start(headless=False, sandbox=False)
Camoufoxcamoufox 0.5.6Camoufox 152.0.4-beta.31AsyncCamoufox(headless=False, os="linux", geoip=True)

先说明两处妥协。对照组用的是 Playwright 1.62.0 而不是当前的 1.63.0,因为 camoufox 0.5.6 要求 playwright<1.63,而所有工具共用一个环境。另外,Camoufox 以 os="linux" 运行且只带 Linux 字体,因为宿主机磁盘几乎已满,我们没有解压 macOS 和 Windows 字体包。Camoufox 的默认行为是随机选择操作系统。

网络条件:

  • 数据中心:宿主机自身的 IP,AS24940 Hetzner Online GmbH,赫尔辛基(据 ipinfo.io)。
  • 住宅、粘性:美国住宅出口,每次运行使用新的粘性会话。所有走代理的运行中,各工具都通过同一个小型本地中继连接。它负责添加代理认证并统计字节数,因为 nodriver 和 Zendriver 无法在 Chrome 启动参数里传递代理凭据。
  • 三种附加条件:一轮轮换住宅测试(每个连接都换新出口 IP)、一轮预热测试(下文解释),以及每款工具在数据中心 IP 下的一次无头运行。

目标页面按以下顺序访问,每次运行都用全新浏览器,每个页面开一个新标签页:bot.sannysoft.com、BrowserScan 机器人检测、deviceandbrowserinfo “Are you a bot?”、CreepJS、Fingerprint 的 playground、nowsecure.nl(位于 Cloudflare 之后;直接用 curl 会得到 403 和 cf-mitigated: challenge),以及 Pixelscan 的机器人检测和指纹检测。Fingerprint 的机器人检测产品页被剔除了,因为它只展示一份静态 JSON 示例,而不是实时判定。

测量内容:页面是否加载、页面打印出的原样判定,以及判定出现所需的时间(每 0.5 秒轮询一次,超时 45 秒)。每次访问我们都保存了页面文本和整页截图。无法解析的判定记为“unparsed”,绝不猜测。有四次有头访问属于这种情况,其中三次页面超时,一次在页面更新中途被抓取。它们在表格中计为 n/a。

运行次数:数据中心、轮换住宅和粘性住宅条件下,每款工具各 3 次;无头和预热条件下每款工具各 1 次。共计 54 次运行,时间为 UTC 06:50 至 08:02。

结果:浏览器层面的自动化检测

表中统计的是运行次数:“3/3”表示页面在三次运行中都标记了该浏览器。每个单元格都来自解析后的结果文件。

工具网络sannysoft 失败项BrowserScan 标记deviceandbrowserinfo isBotCreepJS headlessFingerprint “Bot”Pixelscan 机器人横幅
Playwright(对照组)数据中心(Hetzner)WebDriver3/33/333%3/3:You are a bad bot (type: webdriver)3/3
Playwright(对照组)美国住宅,粘性WebDriver3/33/333%3/3:You are a bad bot (type: webdriver)3/3
Patchright数据中心(Hetzner)无0/30/30%Not detected (3/3)0/3
Patchright美国住宅,粘性无0/30/30%Not detected (3/3)0/3
nodriver数据中心(Hetzner)无0/30/30%Not detected (3/3)0/3
nodriver美国住宅,粘性无0/30/30%Not detected (3/3)0/2(+1 n/a)
Zendriver数据中心(Hetzner)无0/30/30%Not detected (3/3)0/3
Zendriver美国住宅,粘性无0/30/30%Not detected (3/3)0/3
Camoufox数据中心(Hetzner)Chrome0/30/30%Not detected (3/3)0/3
Camoufox美国住宅,粘性Chrome0/30/30%Not detected (3/3)0/3

表格没体现的几个细节:

  • Camoufox 在 sannysoft 上唯一的失败项是 “Chrome (New): missing”。对基于 Firefox 的浏览器来说这是意料之中的,并不是自动化泄露。
  • CreepJS 还会给出一个 “% like headless” 分数。在每次有头运行中,Playwright 为 50%,三款基于 Chrome 的隐身工具为 44%,Camoufox 为 6%。它的 “% stealth” 分数对所有工具都是 0%。CreepJS 并不把这些数字标为通过或失败。
  • Pixelscan 的机器人检测翻转过两次。Patchright 在唯一一次预热运行中得到 “Bot Behavior Detected”(CDP 一项),Zendriver 在轮换测试中出现过一次。两者在截图中都能看到。另有两次 Pixelscan 访问为 n/a:一次超时;另一次我们的文本抓取恰好赶上页面更新中途,片刻之后的截图显示 “You're Definitely a Human”。
  • nowsecure.nl 在每次有头运行中都显示了页面内容,屏幕上没有残留挑战,包括从数据中心 IP 运行的原版 Playwright。在有头模式下,它完全区分不出这些工具。

这些检测为什么能抓到原版 Playwright(navigator.webdriver 标志、CDP Runtime.enable 的副作用、启动参数),请参阅我们的 Patchright 深度解析和浏览器指纹识别解析。

结果:与 IP 相关的信号,数据中心 vs 住宅

只有两个页面会对网络打分。Fingerprint 的 playground 展示 VPN、代理、IP 黑名单和可疑分数。Pixelscan 的指纹检测展示代理和位置一致性。deviceandbrowserinfo 明确表示其测试“不使用 IP 信誉或用户行为”,它的判定不随 IP 变化,与这一说法相符。

工具网络Fingerprint 代理标记Fingerprint VPN 标记Fingerprint 可疑分数Pixelscan “Proxy detected”Pixelscan 位置检查WebRTC 在代理后泄露服务器 IP
Playwright(对照组)数据中心(Hetzner)0/30/3193/3Timezone spoofedn/a(未用代理,显示真实 IP 属正常)
Playwright(对照组)美国住宅,粘性2/30/325–270/3Timezone spoofed3/3
Patchright数据中心(Hetzner)0/30/30–63/3Timezone spoofedn/a(未用代理,显示真实 IP 属正常)
Patchright美国住宅,粘性3/30/38–200/3Timezone spoofed3/3
nodriver数据中心(Hetzner)0/30/30–63/3Timezone spoofedn/a(未用代理,显示真实 IP 属正常)
nodriver美国住宅,粘性3/32/310–230/3Timezone spoofed3/3
Zendriver数据中心(Hetzner)0/30/30–63/3Timezone spoofed已屏蔽(3/3)
Zendriver美国住宅,粘性3/30/38–240/3Timezone spoofed已屏蔽(3/3)
Camoufox数据中心(Hetzner)3/33/3173/3Timezone spoofedn/a(未用代理,显示真实 IP 属正常)
Camoufox美国住宅,粘性3/30/34–141/3匹配(显示美国城市)0/3

住宅 IP 起作用的地方

Pixelscan 不再显示 “Proxy detected”。在每次数据中心运行中它都显示了这条提示,尽管根本没有使用代理,这说明它把托管服务商的 ASN 当作代理。通过住宅代理池时,15 次粘性运行中只出现了 1 次。Camoufox 还得到了匹配的位置。它的 geoip=True 选项会根据出口 IP 设置时区、语言区域、地理位置和 WebRTC IP,因此 Pixelscan 显示的是一个美国城市,而不是 “Timezone spoofed”。

住宅 IP 帮倒忙或毫无作用的地方

Fingerprint 的代理信号在 34 次走代理的运行中,有 33 次把住宅出口识别为代理 IP,三种代理模式都是如此,而且很多还附上了服务商名称。我们的理解是 Fingerprint 掌握了住宅代理池的情报。这并不意味着每个 IP 都泄露了什么,也说明不了某个具体网站会如何使用这一信号。对于基于 Chrome 的隐身工具,可疑分数在走代理后不降反升。Playwright 从 19 升到 25–27,唯一一次预热运行得了 47 分。

其中一部分很可能是自己造成的。基于 Chrome 的工具保留了容器的 UTC 时区,所以美国 IP 配 UTC 时钟,在每次走代理的运行中都被 Pixelscan 判为 “Timezone spoofed”。Camoufox 从反方向展示了同样的机制。在赫尔辛基数据中心 IP 下,它的 GeoIP 查询把出口定位到 Europe/Berlin,而 ipinfo 显示的是赫尔辛基。于是 Fingerprint 在它的三次数据中心运行中都标记了 VPN(“timezone mismatch”)和一个低置信度的数据中心代理,而同一 IP 上的 Chrome 工具两项都没被标记。IP 与时区不一致,无论哪个方向都会被注意到。

WebRTC 泄露了代理背后的服务器

在 Playwright、Patchright 和 nodriver 每次走代理的运行中,CreepJS 和 Pixelscan 都把宿主机的 Hetzner 地址显示为 WebRTC IP。HTTP 流量走了住宅出口,但通过 UDP 发出的 STUN 请求没有。Zendriver 会传入 --webrtc-ip-handling-policy=disable_non_proxied_udp 和 --force-webrtc-ip-handling-policy,这是它的默认设置,CreepJS 在它的所有运行中都报告 WebRTC 已被屏蔽。Camoufox 则把地址替换成了代理出口。WebRTC 被屏蔽本身也是一些网站可能纳入评分的信号。我们没有测试这些页面是否会因此扣分,Pixelscan 没有扣分。反爬系统还会查找哪些其他不一致,请参阅反爬虫系统如何检测代理。

无头模式比 IP 暴露得更多

我们在数据中心 IP 下为每款工具做了一次无头运行。样本比其他部分小,但趋势很明确:

工具(无头,数据中心 IP,1 次运行)sannysoft 失败项deviceandbrowserinfo isBotCreepJS headlessFingerprint “Bot”nowsecure.nl
Playwright(对照组)CHR_MEMORY:FAIL, HEADCHR_UA:FAIL, WebDriver1/1100%1/1:You are a bad bot (type: headless_chrome)45 秒后仍卡在 “Just a moment...”
PatchrightCHR_MEMORY:FAIL, HEADCHR_UA:FAIL1/167%1/1:You are a bad bot (type: headless_chrome)45 秒后仍卡在 “Just a moment...”
nodriverCHR_MEMORY:FAIL, HEADCHR_UA:FAIL1/167%1/1:You are a bad bot (type: headless_chrome)45 秒后仍卡在 “Just a moment...”
Zendriver无1/133%Not detected (1/1)45 秒后仍卡在 “Just a moment...”
CamoufoxChrome0/10%Not detected (1/1)页面已加载

仅仅切换到无头模式,Patchright 和 nodriver 在 deviceandbrowserinfo 和 Fingerprint 上就从干净变成被标记(“bad bot, type: headless_chrome”)。sannysoft 的 HEADCHR_UA 和 CHR_MEMORY 两项失败。所有基于 Chrome 的工具,包括 Zendriver,都卡在了 nowsecure.nl 的 Cloudflare “Just a moment...” 页面上。而在有头模式下,同样的工具,包括原版 Playwright,都直接通过了。Zendriver 躲过了 Fingerprint 的机器人标记,但仍被 deviceandbrowserinfo 标记。Camoufox 在所有这些页面上,无头与有头的表现完全相同。

两项运维发现:粘性会话与后台流量

并行的首批连接破坏了粘性会话

使用粘性会话 ID 时,基于 Chrome 的运行经常在第一个页面显示一个 IP,几个页面之后又变成另一个。12 次粘性运行中有 11 次出现这种情况。Camoufox 在 3 次运行中全部保持同一个 IP,可能是因为它的 GeoIP 查询在浏览器启动前只发一个请求。我们用 curl 复现了这一点:在全新的会话 ID 上并行发 6 个请求,两次试验分别得到 3 个和 5 个不同的 IP。之后的顺序请求则保持在同一个 IP 上。在启动浏览器之前先串行发一个预热请求,4 次运行中 4 次都解决了问题。我们第一轮代理测试还用了网关不认识的会话参数,导致每个连接都悄无声息地换了新 IP,这一轮在上文作为轮换条件报告。两件事给出的教训相同:信任粘性会话之前,先用两个顺序请求验证它。

部分真实 Chrome 方案会通过你的代理在后台下载

在按流量计费的住宅流量上,访问这 9 个页面,Playwright 每次运行约用 7 MB,Patchright 约 8 MB(有一次 12 MB),Camoufox 9–11 MB。nodriver 和 Zendriver 的大多数运行用了 40–61 MB。在我们按主机记录字节数的运行中,大部分流量来自 optimizationguide-pa.googleapis.com,这是 Chrome 的一项后台下载。有两次运行只用了约 9 MB 和 15 MB,所以并非每次都会发生。Patchright 同样在全新配置文件上运行真实 Chrome,却没有出现这种情况,可见这取决于工具如何启动 Chrome。如果你按 GB 付费,扩大规模前请按主机统计字节数。

这次基准测试不能说明什么

  • 它不是生产级的反爬测试。这些页面主要检查浏览器端的痕迹。商业系统还会结合 TLS 和 HTTP/2 指纹、行为、IP 历史以及针对单个站点的模型。在这里拿到干净结果只是必要条件,不是充分条件。这些页面几乎没有触及的一层,可参阅我们的 TLS 指纹识别指南。
  • 样本很小:每个单元格 3 次运行,一天之内,一台位于芬兰的数据中心主机,一个代理国家。像 Pixelscan 那样的单次翻转,无法与噪声区分开。
  • 没有任何交互。没有点击、滚动、表单或鼠标轨迹,因此行为检测没有被触发。
  • 部分检测会保留历史。Fingerprint 的 playground 会显示每台设备的历史(“1 IP, 2 Linked IDs in the past 24 hours”),因此重复访问可能影响了后续分数。
  • 默认设置未经调优。基于 Chrome 的工具保留了 UTC 和 en-US,Camoufox 被限定为 Linux,除了各自 README 的默认配置外没有做任何加固。调优后的方案得分可能不同。那是我们下一步要测的内容,而不是已经测过的结论。
  • 你的目标站点会不一样。用数据中心 IP 还是住宅 IP,取决于你采集什么、在哪里采集。成本方面请参阅住宅代理与数据中心代理的采集对比。对真实网站运行类似测试之前,请先查看 robots.txt、站点条款和个人数据相关规定。

复现方法

测试框架约 260 行 Python,外加一个 80 行的中继和一个只读取已保存页面文本的解析器。下面是在我们数据中表现干净的 Patchright 配置,并内置了上文的两项修复:在 Chrome 打开并行连接之前用预热请求固定粘性会话,以及 Zendriver 的 WebRTC 参数。我们通过美国住宅出口用它访问过一次 deviceandbrowserinfo("isBot": false),这次检查不计入 54 次运行。

import asyncio, os, tempfile, urllib.parse, urllib.request, uuid
from patchright.async_api import async_playwright

USER = os.environ["PROXY_USER"]          # your proxy username
PASS = os.environ["PROXY_PASS"]          # your proxy password, never hard-code it
GATE = "gate.proxyhat.com:8080"

def sticky_username() -> str:
    # one fresh session per browser launch; -sid- keeps the exit IP between connections
    return f"{USER}-country-US-sid-{uuid.uuid4().hex[:10]}-ttl-30m"

def warm_up(username: str) -> str:
    # one serial request so the session is pinned before Chrome opens parallel connections
    url = f"http://{urllib.parse.quote(username)}:{urllib.parse.quote(PASS)}@{GATE}"
    opener = urllib.request.build_opener(urllib.request.ProxyHandler({"http": url, "https": url}))
    return opener.open("https://api.ipify.org", timeout=30).read().decode()

async def main():
    username = sticky_username()
    print("session pinned to", warm_up(username))
    async with async_playwright() as p:
        ctx = await p.chromium.launch_persistent_context(
            user_data_dir=tempfile.mkdtemp(),
            channel="chrome",            # real Google Chrome, as the Patchright README recommends
            headless=False,              # run under Xvfb on a server
            no_viewport=True,
            proxy={"server": f"http://{GATE}", "username": username, "password": PASS},
            args=["--webrtc-ip-handling-policy=disable_non_proxied_udp",
                  "--force-webrtc-ip-handling-policy"],
        )
        page = await ctx.new_page()
        await page.goto("https://deviceandbrowserinfo.com/are_you_a_bot")
        await page.wait_for_function("document.body.innerText.includes('\"isBot\"')", timeout=45000)
        print(await page.evaluate("document.body.innerText.match(/\"isBot\": (true|false)/)[0]"))
        await ctx.close()

asyncio.run(main())

用户名后缀是 ProxyHat 的语法(-country-US 用于定位,-sid-…-ttl-30m 用于粘性会话),请换成你的服务商的格式。长时间运行之前若想先检查出口,免费的代理检测工具能显示网站看到的信息。按我们的方式给单元格打分:

  1. 每次运行启动全新浏览器,每个页面在新标签页中访问,等页面自己的判定文字出现在屏幕上再继续。不要用固定时长的 sleep。CreepJS 的分数出现后我们多等了 6 秒,因为它还会继续计算。
  2. 关闭标签页前保存 document.body.innerText 和整页截图。
  3. 只解析页面打印出来的内容,例如 deviceandbrowserinfo 上的 "isBot" JSON、BrowserScan 上的 Normal/Robot 配对,以及 CreepJS 上的 “N% headless”。其他一律记为 “unparsed”。
  4. 在开始时记录出口 IP,并与最后一个页面显示的 IP 比较。如果不一致,说明你的会话并不粘性。
  5. 统计每次运行的代理字节数,让后台流量在账单到来之前就暴露出来。

该怎么用这些结论

先修好浏览器:原版 Playwright 在两种 IP 下都被抓到了,四款隐身工具在有头模式下两种 IP 都没被抓到。除非你实测过无头模式能通过目标站点,否则请使用有头模式(服务器上用 Xvfb)。然后让网络层的说法自洽:时区与出口 IP 匹配、屏蔽 WebRTC 或让它走代理、确认粘性会话真的粘性。做完这些再加住宅代理,并且要预期那些关注代理的厂商仍会识别出很多住宅 IP。IP 决定你一开始是否被归为数据中心流量,浏览器决定你是否被标记为机器人。

常见问题

用住宅代理能让 Playwright 不被识别为机器人吗?

在我们测试的页面上不能。原版 Playwright 在全部 10 次有头运行中,无论用数据中心 IP 还是美国住宅代理,都被 deviceandbrowserinfo、BrowserScan 和 Fingerprint 的 playground 标记。这些标记来自 navigator.webdriver 等浏览器端信号,IP 改变不了它们。

Patchright、nodriver 和 Camoufox 哪个更容易通过机器人检测?

在有头模式下,三者加上 Zendriver,几乎在每次运行中都通过了 sannysoft、BrowserScan、deviceandbrowserinfo、Fingerprint 的 playground 和 Pixelscan 的所有自动化检测。Pixelscan 只标记过 Patchright 一次、Zendriver 一次。在各一次的无头运行中,Patchright 和 nodriver 被多个页面标记,Zendriver 被 deviceandbrowserinfo 标记,Camoufox 一个都没有。真正出现的差异在于 WebRTC 处理和时区,而不是自动化判定。

明明用了代理,为什么浏览器还显示服务器的 IP?

WebRTC 可能绕过 HTTP 代理,通过 UDP 发送 STUN 请求,从而暴露机器真实的公网 IP。在我们的测试中,Playwright、Patchright 和 nodriver 在每次走代理的运行中都以这种方式暴露了宿主机 IP。Zendriver 的默认 Chrome 参数会屏蔽不走代理的 UDP,Camoufox 则把地址替换成了代理出口。

为什么一打开浏览器,粘性代理会话的 IP 就变了?

在我们的测试中,全新会话 ID 上的多个并行首批连接,会在会话固定到某个出口之前从不同的出口 IP 发出。Chrome 启动时会打开很多连接,因此 12 次运行中有 11 次,第一个页面和后续页面看到的 IP 不同。在启动浏览器之前先在新会话上串行发一个请求,4 次运行中 4 次都保持了同一个 IP。

准备好开始了吗?

覆盖 195+ 国家的住宅、ISP 和移动代理。创建免费账户。

创建免费账户
← 返回博客