diff --git a/backend/browser_manager.py b/backend/browser_manager.py index 327f91b5..2f7f149b 100644 --- a/backend/browser_manager.py +++ b/backend/browser_manager.py @@ -7,6 +7,7 @@ import logging import os import socket +import sys import time from dataclasses import dataclass from pathlib import Path @@ -174,14 +175,9 @@ async def launch(self, profile: dict[str, Any]) -> RunningProfile: self._launching.add(profile_id) display, ws_port = await self.vnc.allocate() + vnc_available = self.vnc.available - try: - cdp_port = self._allocate_cdp_port() - except ValueError: - async with self._lock: - self._launching.discard(profile_id) - await self.vnc.stop_vnc(display) - raise + cdp_port = self._allocate_cdp_port() # Clean stale Chromium lock files (left by previous container crashes) user_data_dir = Path(profile["user_data_dir"]) @@ -193,27 +189,32 @@ async def launch(self, profile: dict[str, Any]) -> RunningProfile: _init_profile_defaults(user_data_dir) try: - # Start KasmVNC on the allocated display - await self.vnc.start_vnc( - display, - ws_port, - width=profile.get("screen_width", 1920), - height=profile.get("screen_height", 1080), - ) + # Start KasmVNC on the allocated display (if available) + if vnc_available: + await self.vnc.start_vnc( + display, + ws_port, + width=profile.get("screen_width", 1920), + height=profile.get("screen_height", 1080), + ) # Build fingerprint args from profile settings extra_args = self._build_fingerprint_args(profile) extra_args += profile.get("launch_args") or [] extra_args.append(f"--remote-debugging-port={cdp_port}") - # Normalize proxy format (host:port:user:pass → http://user:pass@host:port) + # Normalize proxy format raw_proxy = profile.get("proxy") or None proxy = _normalize_proxy(raw_proxy) if raw_proxy else None if proxy: _validate_proxy(proxy) - # Launch CloakBrowser on that display - # DISPLAY is passed via env kwarg to avoid process-wide os.environ mutation + # Build env: on VNC, set DISPLAY; on native desktop, don't + launch_env = {**os.environ} + if vnc_available: + launch_env["DISPLAY"] = f":{display}" + + # Launch CloakBrowser context = await launch_persistent_context_async( user_data_dir=profile["user_data_dir"], headless=bool(profile.get("headless", False)), @@ -230,7 +231,7 @@ async def launch(self, profile: dict[str, Any]) -> RunningProfile: "width": profile.get("screen_width", 1920), "height": profile.get("screen_height", 1080) - 133, }, - env={**os.environ, "DISPLAY": f":{display}"}, + env=launch_env, ) # Inject clipboard listener: captures copied text on every page @@ -380,9 +381,12 @@ def _build_fingerprint_args(self, profile: dict[str, Any]) -> list[str]: """Build extra Chromium args from profile fingerprint settings.""" args: list[str] = [ "--disable-infobars", - "--test-type", # suppress "unsupported flag: --no-sandbox" bad flags warning - "--use-angle=swiftshader", # software GL for VNC (no GPU in container) + "--test-type", ] + # Only use software GL when running via VNC (no GPU in container). + # On native desktop (Windows/macOS), hardware GL is available. + if self.vnc.available: + args.append("--use-angle=swiftshader") seed = profile.get("fingerprint_seed") if seed is not None: diff --git a/backend/database.py b/backend/database.py index e4682564..4f54ef12 100644 --- a/backend/database.py +++ b/backend/database.py @@ -4,14 +4,28 @@ import datetime import json +import os import random import sqlite3 +import sys import uuid from contextlib import contextmanager from pathlib import Path from typing import Any -DATA_DIR = Path("/data") + + +def _get_data_dir() -> Path: + """Platform-appropriate data directory.""" + if os.environ.get("CLOAKBROWSER_DATA_DIR"): + return Path(os.environ["CLOAKBROWSER_DATA_DIR"]) + if sys.platform == "win32": + base = Path(os.environ.get("LOCALAPPDATA", Path.home() / "AppData" / "Local")) + return base / "cloakbrowser-manager" + return Path("/data") + + +DATA_DIR = _get_data_dir() DB_PATH = DATA_DIR / "profiles.db" diff --git a/backend/main.py b/backend/main.py index 92727a53..092c63fb 100644 --- a/backend/main.py +++ b/backend/main.py @@ -589,11 +589,20 @@ async def get_system_status(): @app.post("/api/profiles/{profile_id}/clipboard") async def set_clipboard(profile_id: str, body: ClipboardRequest): - """Push text into the VNC session's X clipboard via xclip.""" + """Push text into the VNC session's X clipboard via xclip. + + On Windows / native desktop, clipboard push is not supported + (xclip is Linux-only). Returns success anyway so the frontend + doesn't error. + """ running = browser_mgr.running.get(profile_id) if not running: raise HTTPException(status_code=404, detail="Profile not running") + if not browser_mgr.vnc.available or running.display == 0: + # Native desktop mode — clipboard is handled by the OS directly + return {"ok": True, "native": True} + import os # Kill previous xclip for this display (it stays alive to serve paste) @@ -647,7 +656,10 @@ async def get_clipboard(profile_id: str): except Exception as exc: logger.debug("Playwright clipboard read failed: %s", exc) - # Fallback: xclip for non-Chrome clipboard owners + # Fallback: xclip for non-Chrome clipboard owners (Linux/VNC only) + if not browser_mgr.vnc.available or running.display == 0: + return {"text": ""} + import os env = {**os.environ, "DISPLAY": f":{running.display}"} @@ -676,7 +688,11 @@ async def get_clipboard(profile_id: str): @app.websocket("/api/profiles/{profile_id}/vnc") async def vnc_proxy(websocket: WebSocket, profile_id: str): - """Proxy WebSocket frames between the frontend and a profile's KasmVNC.""" + """Proxy WebSocket frames between the frontend and a profile's KasmVNC. + + When VNC is unavailable (native desktop mode), reject the connection + so the frontend can show a "browser on desktop" view. + """ if not await _check_websocket_origin(websocket): return @@ -685,6 +701,10 @@ async def vnc_proxy(websocket: WebSocket, profile_id: str): await websocket.close(code=4004, reason="Profile not running") return + if not browser_mgr.vnc.available or running.ws_port == 0: + await websocket.close(code=4005, reason="VNC unavailable — browser running on desktop") + return + # Accept with client's requested subprotocol (if any) — RFC 6455 requires # the server must not respond with a subprotocol the client didn't request. requested = websocket.scope.get("subprotocols", []) @@ -816,8 +836,8 @@ async def vnc_to_client(): ) # Dump Xvnc log on disconnect - import os - xvnc_log = f"/tmp/xvnc-{running.display}.log" + import tempfile + xvnc_log = os.path.join(tempfile.gettempdir(), f"xvnc-{running.display}.log") if os.path.exists(xvnc_log): with open(xvnc_log) as f: log_content = f.read() diff --git a/backend/vnc_manager.py b/backend/vnc_manager.py index d43e5483..5a72b2da 100644 --- a/backend/vnc_manager.py +++ b/backend/vnc_manager.py @@ -1,12 +1,18 @@ -"""KasmVNC display allocation and lifecycle management.""" +"""KasmVNC display allocation and lifecycle management. + +On platforms without KasmVNC (Windows, macOS without XQuartz), VNC is +unavailable — browsers open natively in their own window instead. +""" from __future__ import annotations import asyncio import logging +import os import shutil import subprocess -from dataclasses import dataclass, field +import sys +from dataclasses import dataclass logger = logging.getLogger("cloakbrowser.manager.vnc") @@ -18,6 +24,11 @@ class VNCInstance: process: subprocess.Popen | None = None +def _xvnc_available() -> bool: + """Check if KasmVNC (Xvnc) is installed and runnable.""" + return shutil.which("Xvnc") is not None + + class VNCManager: BASE_DISPLAY = 100 BASE_WS_PORT = 6100 @@ -25,9 +36,24 @@ class VNCManager: def __init__(self): self._allocated: dict[int, VNCInstance] = {} self._lock = asyncio.Lock() + self._available: bool | None = None # cached after first check + + @property + def available(self) -> bool: + """Whether KasmVNC is available on this system.""" + if self._available is None: + self._available = _xvnc_available() + return self._available async def allocate(self) -> tuple[int, int]: - """Returns (display_number, ws_port) for a new profile.""" + """Returns (display_number, ws_port) for a new profile. + + When VNC is unavailable, returns (0, 0) — the caller should + skip VNC setup and let the browser open natively. + """ + if not self.available: + return 0, 0 + async with self._lock: display = self.BASE_DISPLAY while display in self._allocated: @@ -42,12 +68,18 @@ async def start_vnc( ws_port: int, width: int = 1920, height: int = 1080, - ) -> subprocess.Popen: - """Start Xvnc (KasmVNC) on the given display.""" + ) -> subprocess.Popen | None: + """Start Xvnc (KasmVNC) on the given display. + + Returns None when VNC is unavailable (caller should skip VNC). + """ + if not self.available or display == 0: + logger.info("VNC unavailable — browser will open natively on desktop") + return None + xvnc_bin = shutil.which("Xvnc") or "Xvnc" # KasmVNC requires -httpd to enable the WebSocket handler on the websocket port. - # Without it, the port accepts TCP but won't do WebSocket upgrade. httpd_dir = "/usr/share/kasmvnc/www" cmd = [ @@ -59,12 +91,13 @@ async def start_vnc( "-depth", "24", "-SecurityTypes", "None", "-DisableBasicAuth", - "-interface", "127.0.0.1", # internal only, proxied by FastAPI + "-interface", "127.0.0.1", "-AlwaysShared", "-httpd", httpd_dir, ] - log_path = f"/tmp/xvnc-{display}.log" + import tempfile + log_path = os.path.join(tempfile.gettempdir(), f"xvnc-{display}.log") logger.info("Starting Xvnc on :%d (ws_port=%d) log=%s", display, ws_port, log_path) log_file = open(log_path, "w") @@ -73,7 +106,7 @@ async def start_vnc( stdout=log_file, stderr=log_file, ) - log_file.close() # Popen inherited the fd, parent doesn't need it + log_file.close() # Wait a moment for Xvnc to initialize await asyncio.sleep(0.5) @@ -95,6 +128,9 @@ async def start_vnc( async def stop_vnc(self, display: int): """Kill Xvnc for given display and release allocation.""" + if display == 0: + return + async with self._lock: instance = self._allocated.pop(display, None) @@ -118,6 +154,20 @@ async def cleanup_all(self): async def cleanup_stale(self): """Kill orphan Xvnc processes from previous runs.""" + if not self.available: + return + + if sys.platform == "win32": + # On Windows, use taskkill to clean up leftover Xvnc if somehow present + try: + subprocess.run( + ["taskkill", "/F", "/IM", "Xvnc.exe"], + capture_output=True, + ) + except FileNotFoundError: + pass + return + try: result = subprocess.run( ["pkill", "-f", r"Xvnc :[0-9]"], diff --git a/frontend/src/components/ProfileViewer.tsx b/frontend/src/components/ProfileViewer.tsx index 31543e2c..d17299f9 100644 --- a/frontend/src/components/ProfileViewer.tsx +++ b/frontend/src/components/ProfileViewer.tsx @@ -1,5 +1,5 @@ import { useEffect, useRef, useState } from "react"; -import { ClipboardCopy, Code2, Maximize2, Minimize2 } from "lucide-react"; +import { ClipboardCopy, Code2, Maximize2, Minimize2, Monitor } from "lucide-react"; import { api } from "../lib/api"; interface ProfileViewerProps { @@ -20,6 +20,7 @@ export function ProfileViewer({ profileId, cdpUrl, clipboardSync: initialClipboa const [fullscreen, setFullscreen] = useState(false); const [clipboardSync, setClipboardSync] = useState(initialClipboardSync); const [cdpCopied, setCdpCopied] = useState(false); + const [nativeDesktop, setNativeDesktop] = useState(false); useEffect(() => { let rfb: any = null; @@ -48,10 +49,15 @@ export function ProfileViewer({ profileId, cdpUrl, clipboardSync: initialClipboa if (!cancelled) setConnected(true); }); - rfb.addEventListener("disconnect", () => { + rfb.addEventListener("disconnect", (e: any) => { if (!cancelled) { setConnected(false); - onDisconnect(); + // Check if VNC is unavailable (native desktop mode) + if (e.detail?.code === 4005) { + setNativeDesktop(true); + } else { + onDisconnect(); + } } }); @@ -228,6 +234,46 @@ export function ProfileViewer({ profileId, cdpUrl, clipboardSync: initialClipboa return () => container.removeEventListener("wheel", handleWheel); }, []); + if (nativeDesktop) { + return ( +
+
+ +

