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: 更新项目定位和特性描述
This commit is contained in:
@@ -0,0 +1,412 @@
|
||||
# VisionL 反检测设计(专项)
|
||||
|
||||
> 本文档详尽列举页面/后端可检测的自动化痕迹及 VisionL 的应对策略。
|
||||
|
||||
## 核心原则
|
||||
|
||||
**真实浏览器有的,VisionL 必须有。真实浏览器没有的,VisionL 不能有。**
|
||||
|
||||
检查方法:在一个真实的桌面 Chrome 浏览器控制台中执行以下探测脚本,
|
||||
记录返回结果。VisionL 中打开同一个页面执行相同脚本,结果必须一致(或在统计上不可区分)。
|
||||
|
||||
---
|
||||
|
||||
## 1. WebDriver 检测
|
||||
|
||||
### 检测方式
|
||||
|
||||
```javascript
|
||||
navigator.webdriver
|
||||
// 裸 Playwright: true
|
||||
// 真实 Chrome: false 或 undefined
|
||||
```
|
||||
|
||||
这是最直接、最致命的自动化暴露点。几乎所有反爬服务都会首先检查此属性。
|
||||
|
||||
### 应对
|
||||
|
||||
使用 `puppeteer-extra-plugin-stealth` 在页面加载前覆盖此属性为 `false`。
|
||||
|
||||
**验证方法**:打开 bot.sannysoft.com,WebDriver 行应为绿色。
|
||||
|
||||
---
|
||||
|
||||
## 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 交互
|
||||
无感知影响。
|
||||
Reference in New Issue
Block a user