BotBrowser 的确定性浏览器行为模式
介绍如何使用 --bot-noise-seed 在不同会话间生成可重现的浏览器指纹和一致结果。
介绍
浏览器指纹依赖于在会话内稳定但在设备间变化的信号。问题的另一面是可重现性:当多个浏览器会话需要呈现相同身份,或你需要验证隐私保护是否生效时,就需要确定性行为。若浏览器每次启动都生成略有差异的 canvas 哈希、音频指纹或 WebGL 输出,就无法维持稳定身份或可靠地测试配置。
BotBrowser 的确定性模式通过 --bot-noise-seed 标志提供对所有指纹变化来源的完全控制。相同的配置文件与种子组合在不同会话、不同机器和不同操作系统中始终产生相同的指纹。
隐私影响
对指纹保护而言,噪声很重要。在 canvas 输出、音频处理与 WebGL 渲染中添加受控变动可以防止指纹被简单地关联到单一配置文件。但不受控的噪声(每次会话都不同)会导致每个会话看起来像一个独立用户,从而与“融入人群”的目标相反。
研究表明,指纹在会话间不稳定本身就是一种追踪信号。如果指纹在每次访问时都变化,而其他信号(IP、登录状态、行为模式)保持稳定,那么变化的指纹反而成为一个标识。
确定性模式通过显式控制解决了这个问题。你可以选择在何时保持相同指纹(相同种子),何时故意不同(不同种子)。这对以下场景尤为重要:
- 会话连续性:跨多次会话以相同指纹返回网站。
- 多实例协作:运行需要各自稳定身份的多个浏览器实例。
- 测试与验证:确认指纹保护产生预期结果。
- 研究可重现性:在受控实验中消除指纹变动作为变量。
技术背景
指纹变动的来源
现代指纹保护在多个渲染和处理管道中添加受控噪声:
- Canvas 2D:文本渲染、图形绘制和图像操作会产生设备特定的像素输出。噪声在子像素层面引入变动以避免精确匹配。
- WebGL/WebGPU:着色器执行、纹理采样与帧缓冲读回产生 GPU 特定输出。噪声会修改渲染输出的像素值。
- 音频:OfflineAudioContext 渲染与 AnalyserNode 频谱数据生成平台相关浮点值,噪声改变处理输出。
- 文本度量:
measureText()、getBoundingClientRect()与getClientRects()返回依赖字体渲染的尺寸,噪声在测量中引入子像素级变动。
若无种子,以上噪声源在每次浏览器启动时生成新的随机变动;若有种子,PRNG 会用已知状态初始化,生成相同的变动模式。
噪声种子如何工作
噪声种子是初始化确定性伪随机数生成器(PRNG)的单个整数。PRNG 产生的序列看似随机,但完全由种子决定。两个相同种子的会话会产生相同序列,因此在所有指纹表面上应用相同噪声模式。
BotBrowser 的噪声种子(通过 --bot-noise-seed 设置)控制:
- Canvas 2D 图像噪声
- WebGL 图像噪声
- WebGPU 渲染噪声
- 音频上下文处理变动
- 文本度量的变动(client rects、text rects)
种子不会影响非噪声属性,如 navigator.hardwareConcurrency、screen.width 或 user agent 字符串。这些来自配置文件并始终为确定性。
时序确定性
除了指纹噪声外,BotBrowser 还提供时序控制:
--bot-time-seed— 控制 27 项浏览器操作的执行时序差异。每个种子产生独特且稳定的性能配置,涵盖performance.now()间隔、performance.getEntries()、导航时序等。--bot-time-scale— 缩放performance.now()间隔以标准化由服务器负载差异带来的时序差异。
噪声种子与时序种子结合提供对所有可变浏览器输出的全面确定性控制。
常见保护方法及其局限
- 完全无噪声:指纹完全反映配置文件的基值,确定性可复现,但失去变动带来的隐私保护——多次会话容易被关联。
- 无种子的随机噪声:提供变动但不可重现,每次会话为独立指纹,无法保持稳定身份或验证配置。
- 会话级噪声:为每个会话生成随机种子并存储,这是一个手动且易出错的变通方法。
- 基于扩展的噪声:通常在 JS 层注入随机噪声且无种子机制,结果不可控且跨 API 不一致。
BotBrowser 通过将确定性逻辑置于引擎层消除了这些问题。
BotBrowser 的引擎级方法
确定性模式在渲染引擎级别工作,确保来自单个种子的噪声在所有 API 表面上保持一致。
单一种子,全面覆盖
--bot-noise-seed 接受 1 到 4294967295(UINT32_MAX)之间的整数。该值同时控制所有噪声源:
- Canvas 2D 的
toDataURL()、toBlob()与getImageData()在相同种子下产生相同输出。 - WebGL 的
readPixels()在相同场景与种子下返回相同帧缓冲数据。 - OfflineAudioContext 在相同音频图与种子下渲染相同缓冲区。
measureText()与getClientRects()在相同内容与种子下返回相同测量值。
跨机器可重现性
由于噪声源从种子派生并在引擎层应用,相同的配置文件 + 种子组合在不同硬件、操作系统或 GPU 上也会生成相同指纹。
将种子视为身份
实践中,每个种子定义了配置文件内部的唯一子身份。配置文件定义设备类别(OS、浏览器、硬件),种子定义该类别内的个体。
时序确定性
结合 --bot-time-seed,执行时序也会变为可重现。性能测量、导航时序与资源时序在给定种子下遵循确定性模式。
<svg viewBox="0 0 600 300" xmlns="http://www.w3.org/2000/svg" style="font-family: system-ui, sans-serif; max-width: 600px;">
<rect width="600" height="300" fill="#f8f9fa" rx="8"/>
<text x="300" y="30" text-anchor="middle" font-size="16" font-weight="bold" fill="#1a1a2e">Deterministic Mode: Seed Controls All Noise Sources</text>
<!-- Seed box -->
<rect x="230" y="50" width="140" height="40" fill="#2563eb" rx="6"/>
<text x="300" y="75" text-anchor="middle" fill="white" font-size="13" font-weight="bold">--bot-noise-seed=42</text>
<!-- Arrow down -->
<line x1="300" y1="90" x2="300" y2="120" stroke="#2563eb" stroke-width="2" marker-end="url(#arrowBlue)"/>
<!-- PRNG box -->
<rect x="220" y="120" width="160" height="30" fill="#e0e7ff" rx="4" stroke="#2563eb"/>
<text x="300" y="140" text-anchor="middle" font-size="12" fill="#1e40af">Deterministic PRNG</text>
<!-- Arrows to each output -->
<line x1="220" y1="150" x2="100" y2="190" stroke="#6b7280" stroke-width="1.5"/>
<line x1="270" y1="150" x2="220" y2="190" stroke="#6b7280" stroke-width="1.5"/>
<line x1="330" y1="150" x2="380" y2="190" stroke="#6b7280" stroke-width="1.5"/>
<line x1="380" y1="150" x2="500" y2="190" stroke="#6b7280" stroke-width="1.5"/>
<!-- Output boxes -->
<rect x="40" y="190" width="120" height="35" fill="#dbeafe" rx="4" stroke="#93c5fd"/>
<text x="100" y="212" text-anchor="middle" font-size="11" fill="#1e40af">Canvas Noise</text>
<rect x="170" y="190" width="120" height="35" fill="#dbeafe" rx="4" stroke="#93c5fd"/>
<text x="230" y="212" text-anchor="middle" font-size="11" fill="#1e40af">WebGL Noise</text>
<rect x="310" y="190" width="120" height="35" fill="#dbeafe" rx="4" stroke="#93c5fd"/>
<text x="370" y="212" text-anchor="middle" font-size="11" fill="#1e40af">Audio Noise</text>
<rect x="440" y="190" width="120" height="35" fill="#dbeafe" rx="4" stroke="#93c5fd"/>
<text x="500" y="212" text-anchor="middle" font-size="11" fill="#1e40af">Text Metrics</text>
<!-- Result -->
<rect x="150" y="250" width="300" height="35" fill="#dcfce7" rx="4" stroke="#86efac"/>
<text x="300" y="272" text-anchor="middle" font-size="12" font-weight="bold" fill="#166534">Same seed = Same fingerprint every time</text>
<defs>
<marker id="arrowBlue" markerWidth="10" markerHeight="7" refX="10" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#2563eb"/>
</marker>
</defs>
</svg>
配置与使用
基本确定性模式
chrome --bot-profile="/path/to/profile.enc" \
--bot-noise-seed=42 \
--user-data-dir="$(mktemp -d)"
完整确定性控制
chrome --bot-profile="/path/to/profile.enc" \
--bot-noise-seed=42 \
--bot-time-seed=42 \
--bot-stack-seed=profile \
--user-data-dir="$(mktemp -d)"
多实例不同种子
# Instance 1 - stable identity A
chrome --bot-profile="/path/to/profile.enc" \
--bot-noise-seed=1001 \
--user-data-dir="/tmp/session-a" &
# Instance 2 - stable identity B
chrome --bot-profile="/path/to/profile.enc" \
--bot-noise-seed=1002 \
--user-data-dir="/tmp/session-b" &
Playwright 集成
const { chromium } = require('playwright');
const browser = await chromium.launch({
executablePath: '/path/to/botbrowser/chrome',
args: [
'--bot-profile=/path/to/profile.enc',
'--bot-noise-seed=42',
'--bot-time-seed=42'
]
});
const page = await browser.newPage();
await page.goto('https://example.com');
// This canvas fingerprint will be identical on every run
const hash = await page.evaluate(() => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
ctx.fillText('deterministic test', 10, 10);
return canvas.toDataURL();
});
console.log(hash);
Puppeteer 集成
const puppeteer = require('puppeteer');
const browser = await puppeteer.launch({
executablePath: '/path/to/botbrowser/chrome',
defaultViewport: null,
args: [
'--bot-profile=/path/to/profile.enc',
'--bot-noise-seed=42',
'--bot-time-seed=42'
]
});
const page = await browser.newPage();
await page.goto('https://example.com');
禁用特定噪声通道
如果需要对某些信号使用确定性输出而对其他信号保留原生输出:
chrome --bot-profile="/path/to/profile.enc" \
--bot-noise-seed=42 \
--bot-config-noise-canvas=false \
--bot-config-noise-audio-context=false
验证
跨会话可重现性。 在两个独立会话中使用相同的配置文件与种子运行相同的指纹测试,比较 canvas 哈希、音频指纹、WebGL 输出与文本度量。所有值应相同。
跨机器可重现性。 在另一台机器上使用相同配置文件与种子运行相同测试,指纹应完全匹配。
种子变动。 更改种子并重新运行测试,指纹应不同,证明种子确实影响输出。
种子 0 行为。 使用 --bot-noise-seed=0 会保留配置文件默认的噪声,但不做确定性种子初始化,验证这会产生会话间变化的输出。
最佳实践
- 为每个身份使用唯一种子。 每个种子定义稳定的子身份。不要在应显得为不同用户的会话中重用相同种子。
- 存储种子值。 若需恢复会话,需要相同的配置文件与种子。存储会话身份与种子的对应关系。
- 结合
--bot-time-seed使用。 噪声种子控制渲染输出,时序种子控制性能时序。两者共同构成完整指纹。 - 避免使用种子值 0。 0 会保持非确定性的噪声,使用任意正整数以获得确定性行为。
- 测试种子分配。 部署前验证不同种子生成不同指纹且相同种子生成相同指纹。
常见问题
Q: --bot-noise-seed 的有效范围是多少? A: 任何 1 到 4294967295(UINT32_MAX)之间的整数。值 0 会保持噪声活动但非确定性。
Q: 噪声种子会影响浏览器性能吗? A: 不会。噪声操作非常轻量,PRNG 计算开销可以忽略不计。
Q: 不同配置文件使用相同种子会生成相同指纹吗? A: 不会。配置文件定义基准指纹(GPU、屏幕、navigator 等),种子定义噪声变动。不同配置文件即使种子相同也会得出不同指纹。
Q: 不设置 --bot-noise-seed 会怎样? A: 每次启动会使用随机种子应用噪声,导致每次会话都有不同指纹,配置文件的基值保持不变。
Q: 确定性模式会影响 cookies 或 local storage 吗?
A: 不会。确定性模式控制与指纹相关的渲染输出。存储、cookies 与会话状态由 --user-data-dir 与 --bot-cookies 管理。
Q: 在不同 BotBrowser 版本间能否复用相同种子? A: 噪声算法可能在大版本之间变化。为获得完全可重现性,请使用相同的 BotBrowser 版本。
Q: --bot-time-seed 与 --bot-noise-seed 有何不同?
A: --bot-noise-seed 控制渲染噪声(canvas、WebGL、audio、text metrics),--bot-time-seed 控制执行时序差异(performance.now() 模式、导航时序)。两者独立,可设为不同值。
总结
确定性浏览器行为对于维持稳定指纹身份、协调多会话工作流以及验证隐私保护至关重要。BotBrowser 的 --bot-noise-seed 标志提供对所有指纹变动源的完整控制,使相同配置文件与种子组合在不同硬件上产生相同输出。结合 --bot-time-seed 与 --bot-stack-seed,BotBrowser 提供覆盖所有可变输出的全面确定性模式。
相关阅读: What is Browser Fingerprinting、Audio Fingerprint Protection、Canvas Fingerprinting、Noise Seed Reproducibility。
title: "BotBrowser 的确定性浏览器行为模式" description: "BotBrowser 如何使用基于配置文件的设置和运行时标志在不同会话间产生一致、可重复的浏览器行为。" date: "2025-06-10" locale: zh category: fingerprint tags: ["deterministic", "consistent", "fingerprinting", "reproducibility", "privacy"] published: true
为什么确定性很重要
每个浏览器会话在渲染、计时和随机数生成方面都会引入微小变化。真正一致的浏览器身份需要控制所有随机性来源。
BotBrowser 如何实现确定性
BotBrowser 通过基于配置文件的设置和运行时标志在浏览器引擎层面控制非确定性。
基于配置文件的硬件身份
--bot-profile 标志加载完整的设备指纹:
chrome --bot-profile="/profiles/windows-chrome-122.enc" \
--user-data-dir="$(mktemp -d)"
这设置屏幕尺寸、GPU 标识符、平台字符串、字体列表和数十个其他硬件信号。
噪声种子实现渲染一致性
--bot-noise-seed 标志通过确定性随机数生成器控制所有渲染噪声:
chrome --bot-profile="/profiles/windows-chrome-122.enc" \
--bot-noise-seed=42 \
--user-data-dir="$(mktemp -d)"
使用固定种子,Canvas、WebGL 和 Audio 输出在不同会话间保持一致。
计时和栈控制
额外标志控制更多变化来源:
chrome --bot-profile="/profiles/windows-chrome-122.enc" \
--bot-noise-seed=42 \
--bot-time-scale=1.0 \
--bot-stack-seed=profile \
--user-data-dir="$(mktemp -d)"
完整确定性配置
组合所有标志产生完全确定性的浏览器会话:
chrome --bot-profile="/profiles/windows-chrome-122.enc" \
--bot-noise-seed=42 \
--bot-time-scale=1.0 \
--bot-stack-seed=profile \
--bot-config-noise-canvas=true \
--user-data-dir="/data/session-identity-a"
此配置的每次运行都产生相同的指纹输出。
验证
const { chromium } = require('playwright-core');
const browser = await chromium.launch({
executablePath: '/path/to/botbrowser/chrome',
args: [
'--bot-profile=/profiles/windows-chrome-122.enc',
'--bot-noise-seed=42',
'--bot-time-scale=1.0',
'--bot-stack-seed=profile',
],
headless: true,
});
const page = await (await browser.newContext()).newPage();
await page.goto('https://abrahamjuliot.github.io/creepjs/');
// 记录所有指纹哈希,然后使用相同标志重新启动。
// 哈希应完全匹配。