# 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 ` 指定指纹模版。 --- ## 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 - 页面录制与回放