Browser running on your desktop

+

+ The browser window is open directly on your desktop — no VNC viewer needed. +

+ {cdpUrl && ( +
+ +
+ )} + +
+
+ ); + } + if (error) { return (
diff --git a/run.bat b/run.bat new file mode 100644 index 00000000..04935e38 --- /dev/null +++ b/run.bat @@ -0,0 +1,28 @@ +@echo off +title CloakBrowser Manager +echo Starting CloakBrowser Manager... +echo. + +cd /d "%~dp0" + +REM Check for Python +python --version >nul 2>&1 +if %errorlevel% neq 0 ( + echo ERROR: Python is not installed or not in PATH. + echo Please install Python 3.10+ from https://www.python.org/downloads/ + echo Make sure to check "Add Python to PATH" during installation. + pause + exit /b 1 +) + +REM Check for Node.js +node --version >nul 2>&1 +if %errorlevel% neq 0 ( + echo ERROR: Node.js is not installed or not in PATH. + echo Please install Node.js from https://nodejs.org/ + pause + exit /b 1 +) + +python run.py +pause diff --git a/run.py b/run.py new file mode 100644 index 00000000..381d594a --- /dev/null +++ b/run.py @@ -0,0 +1,83 @@ +""" +CloakBrowser Manager — Windows launcher. + +Double-click run.bat to start, or run: + python run.py + +This script: +1. Installs Python dependencies (first run) +2. Builds the React frontend (first run or when changed) +3. Starts the FastAPI backend on http://localhost:8080 +""" + +import os +import subprocess +import sys +import webbrowser +from pathlib import Path + +ROOT = Path(__file__).resolve().parent + + +def step(description: str) -> None: + print(f"\n{'=' * 60}") + print(f" {description}") + print("=" * 60) + + +def run(cmd: list[str], cwd: Path | None = None, check: bool = True) -> subprocess.CompletedProcess: + print(f" $ {' '.join(cmd)}") + return subprocess.run(cmd, cwd=cwd, check=check) + + +def main() -> None: + os.chdir(ROOT) + + print(r""" + ____ _ _ ____ _ + / ___| | ___ __ _| | __| __ ) _ __ _____ _____ _ __ ___ __| | ___ _ __ + | | | |/ _ \ / _` | |/ /| _ \| '__/ _ \ \/ / _ \ '__/ _ \ / _` |/ _ \ '__| + | |___| | (_) | (_| | < | |_) | | | (_) > < __/ | | (_) | (_| | __/ | + \____|_|\___/ \__,_|_|\_\|____/|_| \___/_/\_\___|_| \___/ \__,_|\___|_| + + Browser Profile Manager + """) + + # --- 1. Install Python dependencies --- + step("Installing Python dependencies") + run([sys.executable, "-m", "pip", "install", "-r", "backend/requirements.txt"], cwd=ROOT) + + # --- 2. Build frontend --- + step("Building React frontend") + frontend_dir = ROOT / "frontend" + dist_dir = frontend_dir / "dist" + + if not (frontend_dir / "node_modules").exists(): + print(" Installing npm packages...") + run(["npm", "install"], cwd=frontend_dir) + + run(["npm", "run", "build"], cwd=frontend_dir) + + # --- 3. Start backend --- + step("Starting CloakBrowser Manager") + print(f"\n Opening http://localhost:8080 in your browser...") + print(" Press Ctrl+C to stop.\n") + + # Open browser after a short delay + webbrowser.open("http://localhost:8080") + + run( + [sys.executable, "-m", "uvicorn", "backend.main:app", "--host", "127.0.0.1", "--port", "8080"], + cwd=ROOT, + ) + + +if __name__ == "__main__": + try: + main() + except KeyboardInterrupt: + print("\n\nShutting down...") + except subprocess.CalledProcessError as e: + print(f"\nError: Command failed with exit code {e.returncode}") + print("Make sure you have Node.js and Python installed.") + sys.exit(1) diff --git "a/\346\225\231\347\250\213-\346\214\207\347\272\271\346\265\217\350\247\210\345\231\250\351\205\215\347\275\256\344\270\216\347\256\241\347\220\206.md" "b/\346\225\231\347\250\213-\346\214\207\347\272\271\346\265\217\350\247\210\345\231\250\351\205\215\347\275\256\344\270\216\347\256\241\347\220\206.md" new file mode 100644 index 00000000..9b9a43f8 --- /dev/null +++ "b/\346\225\231\347\250\213-\346\214\207\347\272\271\346\265\217\350\247\210\345\231\250\351\205\215\347\275\256\344\270\216\347\256\241\347\220\206.md" @@ -0,0 +1,690 @@ +# CloakBrowser Manager 中文教程 — 指纹浏览器配置与管理完全指南 + +> **适用版本**:CloakBrowser Manager v0.1.0 +> **适用平台**:Windows / Linux (Docker) / macOS +> **前置条件**:Python 3.10+、Node.js(Windows 本地运行);Docker(容器运行) + +--- + +## 目录 + +1. [什么是 CloakBrowser Manager?](#1-什么是-cloakbrowser-manager) +2. [核心概念:指纹浏览器 vs 普通浏览器](#2-核心概念指纹浏览器-vs-普通浏览器) +3. [安装与启动](#3-安装与启动) +4. [创建你的第一个指纹配置(Profile)](#4-创建你的第一个指纹配置profile) +5. [Profile 配置详解](#5-profile-配置详解) + - [5.1 基本信息 (BASIC)](#51-基本信息-basic) + - [5.2 网络设置 (NETWORK)](#52-网络设置-network) + - [5.3 硬件指纹 (HARDWARE)](#53-硬件指纹-hardware) + - [5.4 行为模拟 (BEHAVIOR)](#54-行为模拟-behavior) + - [5.5 标签管理 (TAGS)](#55-标签管理-tags) + - [5.6 启动参数 (LAUNCH ARGS)](#56-启动参数-launch-args) +6. [启动浏览器与实时查看](#6-启动浏览器与实时查看) +7. [CDP 自动化 API — 对接 Playwright / Puppeteer](#7-cdp-自动化-api--对接-playwright--puppeteer) +8. [实战场景](#8-实战场景) + - [场景一:多账号电商运营](#场景一多账号电商运营) + - [场景二:广告投放与验证](#场景二广告投放与验证) + - [场景三:数据采集与爬虫](#场景三数据采集与爬虫) + - [场景四:社交媒体多账号管理](#场景四社交媒体多账号管理) +9. [常见问题 (FAQ)](#9-常见问题-faq) +10. [进阶技巧](#10-进阶技巧) + +--- + +## 1. 什么是 CloakBrowser Manager? + +**CloakBrowser Manager** 是一个**浏览器配置文件管理器**,用于创建、管理和启动带有独立指纹的浏览器实例。它是 Multilogin、GoLogin、AdsPower 等商业指纹浏览器的**免费、自托管**替代方案。 + +它的底层浏览器引擎是 [**CloakBrowser**](https://github.com/CloakHQ/CloakBrowser)——一个经过 **59 处 C++ 源码级修改** 的 Chromium 浏览器,能够通过 Cloudflare Turnstile、reCAPTCHA v3(评分 0.9)、FingerprintJS 等 30+ 种反检测系统的验证。 + +``` +┌─────────────────────────────────────────────┐ +│ CloakBrowser Manager │ +│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ +│ │Profile 1│ │Profile 2│ │Profile 3│ ... │ +│ │指纹 A │ │指纹 B │ │指纹 C │ │ +│ │代理 IP₁ │ │代理 IP₂ │ │代理 IP₃ │ │ +│ └────┬────┘ └────┬────┘ └────┬────┘ │ +│ │ │ │ │ +│ ┌────▼────────────▼────────────▼────┐ │ +│ │ CloakBrowser 引擎 │ │ +│ │ 59处C++源码级指纹修改 + CDP API │ │ +│ └───────────────────────────────────┘ │ +└─────────────────────────────────────────────┘ +``` + +--- + +## 2. 核心概念:指纹浏览器 vs 普通浏览器 + +### 为什么 VPN + 无痕模式不够? + +网站不只通过 IP 和 Cookie 来识别你。它们使用 **50+ 种信号**来构建你的"浏览器指纹": + +| 检测维度 | 说明 | +|----------|------| +| Canvas 指纹 | 渲染同一张图片,不同 GPU/驱动产生细微差异 | +| WebGL 指纹 | 报告 GPU 型号、驱动版本 | +| AudioContext 指纹 | 音频处理器的硬件差异 | +| 字体列表 | 系统安装的字体集合 | +| 屏幕分辨率 | 屏幕尺寸和色深 | +| 时区和语言 | `Intl.DateTimeFormat` 等 API | +| WebRTC | 泄露真实内网 IP | +| 硬件并发数 | `navigator.hardwareConcurrency` | +| User Agent | 浏览器标识字符串 | + +### 各方案对比 + +| 方案 | 改变了什么 | 账号会关联吗? | +|------|-----------|--------------| +| **VPN** | 仅 IP 地址 | ✅ 会 — 指纹相同 | +| **无痕模式** | 清除 Cookie | ✅ 会 — 指纹相同 | +| **Chrome 多用户** | 分离书签/Cookie | ✅ 会 — 硬件指纹相同 | +| **CloakBrowser** | **全部 — 每个 Profile 模拟一台独立设备** | ❌ 不会 | + +**核心原理**:CloakBrowser 不是在 JavaScript 层面注入脚本来修改指纹(这很容易被检测),而是在 **Chromium 的 C++ 源码层面**修改了浏览器行为。这意味着即使是底层的二进制指纹检测也无法区分 CloakBrowser 和普通 Chrome。 + +--- + +## 3. 安装与启动 + +### Windows 本地运行(推荐新手) + +```bash +# 1. 克隆项目 +git clone https://github.com/CloakHQ/CloakBrowser-Manager.git +cd CloakBrowser-Manager + +# 2. 双击 run.bat 即可启动! +``` + +首次运行会自动: +1. 安装 Python 后端依赖(FastAPI、CloakBrowser 等) +2. 安装前端依赖并构建 React 界面 +3. 自动下载 CloakBrowser 二进制文件(约 200MB) +4. 启动服务器,浏览器自动打开 `http://localhost:8080` + +### Docker 运行(生产环境推荐) + +```bash +# 直接拉取镜像运行 +docker run -p 8080:8080 -v cloakprofiles:/data cloakhq/cloakbrowser-manager + +# 或从源码构建 +docker compose up --build +``` + +### 开启密码保护 + +```bash +# 设置 AUTH_TOKEN 环境变量 +# Docker: +docker run -p 8080:8080 -v cloakprofiles:/data -e AUTH_TOKEN=你的密码 cloakhq/cloakbrowser-manager + +# Windows: +set AUTH_TOKEN=你的密码 +python run.py +``` + +--- + +## 4. 创建你的第一个指纹配置(Profile) + +打开 `http://localhost:8080`,你会看到如下界面: + +> 📸 **初始界面**:左侧边栏有搜索框和"New Profile"按钮,右侧提示"Select a profile or create a new one"。 + +点击左下角的 **"+ New Profile"** 按钮,进入创建界面: + +> 📸 **新建 Profile 表单**:包含 BASIC、NETWORK、HARDWARE、BEHAVIOR、TAGS、LAUNCH ARGS、NOTES 等配置区块。 + +### 最小配置示例 + +只需填写一个名称,其他保持默认,点击 **Create** 即可: + +| 字段 | 值 | 说明 | +|------|-----|------| +| Profile Name | `Amazon卖家-美国站` | 任意名称 | +| 其他 | 默认 | 自动生成随机指纹 | + +创建成功后,Profile 会出现在左侧列表中: + +> 📸 **编辑界面**:左侧显示已创建的 Profile,右侧可编辑各项配置,顶部有 Launch/Stop 按钮。 + +--- + +## 5. Profile 配置详解 + +### 5.1 基本信息 (BASIC) + +| 配置项 | 说明 | 推荐值 | +|--------|------|--------| +| **Profile Name** | Profile 的显示名称 | 按用途命名,如"FB-账号1" | +| **Platform** | 操作系统指纹 | 根据代理 IP 所在地选择,默认 Windows(最常见) | +| **Fingerprint Seed** | 指纹种子,决定整套指纹 | 默认"Auto (random)"每次随机;输入固定数字可保持指纹不变 | + +> ⚠️ **重要**:如果你需要"回头客"身份(即同一个用户多次访问),请使用**固定 Fingerprint Seed**。如果是批量注册新账号,使用随机种子即可。 + +**Fingerprint Seed 的工作原理**: + +``` +Seed: 22680 + ├── Canvas 指纹: 基于种子生成的唯一噪声 + ├── WebGL 指纹: 基于种子选择的 GPU 型号 + ├── Audio 指纹: 基于种子生成的振荡器偏移 + ├── 字体列表: 基于种子过滤的系统字体 + └── ... 等 50+ 个维度 +``` + +同一个 Seed → 同一个"设备" → 网站看到的是同一个"回头客" + +### 5.2 网络设置 (NETWORK) + +这是**最关键的配置**,决定了网站看到的 IP 和地理位置。 + +| 配置项 | 说明 | 示例 | +|--------|------|------| +| **Proxy** | 代理服务器地址 | `http://user:pass@residential.proxy:8080` | +| **Timezone** | 浏览器时区 | `America/New_York`(应与代理 IP 一致) | +| **Locale** | 浏览器语言 | `en-US`(应与代理 IP 所在地匹配) | +| **GeoIP 自动检测** | 根据代理 IP 自动匹配时区和语言 | ✅ 勾选后无需手动设置 Timezone/Locale | + +**代理格式说明**: + +``` +# 标准格式 +http://用户名:密码@主机:端口 + +# 也支持简化格式(自动转换) +主机:端口:用户名:密码 + +# 支持的协议 +http:// https:// socks5:// +``` + +> ⚠️ **为什么需要代理?** +> 指纹浏览器解决了"指纹"问题,但 IP 地址是另一个独立维度。你需要为每个 Profile 配置**不同的代理 IP**,否则所有 Profile 共享同一个 IP,网站仍然能关联你的账号。推荐使用**住宅代理 (Residential Proxy)**,因为它们看起来像真实用户的家庭宽带 IP。 + +**Timezone/Locale 与代理 IP 的匹配**: + +``` +代理 IP: 纽约, 美国 + ├── Timezone: America/New_York ✅ + ├── Locale: en-US ✅ + └── 不一致时 → 容易触发风控 ❌ +``` + +勾选 **GeoIP 自动检测** 可以省去手动配置: + +> "Auto-detect timezone/locale from proxy IP" — CloakBrowser 会自动查询代理 IP 的地理位置并设置对应的时区和语言。 + +### 5.3 硬件指纹 (HARDWARE) + +这部分配置决定了网站在你浏览器中读取到的"硬件信息"。 + +| 配置项 | 说明 | 默认值 | +|--------|------|--------| +| **Screen Resolution** | 屏幕分辨率 | 1920×1080 (Full HD) | +| **Hardware Concurrency** | CPU 核心数 | Auto (from seed) | +| **GPU Preset** | GPU 预设(一键配置) | 无 | +| **GPU Vendor** | GPU 厂商 | Auto (from seed) | +| **GPU Renderer** | GPU 渲染器型号 | Auto (from seed) | + +**GPU 预设** 提供了常见配置的一键选择: + +- **NVIDIA RTX 3070** — 中高端游戏/工作站 +- **NVIDIA RTX 4070** — 新一代中高端 +- **AMD RX 6800 XT** — AMD 高端显卡 +- **Intel UHD 770** — 集成显卡(办公电脑) +- **Apple M3 (macOS)** — Mac 用户 + +> 💡 **提示**:对于电商运营等场景,推荐使用常见的硬件配置(如 Intel UHD 770 + 1920×1080),不要选择过于高端或稀有的配置,否则反而显得可疑。 + +**屏幕分辨率选择建议**: + +| 分辨率 | 市场份额 | 适用场景 | +|--------|---------|----------| +| 1920×1080 | ~22% | 最常见,推荐首选 | +| 1366×768 | ~14% | 低端笔记本,亚洲市场常见 | +| 2560×1440 | ~10% | 高端显示器 | +| 1440×900 | ~6% | 旧款 MacBook | +| 1536×864 | ~5% | 中等分辨率 | + +### 5.4 行为模拟 (BEHAVIOR) + +| 配置项 | 说明 | +|--------|------| +| **Human-like behavior** | 启用后,所有鼠标、键盘、滚动操作都会模拟人类行为(贝塞尔曲线移动、随机打字速度、加速→减速滚动等) | +| **Clipboard sync** | 启用剪贴板同步(在 Docker/VNC 环境下) | +| **Auto launch** | 服务器启动时自动启动此 Profile | +| **Color Scheme** | 浏览器配色方案(浅色/深色/系统默认) | +| **User Agent** | 自定义 User Agent 字符串(留空使用默认) | + +> ⚠️ **Humanize 模式** 会让自动化脚本变慢,但能更好地通过行为检测。如果你对接的是 Playwright/Puppeteer 自动化脚本,启用 `humanize=True` 可以让脚本的操作看起来更像真人。 + +### 5.5 标签管理 (TAGS) + +标签用于**组织和分类 Profile**。常见用法: + +``` +电商场景: + 🏷️ amazon 🏷️ ebay 🏷️ shopify + 🏷️ 美国站 🏷️ 日本站 🏷️ 欧洲站 + +社媒场景: + 🏷️ facebook 🏷️ twitter 🏷️ instagram + 🏷️ 主号 🏷️ 小号 🏷️ 养号中 + +运营场景: + 🏷️ 高优先级 🏷️ 待验证 🏷️ 已封禁 +``` + +标签支持自定义颜色,方便视觉识别。左侧搜索框可以按标签过滤 Profile。 + +### 5.6 启动参数 (LAUNCH ARGS) + +高级用户可以通过启动参数传递自定义 Chromium 命令行标志: + +```bash +# 加载扩展 +--load-extension=/path/to/extension + +# 禁用特定功能 +--disable-features=WebRTC + +# 窗口位置 +--window-position=0,0 +--window-size=1280,720 +``` + +--- + +## 6. 启动浏览器与实时查看 + +### Windows 本地模式 + +在 Windows 上运行 CloakBrowser Manager 时,点击 **Launch** 按钮后: + +1. CloakBrowser 浏览器窗口会在你的桌面上**直接打开**(原生窗口) +2. Web 界面会显示 "Browser running on your desktop" 提示 +3. 你可以直接在浏览器窗口中操作 + +### Docker / VNC 模式 + +在 Docker 中运行时(Linux 容器无桌面环境),浏览器通过 **VNC(虚拟网络计算)** 在 Web 界面内显示: + +1. 点击 Launch → Web 界面内嵌的 VNC 查看器连接浏览器 +2. 你可以直接在网页中操作浏览器(就像操作本地浏览器一样) +3. 支持全屏模式、剪贴板同步 + +> 📸 **VNC 查看器**:工具栏显示连接状态,支持全屏、复制 CDP URL、剪贴板同步。 + +--- + +## 7. CDP 自动化 API — 对接 Playwright / Puppeteer + +每个启动的 Profile 都会暴露一个 **CDP (Chrome DevTools Protocol)** 端点,你可以用 Playwright 或 Puppeteer 连接并自动化操作浏览器。 + +### 获取 CDP URL + +Profile 启动后,在 VNC 查看器工具栏点击 **代码图标 (< >)** 即可复制 CDP URL: + +``` +/api/profiles//cdp +``` + +### Python (Playwright) 示例 + +```python +from playwright.async_api import async_playwright + +async def auto_browse(): + async with async_playwright() as pw: + # 连接到 CloakBrowser Manager 中的某个 Profile + browser = await pw.chromium.connect_over_cdp( + "http://localhost:8080/api/profiles/你的profile-id/cdp" + ) + # 获取已打开的页面 + page = browser.contexts[0].pages[0] + + # 执行自动化操作 + await page.goto("https://www.amazon.com") + await page.fill("#twotabsearchtextbox", "laptop") + await page.click("#nav-search-submit-button") + + # 结果页面 + results = await page.text_content(".s-main-slot") + print(results) + +asyncio.run(auto_browse()) +``` + +### JavaScript (Puppeteer) 示例 + +```javascript +const puppeteer = require('puppeteer-core'); + +async function autoBrowse() { + const browser = await puppeteer.connect({ + browserURL: 'http://localhost:8080/api/profiles/你的profile-id/cdp' + }); + + const pages = await browser.pages(); + const page = pages[0]; + + await page.goto('https://www.amazon.com'); + await page.type('#twotabsearchtextbox', 'laptop'); + await page.click('#nav-search-submit-button'); + + await browser.disconnect(); +} + +autoBrowse(); +``` + +### 自动化流程示意 + +``` +你的 Python/JS 脚本 + │ + │ connect_over_cdp() + ▼ +┌───────────────────────┐ +│ CloakBrowser Manager │ +│ /api/profiles/xxx/cdp│ +└──────────┬────────────┘ + │ CDP WebSocket + ▼ +┌───────────────────────┐ +│ CloakBrowser │ +│ (独立指纹 + 代理IP) │ +└───────────────────────┘ +``` + +--- + +## 8. 实战场景 + +### 场景一:多账号电商运营 + +**需求**:运营 5 个 Amazon 卖家账号,每个账号需要看起来来自不同的设备和网络。 + +**配置方案**: + +| Profile | 代理 IP | 时区 | 指纹种子 | 备注 | +|---------|---------|------|---------|------| +| Amazon-卖家A | 美国东部住宅代理 | America/New_York | 12345 | 固定种子,保持回头客身份 | +| Amazon-卖家B | 美国西部住宅代理 | America/Los_Angeles | 23456 | 固定种子 | +| Amazon-卖家C | 英国住宅代理 | Europe/London | 34567 | en-GB 语言 | +| Amazon-卖家D | 德国住宅代理 | Europe/Berlin | 45678 | de-DE 语言 | +| Amazon-卖家E | 日本住宅代理 | Asia/Tokyo | 56789 | ja-JP 语言 | + +**操作流程**: + +``` +1. 为每个账号创建一个 Profile +2. 配置不同的代理 IP(来自不同地区) +3. 使用固定 Fingerprint Seed(保持登录状态不被风控) +4. 每日轮换启动 → 检查订单 → 回复客户 → 关闭 +5. 使用 CDP API 批量获取销售数据 +``` + +### 场景二:广告投放与验证 + +**需求**:验证广告在不同地区、不同设备上的展示效果。 + +**配置方案**: + +```python +# 批量启动多个 Profile 并截图 +import asyncio +from playwright.async_api import async_playwright + +PROFILES = { + "US-Windows": "profile-id-1", + "UK-Mac": "profile-id-2", + "JP-Windows": "profile-id-3", +} + +async def check_ad(profile_name, profile_id, url): + async with async_playwright() as pw: + browser = await pw.chromium.connect_over_cdp( + f"http://localhost:8080/api/profiles/{profile_id}/cdp" + ) + page = browser.contexts[0].pages[0] + await page.goto(url) + await page.screenshot(path=f"ad-check-{profile_name}.png") + print(f"✅ {profile_name} 截图完成") + await browser.close() + +async def main(): + tasks = [ + check_ad(name, pid, "https://你的广告落地页") + for name, pid in PROFILES.items() + ] + await asyncio.gather(*tasks) + +asyncio.run(main()) +``` + +### 场景三:数据采集与爬虫 + +**需求**:爬取目标网站的数据,避免被反爬虫系统封禁。 + +**关键配置**: + +``` +✅ 启用 humanize=True(模拟人类操作行为) +✅ 每个爬取任务使用不同的 Fingerprint Seed +✅ 配置住宅代理 IP(不要用机房 IP) +✅ 添加随机延迟,不要固定频率访问 +✅ 使用 GeoIP 自动匹配时区和语言 +``` + +**爬虫脚本模板**: + +```python +from cloakbrowser import launch_persistent_context_async +import asyncio + +async def scrape_with_profile(profile_config): + context = await launch_persistent_context_async( + user_data_dir=profile_config["user_data_dir"], + proxy=profile_config["proxy"], + geoip=True, # 自动匹配时区/语言 + humanize=True, # 人类行为模拟 + headless=False, # 有头模式更难检测 + ) + + page = await context.new_page() + await page.goto("https://目标网站.com") + + # 模拟人类滚动 + for _ in range(3): + await page.mouse.wheel(0, 300) + await asyncio.sleep(1.5) # 随机延迟 + + # 提取数据 + data = await page.evaluate("""() => { + return Array.from(document.querySelectorAll('.product')) + .map(el => ({ + title: el.querySelector('.title')?.innerText, + price: el.querySelector('.price')?.innerText, + })); + }""") + + await context.close() + return data +``` + +### 场景四:社交媒体多账号管理 + +**需求**:管理多个 Facebook/Twitter/Instagram 账号,每个账号独立隔离。 + +**Profile 组织**: + +``` +🏷️ facebook + ├── FB-主号 (固定种子 11111, 美国住宅代理) + ├── FB-小号1 (随机种子, 美国住宅代理) + └── FB-小号2 (随机种子, 美国住宅代理) + +🏷️ twitter + ├── TW-品牌号 (固定种子 22222, 日本住宅代理) + └── TW-个人号 (固定种子 33333, 新加坡代理) +``` + +**注意事项**: + +> ⚠️ 社媒平台的风控比电商平台更严格。建议: +> - 新号前 7-14 天只做浏览操作("养号") +> - 不要在同一天内大量操作 +> - 使用 humanize=True 模式 +> - 每次操作间隔随机时间(2-10 秒) + +--- + +## 9. 常见问题 (FAQ) + +### Q: Fingerprint Seed 应该随机还是固定? + +| 场景 | 建议 | 原因 | +|------|------|------| +| 首次注册新账号 | **随机** | 每个新号看起来来自不同设备 | +| 长期运营已有账号 | **固定** | 保持"回头客"身份,避免触发风控 | +| 批量爬取数据 | **随机** | 每次访问看起来是不同用户 | +| 广告验证 | **固定** | 模拟特定地区/设备的真实用户 | + +### Q: 免费版 CloakBrowser 和 Pro 版有什么区别? + +| 功能 | 免费版 | Pro 版 | +|------|--------|--------| +| C++ 源码级指纹修改 | ✅ 59 处 | ✅ 更多 | +| Cloudflare Turnstile 通过 | ✅ | ✅ | +| reCAPTCHA v3 评分 | 0.9 | 0.9 | +| 二进制版本 | v146 | v148 (最新) | +| 平台支持 | Windows/Linux/Mac | 全部 | +| Playwright/Puppeteer API | ✅ | ✅ | +| 商业支持 | 社区 | 邮件支持 | + +**对于大多数用户,免费版完全够用。** + +### Q: 如何检查指纹是否真的不同? + +每个 Profile 启动时会**自动创建**一组书签,位于"Detection Tests"文件夹中: + +- **Rebrowser Bot Detector** — 综合检测 +- **BrowserScan Bot** — 机器人检测 +- **Pixelscan** — 指纹一致性检查 +- **CreepJS** — 深度指纹分析 +- **BrowserLeaks (Canvas/WebGL/Fonts)** — 各维度独立检测 + +启动 Profile 后,打开这些检测网站验证你的指纹是否真实、是否与其他 Profile 不同。 + +### Q: 一个 Profile 占用多少资源? + +- **内存**:约 512 MB / Profile +- **磁盘**:首次下载浏览器约 200 MB;每个 Profile 的会话数据约 50-200 MB +- **CPU**:空闲时几乎为 0,活跃浏览时约 5-15% + +### Q: 数据存储在哪里? + +| 平台 | 数据路径 | +|------|---------| +| Windows | `%LOCALAPPDATA%\cloakbrowser-manager\` | +| Docker | `/data/`(挂载到宿主机 volume) | +| 自定义 | 设置 `CLOAKBROWSER_DATA_DIR` 环境变量 | + +每个 Profile 的数据存储在 `profiles//` 目录下,包含完整的 Chrome 用户数据(Cookie、LocalStorage、扩展等)。 + +--- + +## 10. 进阶技巧 + +### 技巧一:批量创建 Profile + +```python +import requests + +API = "http://localhost:8080/api/profiles" + +profiles = [ + {"name": f"FB-账号{i}", "proxy": f"http://user:pass@proxy-{i}:8080"} + for i in range(1, 11) +] + +for p in profiles: + resp = requests.post(API, json=p) + print(f"✅ 创建: {resp.json()['name']} (ID: {resp.json()['id']})") +``` + +### 技巧二:通过 CDP 注入自定义脚本 + +```python +# 连接到已有 Profile +browser = await pw.chromium.connect_over_cdp(cdp_url) +page = browser.contexts[0].pages[0] + +# 注入反检测脚本 +await page.add_init_script(""" + // 覆盖 navigator.webdriver + Object.defineProperty(navigator, 'webdriver', { get: () => false }); +""") + +# 创建新页面时自动注入 +page = await browser.contexts[0].new_page() +``` + +### 技巧三:使用 Auto Launch 实现"开机自启" + +在 Profile 设置中勾选 **"Launch automatically when container starts"**: + +- Docker 容器启动时 → 自动启动该 Profile +- 服务器重启后 → 自动恢复所有浏览器实例 +- 配合 Cron job → 定时启动/停止 Profile + +### 技巧四:健康检查与监控 + +```bash +# 检查 Manager 状态 +curl http://localhost:8080/api/status +# 返回: {"running_count": 3, "binary_version": "146.0.7680.177.5", "profiles_total": 10} + +# 检查单个 Profile 状态 +curl http://localhost:8080/api/profiles//status +# 返回: {"status": "running", "display": ":100", "cdp_url": "/api/profiles/xxx/cdp"} +``` + +### 技巧五:自定义 Chrome 扩展 + +在 Profile 的 **LAUNCH ARGS** 中添加: + +``` +--load-extension=C:\extensions\my-extension +``` + +扩展会在 Profile 启动时自动加载,可用于: +- 广告拦截(uBlock Origin) +- 自动化辅助(如自动填表) +- 代理管理(SwitchyOmega) + +--- + +## 总结 + +CloakBrowser Manager + CloakBrowser 提供了一个**完整的指纹浏览器解决方案**: + +| 特性 | 说明 | +|------|------| +| 🔒 **真正的指纹隔离** | C++ 源码级修改,非 JS 注入 | +| 🌐 **代理集成** | 支持 HTTP/HTTPS/SOCKS5 代理 | +| 🤖 **自动化 API** | 通过 CDP 对接 Playwright/Puppeteer | +| 📦 **自托管** | 数据完全在你自己的机器上 | +| 🆓 **免费** | 核心功能完全免费 | +| 🖥️ **跨平台** | Windows 原生运行 + Docker 部署 | + +--- + +> 📧 问题反馈:[GitHub Issues](https://github.com/CloakHQ/CloakBrowser-Manager/issues) +> 🌐 官方网站:[cloakbrowser.dev](https://cloakbrowser.dev) +> 📖 CloakBrowser 文档:[github.com/CloakHQ/CloakBrowser](https://github.com/CloakHQ/CloakBrowser)