Files
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

525 lines
20 KiB
Markdown
Raw Permalink 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 架构设计
> 面向 AI 智能体的开源可持久化浏览器 — 架构设计文档
## 1. 概述
### 要解决的根本问题
**让 LLM 访问各类页面尽可能不被简单的人机验证所拦截。**
这是 VisionL 存在的第一性原理,所有设计决策必须围绕这个目标展开。
市面上绝大多数自动化浏览器(Playwright/Puppeteer 裸跑)会在数十个维度上暴露自动化痕迹,
被 Cloudflare、Akamai、DataDome 等反爬服务轻松识别。VisionL 的核心竞争力在于:
**GUI 浏览器所有能被页面/页面后端检测到的特征,在 CLI 中均有值(可以是模拟的)**
### 次要目标
- **页面持久化**:页面在客户端断连后依然存活,只有显式 `kill` 才能终止
- **智能体优先**:CLI 子命令为 LLM 工具调用而设计,输出结构化 JSON
- **本地优先**Daemon 仅监听 localhost,不对外开放
- **GUI 就绪**HTTP/WS API 同时服务于 CLI 和未来的 GUI
---
## 2. 技术栈
| 层级 | 选择 | 理由 |
|------|------|------|
| 浏览器引擎 | Playwright + Chromium | AI 浏览器自动化的事实标准 |
| 反检测框架 | `playwright-extra` + `puppeteer-extra-plugin-stealth` | 自动处理 WebDriver 标记和基础指纹 |
| 指纹补充 | 自研 stealth 模块 | stealth 插件只覆盖约 60% 的指纹,剩余需自行实现 |
| 语言 | TypeScript + Node.js | Playwright 原生语言 |
| 通信协议 | HTTP REST + WebSocket (localhost) | 职责分离,GUI 可复用 |
| 包管理 | npm workspaces (monorepo) | 共享类型 |
| CLI 框架 | commander + chalk | 轻量 |
| HTTP 服务 | 原生 `http` + `ws` 库 | 本地 daemon 无需重型框架 |
### JSON 转义处理
所有返回给智能体的 JSON 必须通过 `JSON.stringify` 序列化整个响应对象,
禁止手动拼接 JSON 字符串。页面内容字段中的引号、反斜杠、控制字符均正确处理。
---
## 3. 反检测设计(核心)
### 3.1 指纹覆盖全景
目标:**所有能通过 JS API 读取到的浏览器特征,都必须返回真实 GUI 浏览器的值,
不能留下任何自动化痕迹。**
以下表格列出页面/后端可检测的维度、Playwright 默认值的问题、以及 VisionL 的应对策略。
#### 3.1.1 Navigator 属性
| 属性 | 裸 Playwright 值 | 问题 | VisionL 策略 |
|------|-----------------|------|-------------|
| `navigator.webdriver` | `true` | **最致命的暴露** | stealth 插件 → `false` |
| `navigator.userAgent` | 含 "HeadlessChrome" | 直接暴露 | 模拟真实 Chrome UA,可配置 |
| `navigator.plugins` | 空数组 | 真实 Chrome 有 5 个插件 | stealth 插件 → 注入 PDF Viewer 等 |
| `navigator.languages` | `["en-US"]` | 需匹配目标地区 | 可配置,默认 `["zh-CN", "en-US"]` |
| `navigator.platform` | 随 OS | 无问题但需一致性 | 随系统,但确保与其他指纹一致 |
| `navigator.hardwareConcurrency` | 实际 CPU 核数 | OK | 保持真值 |
| `navigator.deviceMemory` | 实际值 | OK | 保持真值 |
| `navigator.maxTouchPoints` | `0` | 桌面无触摸 | 可配置,桌面默认 `0` |
| `navigator.vendor` | `"Google Inc."` | stealth 会修正 | stealth 插件处理 |
| `navigator.productSub` | `"20030107"` | 需一致 | stealth 插件处理 |
| `navigator.connection` | `undefined` (headless) | 真实 Chrome 有值 | 注入 NetworkInformation 对象 |
| `navigator.mediaDevices` | 存在 | 可能暴露空设备列表 | 模拟至少一个音频设备 |
#### 3.1.2 Chrome 特有属性
| 属性 | 裸 Playwright 值 | 问题 | VisionL 策略 |
|------|-----------------|------|-------------|
| `window.chrome` | `undefined` (headless) | 真实 Chrome 有此对象 | 注入完整 `window.chrome` 对象 |
| `chrome.runtime` | N/A | 检测自动化时常用 | 注入 mock runtime |
| `navigator.brave` | N/A | Brave 检测用,非必须 | 不注入(伪装 Chrome 不是 Brave |
#### 3.1.3 屏幕与视口
| 属性 | 裸 Playwright 值 | 问题 | VisionL 策略 |
|------|-----------------|------|-------------|
| `screen.width/height` | 默认 1280x720 | 非标准分辨率 | 可配置,默认 1920x1080 |
| `screen.availWidth/Height` | 同 screen | 任务栏高度需扣除 | 模拟,比 screen 小 40-80px |
| `screen.colorDepth` | `24` | OK | `24` |
| `screen.pixelDepth` | `24` | OK | `24` |
| `window.outerWidth/Height` | 与视口相同 | 真实浏览器窗口含边框 | 比视口大(含窗口装饰) |
| `window.innerWidth/Height` | 视口尺寸 | 需与 screen 逻辑一致 | 保证 inner < outer < screen |
| `window.devicePixelRatio` | `1` | 现代设备多为 2 | 可配置,默认 `1` 或自动检测 |
#### 3.1.4 Canvas 指纹
页面可以在 canvas 上渲染特定图形,获取像素哈希作为指纹。不同 GPU/驱动/OS 的渲染结果
有微小差异。VisionL 需要在 canvas 渲染中加入可控的随机噪声,使每次指纹不同但看起来
像正常设备,从而绕过基于 canvas 哈希的追踪。
| 措施 | 实现 |
|------|------|
| Canvas 2D 噪声 | `toDataURL()``getImageData()` 返回时在像素末尾添加 ±1 随机扰动 |
| WebGL 噪声 | `getParameter()``readPixels()` 添加类似扰动 |
| 一致性 | 同一页面会话内噪声种子固定,跨页面会话刷新 |
#### 3.1.5 WebGL 指纹
| 属性 | 裸 Playwright 值 | VisionL 策略 |
|------|-----------------|-------------|
| `UNMASKED_VENDOR_WEBGL` | 真实 GPU 厂商 | 可保留真值(多样化),也可统一模拟 |
| `UNMASKED_RENDERER_WEBGL` | 真实 GPU 型号 | 同上 |
| WebGL 渲染噪声 | 无 | 类似 Canvas,在 readPixels 加入微量噪声 |
#### 3.1.6 AudioContext 指纹
页面通过 AudioContext 处理音频信号提取指纹。VisionL 策略:
- `createOscillator()` 生成的波形在浮点精度末尾加入随机噪声
- `createAnalyser()``getByteFrequencyData()` 同样处理
- 噪声量级极小(-100dB 级别),不改变音频语义
#### 3.1.7 字体枚举
页面无法直接枚举系统字体列表,但可通过以下方式探测:
- 测量指定字体的文本宽度
- `document.fonts` API
- Flash(已消亡)
VisionL 默认不做字体列表注入(多数反爬不会测字体),但保持基础常见字体的一致性。
#### 3.1.8 HTTP 请求头
| 头部 | 裸 Playwright 值 | VisionL 策略 |
|------|-----------------|-------------|
| `User-Agent` | 含 HeadlessChrome | 模拟真实 Chrome UA,可配置 |
| `Accept-Language` | `en-US` | 匹配 `navigator.languages` |
| `Sec-CH-UA` | 不完整 | 补全 `"Chromium";v="xxx", "Google Chrome";v="xxx"` |
| `Sec-CH-UA-Platform` | 随 OS | 确保一致性 |
| `Sec-CH-UA-Mobile` | `?0` | `?0`(桌面) |
| `Accept` | OK | 保持默认 |
| `Accept-Encoding` | OK | 保持默认 |
| `Connection` | `keep-alive` | 保持默认 |
| `Upgrade-Insecure-Requests` | `1` | 保持默认 |
#### 3.1.9 权限状态
| 权限 | 裸 Playwright 值 | VisionL 策略 |
|------|-----------------|-------------|
| `notifications` | `prompt` | 可配置:`prompt`/`granted`/`denied` |
| `geolocation` | `prompt` | 可配置,`granted` 时提供模拟坐标 |
| `camera` | `prompt` | 可配置 |
| `microphone` | `prompt` | 可配置 |
| `midi` | `prompt` | 保持 `prompt` |
#### 3.1.10 行为模拟
**鼠标移动**v1 基础版,v2 增强):
- 点击前鼠标从当前位置线性移动到目标中心(带随机抖动)
- 移动速度加入随机变化(不恒定)
- `mousemove` 事件在移动路径上均匀发射
**键盘输入**v1 基础版):
- 每个字符间隔 `50-150ms` 随机
- `keydown``keypress``keyup` 完整序列
- 中文输入法暂不模拟
**滚动**
- 非瞬间跳转,分段滚动
- 每次滚动步长加入随机抖动
### 3.2 实现方案
```
daemon/
├── src/
│ ├── stealth/ # 反检测模块
│ │ ├── index.ts # 统一入口,页面创建时注入
│ │ ├── navigator.ts # navigator 属性覆盖
│ │ ├── chrome-runtime.ts # window.chrome 注入
│ │ ├── screen.ts # 屏幕/视口尺寸管理
│ │ ├── canvas-noise.ts # Canvas/WebGL/Audio 噪声
│ │ ├── headers.ts # HTTP 头拦截修正
│ │ ├── permissions.ts # 权限状态管理
│ │ ├── human-input.ts # 鼠标/键盘/滚动行为模拟
│ │ ├── fingerprint-profile.ts # 指纹配置文件结构
│ │ └── profiles/ # 内置指纹模版
│ │ ├── desktop-chrome.ts # 桌面 Chrome 通用模版
│ │ ├── desktop-windows.ts # Windows Chrome
│ │ └── desktop-mac.ts # macOS Chrome
```
### 3.3 指纹配置(FingerprintProfile
```typescript
// packages/core/src/types/fingerprint.ts
interface FingerprintProfile {
// 浏览器基础
userAgent: string;
platform: string;
languages: string[];
acceptLanguage: string;
// 屏幕
screen: {
width: number;
height: number;
colorDepth: number;
pixelRatio: number;
};
viewport: {
width: number;
height: number;
};
// GPU
webglVendor: string;
webglRenderer: string;
// 时区与位置
timezone: string;
geolocation?: { latitude: number; longitude: number; accuracy: number };
// 权限
permissions: {
notifications: 'prompt' | 'granted' | 'denied';
geolocation: 'prompt' | 'granted' | 'denied';
camera: 'prompt' | 'granted' | 'denied';
microphone: 'prompt' | 'granted' | 'denied';
};
// 行为
behavior: {
mouseMoveDelay: { min: number; max: number }; // ms
keyPressDelay: { min: number; max: number }; // ms
scrollStepDelay: { min: number; max: number }; // ms
};
// Canvas 噪声
canvasNoise: {
enabled: boolean;
strength: number; // 0-1, 噪声强度
};
}
```
### 3.4 WebDriver 检测网站验证
v1 目标:通过以下检测站点的自动化识别:
| 检测站点 | 检测方式 | 目标 |
|---------|---------|------|
| https://bot.sannysoft.com | 综合(navigator + screen + chrome + canvas + webgl + fonts | **必须全绿** |
| https://fingerprint.com/demo | 综合指纹(最全面) | 降低置信度到非 bot 区间 |
| https://abrahamjuliot.github.io/creepjs/ | 浏览器指纹各维度逐一打分 | 所有维度分数控制在合理范围 |
---
## 4. 项目结构
```
VisionL/
├── docs/
│ ├── development/
│ │ ├── architecture.md # 本文档
│ │ ├── anti-detection.md # 反检测专项文档(检测维度详情、绕过原理)
│ │ ├── api.md # REST + WS 接口文档
│ │ ├── cli.md # CLI 命令参考
│ │ └── contributing.md # 开发规范
│ └── usage/
│ ├── quickstart.md # 快速开始
│ ├── llm-integration.md # 智能体集成指南
│ └── examples.md # 典型场景示例
├── packages/
│ ├── core/ # 共享库:类型定义、HTTP 客户端
│ │ ├── src/
│ │ │ ├── types/
│ │ │ │ ├── page.ts
│ │ │ │ ├── api.ts
│ │ │ │ ├── ws.ts
│ │ │ │ └── fingerprint.ts # 指纹配置类型
│ │ │ ├── client.ts
│ │ │ ├── escape.ts
│ │ │ └── index.ts
│ │ └── package.json
│ ├── daemon/ # 后台进程
│ │ ├── src/
│ │ │ ├── server.ts
│ │ │ ├── routes/
│ │ │ │ ├── pages.ts
│ │ │ │ ├── actions.ts
│ │ │ │ ├── content.ts
│ │ │ │ └── health.ts
│ │ │ ├── browser-manager.ts
│ │ │ ├── page-registry.ts
│ │ │ ├── ws-relay.ts
│ │ │ ├── pidfile.ts
│ │ │ └── stealth/ # 反检测模块
│ │ │ ├── index.ts # 统一注入入口
│ │ │ ├── navigator.ts
│ │ │ ├── chrome-runtime.ts
│ │ │ ├── screen.ts
│ │ │ ├── canvas-noise.ts
│ │ │ ├── headers.ts
│ │ │ ├── permissions.ts
│ │ │ ├── human-input.ts
│ │ │ ├── fingerprint-profile.ts
│ │ │ └── profiles/
│ │ │ ├── desktop-chrome.ts
│ │ │ ├── desktop-windows.ts
│ │ │ └── desktop-mac.ts
│ │ └── package.json
│ └── cli/
│ ├── src/
│ │ ├── index.ts
│ │ ├── commands/
│ │ │ ├── page.ts
│ │ │ ├── action.ts
│ │ │ ├── view.ts
│ │ │ └── daemon.ts
│ │ ├── format.ts
│ │ └── auto-daemon.ts
│ └── package.json
├── package.json
├── tsconfig.json
├── .gitignore
├── README.md
└── LICENSE
```
---
## 5. Daemon 设计
### 5.1 REST API
| 方法 | 路径 | 说明 | 请求体 | 响应 |
|------|------|------|--------|------|
| POST | `/pages` | 打开新页面 | `{ url, alias?, profile? }` | `{ id, url, alias?, title, status, profileId }` |
| GET | `/pages` | 列出所有页面 | — | `[{ id, url, alias?, title, status }]` |
| GET | `/pages/:id` | 页面详情 | — | 同上 |
| DELETE | `/pages/:id` | 杀死页面 | — | `{ ok: true }` |
| POST | `/pages/:id/navigate` | 跳转 URL | `{ url }` | `{ url, title }` |
| POST | `/pages/:id/click` | 点击元素 | `{ selector }` | `{ success }` |
| POST | `/pages/:id/type` | 输入文本 | `{ selector, text }` | `{ success }` |
| POST | `/pages/:id/scroll` | 滚动页面 | `{ deltaY?, toBottom? }` | `{ success }` |
| POST | `/pages/:id/eval` | 执行 JS | `{ code }` | `{ result }` |
| POST | `/pages/:id/wait` | 等待条件 | `{ selector?, ms? }` | `{ success }` |
| GET | `/pages/:id/screenshot` | 页面截图 | — | `{ base64, mime }` |
| GET | `/pages/:id/text` | 页面纯文本 | — | `{ text }` |
| GET | `/pages/:id/html` | 页面 HTML | — | `{ html }` |
| GET | `/profiles` | 列出可用的指纹配置 | — | `[{ id, name }]` |
| GET | `/health` | 健康检查 | — | `{ status: "ok" }` |
> **新增**`POST /pages` 的 `profile` 字段指定指纹配置(默认 `desktop-chrome`)。
> `GET /profiles` 返回所有内置指纹模版。
### 5.2 WebSocket 事件
同前,另增:
| 事件 | 数据 | 触发时机 |
|------|------|---------|
| `page:detection:warning` | `{ id, level, detail }` | 页面检测到潜在的自动化特征 |
### 5.3 页面创建流程(含反检测注入)
```
visionl page open https://example.com --alias demo --profile desktop-windows
→ POST /pages { url, alias: "demo", profile: "desktop-windows" }
→ browser-manager.createPage(url, fingerprintProfile)
→ 1. 创建 BrowserContext(从 profile 读取 viewport、locale、timezone、geolocation
→ 2. 创建 Page
→ 3. 注入 stealth 插件(navigator.webdriver 等基础隐藏)
→ 4. 注入自研 stealth 模块:
- 注入 navigator 覆盖脚本(evalOnNewDocument
- 注入 window.chrome 对象
- 注入 canvas/webgl/audio 噪声脚本
- 注册请求拦截器修正 HTTP 头
- 注入权限状态
→ 5. 导航到目标 URL
→ 6. 注册到 PageRegistry
→ 返回 { id: "p_abc123", ..., profileId: "desktop-windows" }
```
### 5.4 行为模拟流程
操作命令(click/type/scroll)不走 Playwright 的 API,而是走自研的 `human-input` 模块:
```
visionl click p_abc123 "#login"
→ POST /pages/p_abc123/click { selector: "#login" }
→ action-handler:
→ 1. 获取元素 bounds
→ 2. 计算鼠标路径(当前坐标 → 目标中心 + 抖动)
→ 3. 沿路径逐个发射 mousemove
→ 4. 到达后依次发射 mousedown → mouseup → click
→ 5. (Future) 考虑 ancestor visibility 和 z-index
```
```
visionl type p_abc123 "#username" "hello"
→ POST /pages/p_abc123/type { selector: "#username", text: "hello" }
→ action-handler:
→ 1. 聚焦元素(click or focus
→ 2. 逐字符:keydown → keypress → keyup
→ 3. 每字符间隔随机 50-150ms
→ 4. 完成后可选触发 change/blur 事件
```
---
## 6. CLI 设计(略,同前版本)
CLI 子命令不变。`page open` 新增 `--profile <name>` 指定指纹模版。
---
## 7. 类型定义(core 包核心类型)
```typescript
// 新增
interface FingerprintProfile {
id: string; // "desktop-chrome"
name: string; // "桌面 Chrome (通用)"
userAgent: string;
platform: string;
languages: string[];
screen: { width: number; height: number; colorDepth: number; pixelRatio: number };
viewport: { width: number; height: number };
webgl: { vendor: string; renderer: string };
timezone: string;
geolocation?: { latitude: number; longitude: number; accuracy: number };
permissions: FingerprintPermissions;
behavior: FingerprintBehavior;
canvasNoise: { enabled: boolean; strength: number };
}
interface FingerprintPermissions {
notifications: PermissionState;
geolocation: PermissionState;
camera: PermissionState;
microphone: PermissionState;
}
interface FingerprintBehavior {
mouseMoveDelay: { min: number; max: number };
keyPressDelay: { min: number; max: number };
scrollStepDelay: { min: number; max: number };
}
```
---
## 8. 关键设计决策
### 反检测为首要质量指标
单测、集成测试、手动检测结果共同构成反检测的"质量门"。任何新功能引入不能降低
反检测评分。
### 指纹模版化
不同网站对不同地区的浏览器有不同的预期。VisionL 提供多套预置指纹模版,
智能体可根据目标网站选择。v1 提供 3 套:
| 模版 ID | 说明 | UA 平台 |
|---------|------|---------|
| `desktop-chrome` | 桌面 Chrome 通用 | Linux x86_64 |
| `desktop-windows` | Windows 10 Chrome | Windows NT 10.0 |
| `desktop-mac` | macOS Chrome | Macintosh Intel |
### Canvas 噪声策略
- 同一页面会话(BrowserContext)使用相同的随机种子
- 跨页面会话自动刷新种子
- 噪声强度可配置(0-1),默认 0.3
### HTTP 头注入时机
- `User-Agent``Accept-Language`:在创建 BrowserContext 时通过 Playwright API 设置
- `Sec-CH-UA-*` 系列:通过请求拦截(`page.route()`)修改
### 其他设计决策(同前)
- v1 不包含会话恢复
- 无身份认证(仅 localhost
- Page ID + 别名双轨引用
- 每个 daemon 一个 Browser 实例
---
## 9. 测试策略
| 层级 | 工具 | 范围 |
|------|------|------|
| 反检测验证 | 脚本 + 人工 | 对 bot.sannysoft.com、fingerprint.com 全绿验证 |
| stealth/navigator | vitest | navigator 各属性值验证 |
| stealth/chrome-runtime | vitest | window.chrome 对象完整性 |
| stealth/canvas-noise | vitest | 噪声输出一致性、强度控制 |
| stealth/headers | vitest + msw | HTTP 头正确性 |
| stealth/human-input | vitest | 鼠标路径计算、键盘延迟分布 |
| `core` 类型 | ts 类型检查 | 编译期正确性 |
| `daemon/routes` | vitest + playwright-test | 路由逻辑 |
| `cli/commands` | vitest + mock | 命令解析 |
| 集成测试 | vitest + 真实 daemon | CLI → daemon → Playwright |
| JSON 转义 | vitest | 特殊字符安全 |
---
## 10. 未来规划(v1 范围外)
- `visionl-gui`:桌面 GUI
- 会话持久化
- 更多指纹模版(移动端 Chrome、Safari、Edge 等)
- 指纹随机化(每个页面随机微调指纹参数)
- 行为模拟增强(贝塞尔曲线鼠标路径、人类式打字节奏)
- 验证码自动识别(对接打码服务)
- 多用户支持
- 插件系统
- 远程 daemon
- 网络拦截和 Mock API
- Cookie/存储管理 CLI
- 页面录制与回放