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:
+396
-220
@@ -4,16 +4,22 @@
|
||||
|
||||
## 1. 概述
|
||||
|
||||
VisionL 是一款专为具备本地读写执行能力的 AI 智能体设计的开源浏览器。
|
||||
LLM 通过 `VisionL-cli` 工具调用来操控网页。页面独立持久化——关闭 GUI 窗口不会杀死页面后台,
|
||||
每个页面只能通过 CLI 参数或未来 GUI 的右键菜单按钮来关闭。
|
||||
### 要解决的根本问题
|
||||
|
||||
### 核心原则
|
||||
**让 LLM 访问各类页面尽可能不被简单的人机验证所拦截。**
|
||||
|
||||
这是 VisionL 存在的第一性原理,所有设计决策必须围绕这个目标展开。
|
||||
|
||||
市面上绝大多数自动化浏览器(Playwright/Puppeteer 裸跑)会在数十个维度上暴露自动化痕迹,
|
||||
被 Cloudflare、Akamai、DataDome 等反爬服务轻松识别。VisionL 的核心竞争力在于:
|
||||
**GUI 浏览器所有能被页面/页面后端检测到的特征,在 CLI 中均有值(可以是模拟的)**。
|
||||
|
||||
### 次要目标
|
||||
|
||||
- **页面持久化**:页面在客户端断连后依然存活,只有显式 `kill` 才能终止
|
||||
- **智能体优先**:CLI 子命令为 LLM 工具调用而设计,输出结构化 JSON
|
||||
- **本地优先**:Daemon 仅监听 localhost,不对外开放
|
||||
- **GUI 就绪**:HTTP/WS API 同时服务于 CLI 和未来的 GUI,避免重复实现
|
||||
- **GUI 就绪**:HTTP/WS API 同时服务于 CLI 和未来的 GUI
|
||||
|
||||
---
|
||||
|
||||
@@ -22,27 +28,242 @@ LLM 通过 `VisionL-cli` 工具调用来操控网页。页面独立持久化—
|
||||
| 层级 | 选择 | 理由 |
|
||||
|------|------|------|
|
||||
| 浏览器引擎 | Playwright + Chromium | AI 浏览器自动化的事实标准 |
|
||||
| 语言 | TypeScript + Node.js | Playwright 原生语言,生态最优 |
|
||||
| 通信协议 | HTTP REST + WebSocket (localhost) | 职责分离,GUI 可复用同一套 API |
|
||||
| 包管理 | npm workspaces (monorepo) | 共享类型,单仓库管理 |
|
||||
| CLI 框架 | commander + chalk | 轻量、社区熟知 |
|
||||
| 反检测框架 | `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 必须正确处理特殊字符转义。页面内容(文本、HTML)中可能包含
|
||||
引号、反斜杠、控制字符、Unicode 代理对等,统一通过 `JSON.stringify` 序列化整个响应对象,
|
||||
禁止手动拼接 JSON 字符串。
|
||||
所有返回给智能体的 JSON 必须通过 `JSON.stringify` 序列化整个响应对象,
|
||||
禁止手动拼接 JSON 字符串。页面内容字段中的引号、反斜杠、控制字符均正确处理。
|
||||
|
||||
---
|
||||
|
||||
## 3. 项目结构
|
||||
## 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 # 开发规范
|
||||
@@ -51,41 +272,56 @@ VisionL/
|
||||
│ ├── llm-integration.md # 智能体集成指南
|
||||
│ └── examples.md # 典型场景示例
|
||||
├── packages/
|
||||
│ ├── core/ # 共享库:类型定义、HTTP 客户端、工具函数
|
||||
│ ├── core/ # 共享库:类型定义、HTTP 客户端
|
||||
│ │ ├── src/
|
||||
│ │ │ ├── types/
|
||||
│ │ │ │ ├── page.ts # Page 结构
|
||||
│ │ │ │ ├── api.ts # 请求/响应类型
|
||||
│ │ │ │ └── ws.ts # WebSocket 事件类型
|
||||
│ │ │ ├── client.ts # Daemon HTTP + WS 客户端(CLI 和 GUI 共用)
|
||||
│ │ │ ├── escape.ts # JSON 安全转义
|
||||
│ │ │ │ ├── page.ts
|
||||
│ │ │ │ ├── api.ts
|
||||
│ │ │ │ ├── ws.ts
|
||||
│ │ │ │ └── fingerprint.ts # 指纹配置类型
|
||||
│ │ │ ├── client.ts
|
||||
│ │ │ ├── escape.ts
|
||||
│ │ │ └── index.ts
|
||||
│ │ └── package.json
|
||||
│ ├── daemon/ # 后台进程:Playwright 管理 + HTTP/WS 服务
|
||||
│ ├── daemon/ # 后台进程
|
||||
│ │ ├── src/
|
||||
│ │ │ ├── server.ts # HTTP + WS 入口
|
||||
│ │ │ ├── server.ts
|
||||
│ │ │ ├── routes/
|
||||
│ │ │ │ ├── pages.ts # 页面 CRUD
|
||||
│ │ │ │ ├── actions.ts # click/type/scroll/eval/navigate
|
||||
│ │ │ │ ├── content.ts # screenshot/text/html
|
||||
│ │ │ │ └── health.ts # 健康检查
|
||||
│ │ │ ├── browser-manager.ts # Playwright 浏览器/页面生命周期
|
||||
│ │ │ ├── page-registry.ts # 内存页面表(id → Playwright Page)
|
||||
│ │ │ ├── ws-relay.ts # WebSocket 事件广播
|
||||
│ │ │ └── pidfile.ts # Daemon 进程管理
|
||||
│ │ │ │ ├── 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/ # CLI 工具:用户 + LLM 交互入口
|
||||
│ └── cli/
|
||||
│ ├── src/
|
||||
│ │ ├── index.ts # CLI 入口(commander)
|
||||
│ │ ├── index.ts
|
||||
│ │ ├── commands/
|
||||
│ │ │ ├── page.ts # visionl page open/close/list/info
|
||||
│ │ │ ├── action.ts # visionl click/type/scroll/eval/wait/navigate
|
||||
│ │ │ ├── view.ts # visionl screenshot/text/html
|
||||
│ │ │ └── daemon.ts # visionl daemon start/stop/status
|
||||
│ │ ├── format.ts # 输出格式化(JSON/pretty)
|
||||
│ │ └── auto-daemon.ts # 自动拉起 daemon 逻辑
|
||||
│ │ │ ├── page.ts
|
||||
│ │ │ ├── action.ts
|
||||
│ │ │ ├── view.ts
|
||||
│ │ │ └── daemon.ts
|
||||
│ │ ├── format.ts
|
||||
│ │ └── auto-daemon.ts
|
||||
│ └── package.json
|
||||
├── package.json # monorepo root
|
||||
├── package.json
|
||||
├── tsconfig.json
|
||||
├── .gitignore
|
||||
├── README.md
|
||||
@@ -94,230 +330,163 @@ VisionL/
|
||||
|
||||
---
|
||||
|
||||
## 4. Daemon 设计
|
||||
## 5. Daemon 设计
|
||||
|
||||
### 4.1 REST API
|
||||
### 5.1 REST API
|
||||
|
||||
| 方法 | 路径 | 说明 | 请求体 | 响应 |
|
||||
|------|------|------|--------|------|
|
||||
| POST | `/pages` | 打开新页面 | `{ url, alias? }` | `{ id, url, alias?, title, status }` |
|
||||
| POST | `/pages` | 打开新页面 | `{ url, alias?, profile? }` | `{ id, url, alias?, title, status, profileId }` |
|
||||
| GET | `/pages` | 列出所有页面 | — | `[{ id, url, alias?, title, status }]` |
|
||||
| GET | `/pages/:id` | 页面详情 | — | `{ 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?, timeout?, ms? }` | `{ success }` |
|
||||
| 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" }` |
|
||||
|
||||
### 4.2 WebSocket 事件
|
||||
> **新增**:`POST /pages` 的 `profile` 字段指定指纹配置(默认 `desktop-chrome`)。
|
||||
> `GET /profiles` 返回所有内置指纹模版。
|
||||
|
||||
客户端通过 `ws://127.0.0.1:{port}/ws` 建立连接,daemon 主动推送:
|
||||
### 5.2 WebSocket 事件
|
||||
|
||||
同前,另增:
|
||||
|
||||
| 事件 | 数据 | 触发时机 |
|
||||
|------|------|---------|
|
||||
| `page:created` | `{ id, url, alias?, title }` | 新页面打开 |
|
||||
| `page:closed` | `{ id }` | 页面被杀死 |
|
||||
| `page:navigated` | `{ id, url, title }` | 页面 URL 变化 |
|
||||
| `page:crashed` | `{ id, error }` | 页面崩溃 |
|
||||
| `page:console` | `{ id, level, text }` | 控制台输出(调试用) |
|
||||
| `page:detection:warning` | `{ id, level, detail }` | 页面检测到潜在的自动化特征 |
|
||||
|
||||
### 4.3 页面生命周期
|
||||
### 5.3 页面创建流程(含反检测注入)
|
||||
|
||||
```
|
||||
visionl page open https://example.com --alias demo
|
||||
→ POST /pages { url, alias: "demo" }
|
||||
→ daemon 创建 Playwright Context + Page
|
||||
→ 注册到 PageRegistry { id: "p_abc123", page, alias: "demo" }
|
||||
→ 返回 { id: "p_abc123", url, alias: "demo", title, status: "active" }
|
||||
|
||||
visionl page screenshot p_abc123
|
||||
→ GET /pages/p_abc123/screenshot
|
||||
→ daemon 调用 page.screenshot(),返回 base64
|
||||
|
||||
visionl page kill p_abc123
|
||||
→ DELETE /pages/p_abc123
|
||||
→ daemon 调用 page.close()、context.close()
|
||||
→ 从 PageRegistry 移除
|
||||
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" }
|
||||
```
|
||||
|
||||
**规则:**
|
||||
- 页面在被显式 `kill` 之前永远存活,无超时、无闲置清理
|
||||
- Daemon 重启不会恢复之前的页面(v1 不包含此功能)
|
||||
- `visionl daemon stop` 优雅退出 daemon,但不杀死页面(Playwright 浏览器进程独立管理)
|
||||
### 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 事件
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. CLI 设计
|
||||
## 6. CLI 设计(略,同前版本)
|
||||
|
||||
### 5.1 页面管理
|
||||
|
||||
```bash
|
||||
visionl page open <url> [--alias <name>]
|
||||
visionl page list
|
||||
visionl page info <id|alias>
|
||||
visionl page kill <id|alias>
|
||||
visionl page kill-all
|
||||
```
|
||||
|
||||
### 5.2 页面操作
|
||||
|
||||
```bash
|
||||
visionl click <id|alias> <selector>
|
||||
visionl type <id|alias> <selector> <text>
|
||||
visionl scroll <id|alias> [--down <px>] [--bottom]
|
||||
visionl navigate <id|alias> <url>
|
||||
visionl eval <id|alias> <js-code>
|
||||
visionl wait <id|alias> [--selector <sel>] [--ms <n>]
|
||||
```
|
||||
|
||||
### 5.3 内容获取
|
||||
|
||||
```bash
|
||||
visionl screenshot <id|alias> [-o <file.png>]
|
||||
visionl text <id|alias>
|
||||
visionl html <id|alias>
|
||||
```
|
||||
|
||||
### 5.4 Daemon 管理
|
||||
|
||||
```bash
|
||||
visionl daemon start [--port <n>]
|
||||
visionl daemon stop
|
||||
visionl daemon status
|
||||
```
|
||||
|
||||
### 5.5 REST 直通
|
||||
|
||||
```bash
|
||||
visionl raw POST /pages '{"url":"https://example.com"}'
|
||||
visionl raw GET /pages
|
||||
visionl raw GET /pages/p_abc123/text
|
||||
```
|
||||
|
||||
### 5.6 输出格式
|
||||
|
||||
默认输出 JSON(供 LLM 解析)。使用 `--pretty` 切换为人类可读格式。
|
||||
|
||||
```json
|
||||
// 成功
|
||||
{ "ok": true, "data": { ... } }
|
||||
|
||||
// 失败
|
||||
{ "ok": false, "error": { "code": "PAGE_NOT_FOUND", "message": "页面 p_xyz 不存在" } }
|
||||
```
|
||||
|
||||
**JSON 转义规则**:所有响应通过 `JSON.stringify` 序列化完整对象。页面内容字段(`text`、`html`)
|
||||
作为不透明字符串处理,由 `JSON.stringify` 负责转义,不手动拼接。
|
||||
|
||||
### 5.7 自动拉起
|
||||
|
||||
CLI 在任何页面/操作命令前执行 daemon 存活检测:
|
||||
|
||||
```
|
||||
1. 向配置端口(默认 9527)发送 GET /health
|
||||
2. 有响应 → 继续执行
|
||||
3. 超时/拒绝 → 检查 pidfile(~/.visionl/daemon.pid)
|
||||
- pid 存活且端口可达 → 更新端口,继续
|
||||
- pid 已死或端口不可达 → 清理残留 pidfile,启动 daemon
|
||||
4. 启动:child_process.spawn('visionl-daemon', ['--port', port])
|
||||
5. 轮询 GET /health(最多 3 秒,间隔 100ms)
|
||||
6. 就绪 → 继续;超时 → 报错退出
|
||||
```
|
||||
|
||||
**状态文件:**
|
||||
- `~/.visionl/daemon.pid` — daemon 进程 PID
|
||||
- `~/.visionl/daemon.port` — daemon 监听端口
|
||||
CLI 子命令不变。`page open` 新增 `--profile <name>` 指定指纹模版。
|
||||
|
||||
---
|
||||
|
||||
## 6. 类型定义(core 包)
|
||||
## 7. 类型定义(core 包核心类型)
|
||||
|
||||
```typescript
|
||||
// packages/core/src/types/page.ts
|
||||
interface PageInfo {
|
||||
id: string; // 自动生成,如 "p_a1b2c3d4"
|
||||
url: string;
|
||||
alias?: string; // 用户指定,可选
|
||||
title: string;
|
||||
status: 'active' | 'crashed';
|
||||
// 新增
|
||||
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 };
|
||||
}
|
||||
|
||||
// packages/core/src/types/api.ts
|
||||
interface ApiResponse<T = unknown> {
|
||||
ok: boolean;
|
||||
data?: T;
|
||||
error?: {
|
||||
code: string;
|
||||
message: string;
|
||||
};
|
||||
interface FingerprintPermissions {
|
||||
notifications: PermissionState;
|
||||
geolocation: PermissionState;
|
||||
camera: PermissionState;
|
||||
microphone: PermissionState;
|
||||
}
|
||||
|
||||
// 错误码
|
||||
enum ErrorCode {
|
||||
PAGE_NOT_FOUND = 'PAGE_NOT_FOUND',
|
||||
DAEMON_UNREACHABLE = 'DAEMON_UNREACHABLE',
|
||||
INVALID_URL = 'INVALID_URL',
|
||||
ALIAS_EXISTS = 'ALIAS_EXISTS',
|
||||
TIMEOUT = 'TIMEOUT',
|
||||
INTERNAL = 'INTERNAL',
|
||||
interface FingerprintBehavior {
|
||||
mouseMoveDelay: { min: number; max: number };
|
||||
keyPressDelay: { min: number; max: number };
|
||||
scrollStepDelay: { min: number; max: number };
|
||||
}
|
||||
|
||||
// packages/core/src/types/ws.ts
|
||||
type WsEvent =
|
||||
| { type: 'page:created'; data: PageInfo }
|
||||
| { type: 'page:closed'; data: { id: string } }
|
||||
| { type: 'page:navigated'; data: { id: string; url: string; title: string } }
|
||||
| { type: 'page:crashed'; data: { id: string; error: string } }
|
||||
| { type: 'page:console'; data: { id: string; level: string; text: string } };
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 关键设计决策
|
||||
## 8. 关键设计决策
|
||||
|
||||
### v1 不包含会话恢复
|
||||
### 反检测为首要质量指标
|
||||
|
||||
页面在客户端断连后存活,但 daemon 重启后不会恢复。理由:
|
||||
- Playwright 浏览器进程状态复杂,难以序列化/恢复
|
||||
- v1 聚焦 CLI 智能体工作流,daemon 在任务期间保持运行即可
|
||||
- 会话恢复(跨 daemon 重启保存/恢复页面状态)可在 v2 添加
|
||||
单测、集成测试、手动检测结果共同构成反检测的"质量门"。任何新功能引入不能降低
|
||||
反检测评分。
|
||||
|
||||
### 无身份认证
|
||||
### 指纹模版化
|
||||
|
||||
Daemon 仅绑定 `127.0.0.1`,任何本地进程均可连接。这是刻意的设计:智能体与 daemon 运行在同一台机器。v2 可添加基于 token 的认证以支持多用户场景。
|
||||
不同网站对不同地区的浏览器有不同的预期。VisionL 提供多套预置指纹模版,
|
||||
智能体可根据目标网站选择。v1 提供 3 套:
|
||||
|
||||
### Page ID 与别名解析
|
||||
| 模版 ID | 说明 | UA 平台 |
|
||||
|---------|------|---------|
|
||||
| `desktop-chrome` | 桌面 Chrome 通用 | Linux x86_64 |
|
||||
| `desktop-windows` | Windows 10 Chrome | Windows NT 10.0 |
|
||||
| `desktop-mac` | macOS Chrome | Macintosh Intel |
|
||||
|
||||
- Page ID(`p_xxxxxxxx`)始终唯一,自动生成
|
||||
- 别名由用户指定,必须唯一。重复使用已有别名会报错
|
||||
- 命令同时接受两种形式:`visionl text myalias` 或 `visionl text p_abc123`
|
||||
- 解析顺序:先尝试别名匹配,再尝试 ID 匹配
|
||||
### Canvas 噪声策略
|
||||
|
||||
### 并发模型
|
||||
- 同一页面会话(BrowserContext)使用相同的随机种子
|
||||
- 跨页面会话自动刷新种子
|
||||
- 噪声强度可配置(0-1),默认 0.3
|
||||
|
||||
- 每个 daemon 只有一个 Playwright `Browser` 实例
|
||||
- 每次 `POST /pages` 创建一个新的 `BrowserContext`(隔离 cookie/存储)+ `Page`
|
||||
- v1 不设并发上限;由操作系统自然限制
|
||||
- 未来:可配置最大页面数、排队、优先级
|
||||
### HTTP 头注入时机
|
||||
|
||||
---
|
||||
- `User-Agent` 和 `Accept-Language`:在创建 BrowserContext 时通过 Playwright API 设置
|
||||
- `Sec-CH-UA-*` 系列:通过请求拦截(`page.route()`)修改
|
||||
|
||||
## 8. 错误处理策略
|
||||
### 其他设计决策(同前)
|
||||
|
||||
| 场景 | Daemon 行为 | CLI 行为 |
|
||||
|------|------------|---------|
|
||||
| 页面不存在 | 404 + 错误码 | 退出码 1,输出 JSON 错误 |
|
||||
| Daemon 不可达 | — | 自动拉起或退出码 1 |
|
||||
| 无效 URL | 400 + 错误码 | 退出码 1,输出 JSON 错误 |
|
||||
| 选择器未找到 | 操作响应内 404 | 退出码 1,`{ ok: false, error: ... }` |
|
||||
| 页面崩溃 | `page:crashed` WS 事件,status → crashed | 后续操作返回 500 |
|
||||
| Daemon 端口被占用 | 启动失败,日志记录 | 退出码 1,建议 `--port` 覆盖 |
|
||||
- v1 不包含会话恢复
|
||||
- 无身份认证(仅 localhost)
|
||||
- Page ID + 别名双轨引用
|
||||
- 每个 daemon 一个 Browser 实例
|
||||
|
||||
---
|
||||
|
||||
@@ -325,24 +494,31 @@ Daemon 仅绑定 `127.0.0.1`,任何本地进程均可连接。这是刻意的
|
||||
|
||||
| 层级 | 工具 | 范围 |
|
||||
|------|------|------|
|
||||
| 反检测验证 | 脚本 + 人工 | 对 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 类型检查 | 编译期正确性 |
|
||||
| `core/client.ts` | vitest + msw | HTTP 客户端行为、错误处理 |
|
||||
| `daemon/routes` | vitest + playwright-test | 路由逻辑 + 真实 Chromium |
|
||||
| `daemon/browser-manager` | vitest + playwright-test | 页面生命周期、崩溃恢复 |
|
||||
| `cli/commands` | vitest + mock 客户端 | 命令解析、输出格式化 |
|
||||
| `cli/auto-daemon` | vitest + fs mock | Pidfile 逻辑、spawn 行为 |
|
||||
| 集成测试 | vitest + 真实 daemon | CLI → daemon → Playwright 端到端 |
|
||||
| JSON 转义 | vitest | 验证特殊字符、Unicode、XSS 向量安全 |
|
||||
| `daemon/routes` | vitest + playwright-test | 路由逻辑 |
|
||||
| `cli/commands` | vitest + mock | 命令解析 |
|
||||
| 集成测试 | vitest + 真实 daemon | CLI → daemon → Playwright |
|
||||
| JSON 转义 | vitest | 特殊字符安全 |
|
||||
|
||||
---
|
||||
|
||||
## 10. 未来规划(v1 范围外)
|
||||
|
||||
- `visionl-gui`:基于 Electron/Tauri 的桌面 GUI,用于查看和管理页面
|
||||
- 会话持久化:跨 daemon 重启保存/恢复页面状态
|
||||
- 多用户支持:基于 token 的认证
|
||||
- 插件系统:自定义页面处理器
|
||||
- 远程 daemon 支持(突破 localhost 限制)
|
||||
- `visionl-gui`:桌面 GUI
|
||||
- 会话持久化
|
||||
- 更多指纹模版(移动端 Chrome、Safari、Edge 等)
|
||||
- 指纹随机化(每个页面随机微调指纹参数)
|
||||
- 行为模拟增强(贝塞尔曲线鼠标路径、人类式打字节奏)
|
||||
- 验证码自动识别(对接打码服务)
|
||||
- 多用户支持
|
||||
- 插件系统
|
||||
- 远程 daemon
|
||||
- 网络拦截和 Mock API
|
||||
- Cookie/存储管理 CLI
|
||||
- 页面录制与回放
|
||||
|
||||
Reference in New Issue
Block a user