Files
VisionL/docs/development/anti-detection.md
T
AskaEth 985860b2a2 docs: 将反检测作为核心设计原则,全面更新架构和文档
- architecture.md: 新增反检测为核心第一性原理,新增 §3 反检测设计(13 个指纹维度全覆盖)
- anti-detection.md: 新增反检测专项文档(14 节,详尽列举检测方式和应对策略)
- api.md: POST /pages 新增 profile 参数,新增 GET /profiles 端点
- cli.md: 新增 --profile 全局选项,新增 visionl profiles 命令
- contributing.md: 新增反检测开发规范、检测站点验证要求
- README.md: 更新项目定位和特性描述
2026-08-12 20:21:53 +08:00

413 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# VisionL 反检测设计(专项)
> 本文档详尽列举页面/后端可检测的自动化痕迹及 VisionL 的应对策略。
## 核心原则
**真实浏览器有的,VisionL 必须有。真实浏览器没有的,VisionL 不能有。**
检查方法:在一个真实的桌面 Chrome 浏览器控制台中执行以下探测脚本,
记录返回结果。VisionL 中打开同一个页面执行相同脚本,结果必须一致(或在统计上不可区分)。
---
## 1. WebDriver 检测
### 检测方式
```javascript
navigator.webdriver
// 裸 Playwright: true
// 真实 Chrome: false 或 undefined
```
这是最直接、最致命的自动化暴露点。几乎所有反爬服务都会首先检查此属性。
### 应对
使用 `puppeteer-extra-plugin-stealth` 在页面加载前覆盖此属性为 `false`
**验证方法**:打开 bot.sannysoft.comWebDriver 行应为绿色。
---
## 2. Navigator 属性检测
### 2.1 plugins
```javascript
navigator.plugins
// 裸 Playwright: PluginArray { length: 0 }
// 真实 Chrome: PluginArray { 0: Plugin, 1: Plugin, 2: Plugin, ... length: 5 }
```
真实 Chrome 内置 5 个插件:
- Chrome PDF Plugin
- Chrome PDF Viewer
- Native Client(已弃用但仍存在)
### 2.2 languages
```javascript
navigator.languages
// 裸 Playwright: ["en-US"]
// 真实中文系统: ["zh-CN", "en", "en-US"]
```
### 2.3 platform
```javascript
navigator.platform
// Linux: "Linux x86_64"
// Windows: "Win32"
// macOS: "MacIntel"
```
### 2.4 hardwareConcurrency / deviceMemory
```javascript
navigator.hardwareConcurrency // 实际 CPU 核数
navigator.deviceMemory // 实际内存(GB 整数),如 4、8
```
这两个值保留真实值即可,多样化反而是优势。
### 2.5 maxTouchPoints
```javascript
navigator.maxTouchPoints // 桌面: 0,触屏设备: 1-10
```
### 2.6 connection
```javascript
navigator.connection
// { downlink: 10, effectiveType: "4g", rtt: 50, saveData: false }
```
Headless Chrome 中此属性为 `undefined`。需要注入。
### 2.7 vendor / product / productSub
```javascript
navigator.vendor // "Google Inc."
navigator.product // "Gecko"
navigator.productSub // "20030107"
```
---
## 3. Chrome 特有对象
### 3.1 window.chrome
```javascript
typeof window.chrome
// 裸 headless: "undefined"
// 真实 Chrome: "object"
window.chrome.runtime
// headless 没有这个对象
```
真实 Chrome 的 `window.chrome` 包含以下属性:
- `app`
- `csi`
- `loadTimes`
- `runtime`
### 3.2 navigator.brave 和 navigator.permissions.query('brave')
检测 Brave 浏览器的特有 API。VisionL 不应注入这些。
---
## 4. 屏幕与视口
### 4.1 尺寸层级关系
真实浏览器的尺寸遵循严格层级:
```
screen.width >= screen.availWidth >= window.outerWidth > window.innerWidth >= viewport
```
不是所有值都相等。反爬服务会检查:
```javascript
const checks = [
screen.width, // 总屏幕宽度
screen.availWidth, // 可用区域(扣除任务栏)
window.outerWidth, // 窗口外边(含边框和 DevTools)
window.innerWidth, // 窗口内边(含滚动条)
document.documentElement.clientWidth, // 视口宽度
];
```
### 4.2 devicePixelRatio
现代设备多为 2(Retina)或 1~3。桌面默认 1 即可。
### 4.3 colorDepth / pixelDepth
桌面始终为 `24`
---
## 5. Canvas 指纹
### 检测原理
```javascript
const canvas = document.createElement('canvas');
canvas.width = 200;
canvas.height = 50;
const ctx = canvas.getContext('2d');
ctx.textBaseline = 'top';
ctx.font = '14px Arial';
ctx.fillStyle = '#f60';
ctx.fillRect(125, 1, 62, 20);
ctx.fillStyle = '#069';
ctx.fillText('Hello, VisionL!', 2, 15);
ctx.fillStyle = 'rgba(102, 204, 0, 0.7)';
ctx.fillText('Hello, VisionL!', 4, 17);
const hash = canvas.toDataURL();
// 不同 GPU/驱动/OS 的 hash 有微小差异
```
### 策略
`toDataURL()` / `getImageData()` / `toBlob()` 等输出时,在像素末尾加入
±1 的 RGB 随机扰动。扰动基于页面上下文种子,同页面的扰动一致,跨页面不同。
**噪声强度**:默认 0.3(0-1 刻度)。0.3 意味着约 30% 的像素有 ±1 扰动。
### 一致性检查
某些反爬服务会连续两次获取 canvas 指纹并比较。VisionL 确保同页面内两次调用
`toDataURL()` 返回相同结果(基于固定种子),但不同页面返回不同结果。
---
## 6. WebGL 指纹
### 检测原理
```javascript
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl');
// GPU 信息
gl.getParameter(gl.UNMASKED_VENDOR_WEBGL); // "Google Inc. (Intel)" 等
gl.getParameter(gl.UNMASKED_RENDERER_WEBGL); // "ANGLE (Intel, Mesa Intel(R) UHD Graphics..."
// 渲染测试
// 类似 Canvas 指纹,在 3D 场景中绘制并获取像素值
```
### 策略
- `UNMASKED_VENDOR_WEBGL``UNMASKED_RENDERER_WEBGL` 保留真值或使用模版值
-`readPixels()` 加入微量噪声
- 其余 `getParameter()` 调用返回真实值
---
## 7. AudioContext 指纹
### 检测原理
```javascript
const ctx = new AudioContext();
const oscillator = ctx.createOscillator();
const analyser = ctx.createAnalyser();
const gain = ctx.createGain();
// ... 连接并处理音频
const array = new Float32Array(analyser.frequencyBinCount);
analyser.getFloatFrequencyData(array);
// 不同设备的浮点精度有微小差异
```
### 策略
`getFloatFrequencyData()` / `getByteFrequencyData()` / `getFloatTimeDomainData()`
等输出中,在显著低于信号水平的量级上加入随机噪声(约 -100dB)。人耳听不到,
但足以使音频指纹每次不同。
---
## 8. 字体检测
### 检测方式
浏览器没有直接枚举系统字体的 API,但页面可以通过以下方式探测:
```javascript
document.fonts.ready.then(() => {
document.fonts.forEach(f => console.log(f.family));
});
```
或测量固定文本在不同字体下的宽度。
### 策略
V1 不做专门的字体列表注入。基础的中英文字体(Arial、sans-serif、serif、monospace 等)
保持一致即可。
---
## 9. 时间精度
### 检测方式
```javascript
performance.now() // 高精度时间
Date.now() // Unix 时间戳
```
某些反爬检测会测量代码执行时间,自动化工具(如通过 CDP 注入脚本)可能在时间线上
留下异常模式。
### 策略
V1 不干扰时间 API。但确保 `performance.now()` 的精度受浏览器控制(通常微秒级),
且没有人为的时间偏移。
---
## 10. HTTP 请求头
### 检测标头
| 标头 | 裸 Playwright | 真实 Chrome | VisionL |
|------|-------------|-----------|---------|
| `User-Agent` | 含 HeadlessChrome | `Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36` | 模拟真实 UA |
| `Accept-Language` | `en-US` | `zh-CN,zh;q=0.9,en;q=0.8` | 可配置 |
| `Sec-CH-UA` | 不完整 | `"Chromium";v="132", "Google Chrome";v="132", "Not?A_Brand";v="99"` | 完整注入 |
| `Sec-CH-UA-Platform` | 缺失 | `"Linux"` | 注入 |
| `Sec-CH-UA-Mobile` | 缺失 | `?0` | 注入 |
| `sec-ch-ua-arch` | 缺失 | `"arm"``"x86"` | 注入 |
| `sec-ch-ua-bitness` | 缺失 | `"64"` | 注入 |
| `sec-ch-ua-full-version` | 缺失 | 完整版本号 | 注入 |
| `sec-ch-ua-platform-version` | 缺失 | OS 版本 | 注入 |
### 策略
- UA 和 Accept-Language 通过 Playwright Context 配置设置
- `Sec-CH-UA-*` 系列通过 `page.route()` 拦截请求并修改标头
---
## 11. 权限状态
### 检测方式
```javascript
const status = await navigator.permissions.query({ name: 'notifications' });
// { state: "prompt" | "granted" | "denied" }
navigator.permissions.query({ name: 'geolocation' });
navigator.permissions.query({ name: 'camera' });
navigator.permissions.query({ name: 'microphone' });
```
裸 Playwright 对这些权限查询返回 `prompt` 状态,这是正常的默认行为。
### 策略
通过 Playwright Context 权限 API 预设权限状态,匹配指纹模版。
---
## 12. 行为模拟
### 12.1 鼠标移动
**裸 API 问题**Playwright 的 `click` 直接跳转到目标元素中心,无中间 `mousemove` 事件。
**检测**:某些页面监听 `mousemove` 事件,检测鼠标在点击前是否有移动轨迹。
**策略**
```
鼠标路径:当前位置 → 目标中心 + 随机抖动(±5px)
Bezier 插值:起点 → 控制点1(偏右下) → 控制点2(偏左上) → 终点
速度分布:先加速后减速(模拟 Fitts 定律)
抖动:路径上每 10ms 加入 ±2px 随机偏移
```
V1 实现简化版(线性 + 抖动),V2 升级为贝塞尔曲线。
### 12.2 键盘输入
**裸 API 问题**Playwright 的 `type` 瞬间输入所有字符,无时间间隔。
**检测**:某些页面计算 `keydown``keyup` 之间的时间间隔,检测异常输入速度。
**策略**
```
逐字符输入:keydown → (10ms) → keypress → (10ms) → keyup
字符间隔:50-150ms 随机
标点/回车:相比字母略长(+20ms)
中文输入法:V1 不模拟,使用 paste 或逐个字符注入
```
### 12.3 滚动
**裸 API 问题**:瞬间跳转到目标位置。
**策略**
```
分段滚动:每次 50-200px(随机),间隔 10-30ms
缓动:先快后慢
```
---
## 13. 检测站点
### 13.1 bot.sannysoft.com
检测项:navigator.webdriver、plugins、languages、chrome、permissions、canvas、webgl、fonts、screen resolution 等。
**目标:所有测试项绿色通过**
### 13.2 abrahamjuliot.github.io/creepjs
检测项:每个浏览器指纹维度逐一打分(0%-100% 异常分数)。
异常分数含义:
- 0-30%:正常范围,不同设备的自然差异
- 30-70%:可疑,但某些配置可能触发
- 70-100%:自动化工具明确痕迹
**目标:所有维度 ≤ 30% 异常分数**
### 13.3 fingerprint.com/demo
综合指纹服务,给出置信度评分。
**目标:被识别为正常浏览器(非 bot)**
---
## 14. 性能开销
反检测模块不应显著影响性能:
| 模块 | 开销 | 说明 |
|------|------|------|
| stealth 插件 (playwright-extra) | ~0ms | 页面加载前注入,无后续开销 |
| canvas 噪声 | 每帧 0-1ms | 仅在截图/toDataURL 时触发 |
| audio 噪声 | 每次调用 0-1ms | 仅在音频 API 调用时触发 |
| 鼠标路径计算 | 每次点击 0-2ms | 纯 JS 数学计算 |
| HTTP 头拦截 | 每请求 0-1ms | page.route 拦截 |
| 页面初始注入脚本 | 页面加载时 10-50ms | evalOnNewDocument,一次性 |
总体而言,页面加载时增加 10-50ms,运行时交互延迟增加 0-5ms,对用户体验和 LLM 交互
无感知影响。