帮助中心
🔍
✨ 幻影指纹浏览器 · 帮助中心

您好,有什么可以帮您?

查阅文档、了解 API 用法、配置指纹浏览器。支持 Windows / Mac / Android / iPhone 多平台模拟,30+ 维度指纹保护。

📖 创建配置详解
30+
指纹保护维度
5
支持平台
REST
标准 API
本地
完全本地化部署
首页快速开始

🚀 快速开始

📅 2025-03-05⏱ 约 3 分钟

幻影浏览器基于 Chromium,支持通过图形界面或 API 创建多个独立浏览器环境,每个环境有独立的指纹、代理和存储空间。

💡
API 服务端口可自定义设置,启动应用后自动开启。可在 幻影浏览器 → API & MCP → API端口配置 中查看或修改「自动化API端口」。

启动步骤

1

下载并安装

下载安装包,按向导完成安装,首次启动会自动初始化数据库。

2

登录账户

使用账户登录。API 调用需处于已登录状态,同时提供 API Key。

3

创建分组

在「窗口管理」页面创建分组,记录 group_id(API 创建配置时需要)。

4

获取 API Key

进入「API管理」页面,点击生成 API Key,用于所有 API 请求的身份验证。

5

发起第一个请求

POST /api/browsers/profiles 发送请求,创建第一个浏览器配置。

第一个 API 请求

bash · curl
curl -X POST http://127.0.0.1:{YOUR_PORT}/api/browsers/profiles \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_API_KEY" \ -d '{"group_id": 1}'

下一步

首页下载与安装

📦 下载与安装

📅 2025-03-05
💡
请从官方渠道下载安装包,避免使用第三方渠道。安装完成后双击启动,首次启动自动完成数据库初始化。

系统要求

  • Windows 10 / 11(64位)
  • 内存:4GB 以上(推荐 8GB+)
  • 磁盘空间:2GB 以上

安装步骤

1

下载安装包

从官方网站下载最新版本的安装包(.exe)。

2

运行安装程序

双击安装包,按照向导提示完成安装。

3

首次启动

启动程序,系统自动初始化本地数据库和 Chrome 内核。

首页创建浏览器窗口

🖥️ 创建浏览器窗口

📅 2025-03-05

在主界面点击「新建窗口」,选择平台、配置指纹和代理后保存,即可创建新的浏览器配置。也可通过 API 批量创建。

💡
API 方式可以批量创建,详见 创建配置 API 文档

支持的平台

平台适用场景特点
Windows 10 / 11通用场景兼容性最佳,推荐默认选择
Mac模拟苹果用户Safari 风格 UA
Android移动端/社交媒体自动生成手机型号,开启开发者工具
iPhoneiOS/高端用户模拟自动生成 iPhone 型号
首页指纹配置

🔐 指纹配置说明

📅 2025-03-05

幻影浏览器支持 30+ 维度指纹保护,防止被网站追踪识别。

指纹类型推荐模式说明
Canvasnoise在 Canvas 渲染中注入随机噪声
WebGLnoiseWebGL 渲染器信息噪声保护
WebGPUnoiseGPU 信息随机化
AudioContextnoise音频处理指纹噪声
ClientRectsnoise元素位置偏移噪声
字体指纹noise字体列表随机化
WebRTCreplace替换真实 IP,防止泄露
媒体设备noise摄像头/麦克风设备 ID 噪声
JA4noiseTLS 指纹随机化
💡
推荐所有指纹相关参数设为 noise 模式,每个实例都会生成独一无二的指纹值。

验证指纹效果

启动浏览器后访问以下网站验证:

首页代理IP配置

🌐 代理 IP 配置

📅 2025-03-05

详见 代理 IP 说明文档,支持 HTTP、HTTPS、SOCKS5 以及平台代理。

首页分组管理

📂 分组管理

📅 2025-03-05

分组用于对浏览器配置进行归类管理。创建分组后可获取 group_id,API 创建配置时需要传入该值。

💡
API 创建配置时 group_id必填参数,必须使用真实存在的分组 ID,否则创建失败。

方式一:通过界面查看 group_id

1

打开幻影浏览器

确保已登录账号。

2

进入窗口管理页面

点击左侧「窗口管理」。

3

查看分组 ID

分组名称旁边的数字即为 group_id,填入脚本中 GROUP_ID = 1(替换为你的数字)。

方式二:通过 API 获取分组列表

GET /api/browsers/groups
GET http://127.0.0.1:{YOUR_PORT}/api/browsers/groups Authorization: Bearer YOUR_API_KEY // 返回格式(注意:用 code 和 msg,不是 success): { "code": 200, "msg": { "list": [ { "id": 1, "name": "默认分组" }, { "id": 2, "name": "测试分组" } ] } }
python · 获取所有分组
import requests BASE_URL = "http://127.0.0.1:36108/api" # 替换为你的端口 HEADERS = {"Authorization": "Bearer YOUR_API_KEY"} resp = requests.get(f"{BASE_URL}/browsers/groups", headers=HEADERS, timeout=5) data = resp.json() if data.get("code") == 200: groups = data["msg"]["list"] for g in groups: print(f"分组 ID={g['id']} 名称={g['name']}") # 输出示例: # 分组 ID=1 名称=默认分组 # 分组 ID=2 名称=测试分组 else: print(f"获取失败: {data}")
⚠️
如果 API 返回 401,请检查:① 幻影浏览器是否已登录;② API Key 是否正确;③ 端口号是否与「API端口配置」中的「自动化API端口」一致。
首页API 概述

📡 API 概述

📅 2025-03-05

幻影浏览器提供完整的 RESTful API,运行在本地,所有数据不经过云端,保证隐私安全。

基本信息

项目说明
基础 URLhttp://127.0.0.1:{YOUR_PORT}/api(端口见 API & MCP → API端口配置)
协议HTTP/1.1 · RESTful
数据格式JSON(Content-Type: application/json
认证方式Bearer Token

主要端点

POST/api/browsers/profiles创建浏览器配置(返回 data.id 即 profile_id)
POST/api/browsers/launch/:id启动浏览器(返回 data.http 调试地址)
DEL/api/browsers/:id停止浏览器进程
DEL/api/browsers/profiles/:id删除浏览器配置
GET/api/browsers/groups获取分组列表(返回 msg.list[],含 group_id)— 不知道分组 ID?可用此接口查询,也可在幻影指纹浏览器「项目管理」页面直接查看分组旁的数字

统一响应格式

json · 成功
{ "success": true, "data": { ... }, "message": "操作成功" }
json · 失败
{ "success": false, "error": "错误信息描述" }
首页API 说明认证方式

🔑 认证方式

📅 2025-03-05

双重认证机制

API 采用双重认证,需同时满足:

  • ✅ 幻影浏览器应用处于 已登录 状态
  • ✅ 请求头携带有效的 API Key
⚠️
若未登录或 API Key 无效,所有请求将返回 401 Unauthorized

获取 API Key

1

打开 API 管理页面

主界面顶部导航点击「API管理」。

2

生成 API Key

点击「生成 API Key」,复制密钥妥善保存。

3

在请求头中使用

添加到 Authorization 请求头。

请求头格式

http header
Authorization: Bearer YOUR_API_KEY

Python 示例

python
import requests headers = { "Content-Type": "application/json", "Authorization": "Bearer YOUR_API_KEY" } resp = requests.post( "http://127.0.0.1:{YOUR_PORT}/api/browsers/profiles", headers=headers, json={"group_id": 1} ) print(resp.json())
首页API 说明浏览器管理 API

🤖 浏览器管理 API

📅 2025-03-05

启动浏览器

⚠️
重要提示:此接口只支持 POST 请求,使用 GET 请求会返回 HTML 错误页,导致 invalid character '<' looking for beginning of value 错误。同时需要在请求头中提供认证信息。
📝
错误排查:如果遇到 invalid character '<' looking for beginning of value 错误,请检查:
1. 是否使用了 POST 请求
2. 是否添加了 Authorization: Bearer YOUR_API_KEY
3. 接口路径是否正确:/api/browsers/launch/{id}
POST /api/browsers/launch/:id
POST http://127.0.0.1:{YOUR_PORT}/api/browsers/launch/123?headless=false&showCheckPage=true Authorization: Bearer YOUR_API_KEY // 成功响应 { "success": true, "data": { "http": "http://127.0.0.1:9222", // 取最后端口号连接 DrissionPage/Selenium "ws": "ws://127.0.0.1:9222/devtools/browser/xxx" // Playwright 用 ws, "driver": "C:\\path\\to\\hyBrowser.exe", "chromedriver": "C:\\path\\to\\chromedriver.exe", "pid": 31295 } } // Python 提取端口: debug_port = data["http"].split(":")[-1] # → "9222"

Query 参数说明

参数名默认值说明
headless"false""true" = 无界面后台运行(省资源,适合服务器);"false" = 显示窗口(调试推荐)
showCheckPage"true""true" = 启动后展示指纹/代理检测页面;"false" = 直接进入,跳过检测
args追加 Chrome 启动参数,JSON 数组字符串,如 ["--window-size=1280,720","--window-position=0,0","--disable-blink-features=AutomationControlled"]

停止浏览器进程

DELETE /api/browsers/:id
DELETE http://127.0.0.1:{YOUR_PORT}/api/browsers/123 // 响应:{ "success": true } // HTTP 404 = 进程已不存在,可视为成功,无需处理

删除浏览器配置

DELETE /api/browsers/profiles/:id
DELETE http://127.0.0.1:{YOUR_PORT}/api/browsers/profiles/123 // ⚠️ 注意:此接口返回格式与其他接口不同! // 响应:{ "code": 200, "msg": "删除成功" }(用 code 判断,不是 success) // HTTP 404 = 配置不存在(已删除),可视为成功 # Python 判断方式: result = resp.json() if result.get('code') == 200: print("删除成功")

获取分组列表

GET /api/browsers/groups
GET http://127.0.0.1:{YOUR_PORT}/api/browsers/groups Authorization: Bearer YOUR_API_KEY // 响应: { "code": 200, "msg": { "list": [{ "id": 1, "name": "默认分组" }] } }
启动后返回的 data.http 端口可直接用于 DrissionPage / Selenium / Playwright / Puppeteer 接入远程调试。
首页API 说明创建配置 API

➕ 创建浏览器配置 API

📅 2025-03-05
📖
本接口拥有完整独立文档,包含所有参数与多语言示例。
点击查看《API 创建配置详细文档》→

接口信息

项目说明
请求方式POST
接口路径/api/browsers/profiles
Content-Typeapplication/json
最少参数仅需 group_id

最简示例

json · 最少参数
{ "group_id": 1 } // 系统自动生成:UA、指纹噪声值、设备信息、分辨率等

常用参数

参数必填说明
group_id必填分组 ID,在窗口管理页面或 GET /api/browsers/groups 中查看
name可选配置名称,默认自动生成(含时间戳)
remark可选备注信息
browser_config.platform可选Windows 11 / Windows 10 / Mac / Android / iPhone,默认 Windows 11
browser_config.canvas自动Canvas 指纹:noise(推荐)/ real
browser_config.webrtc自动WebRTC:replace(推荐,防止真实IP泄露)/ real / disabled
browser_config.systemSpoofing自动系统信息伪装:enabled / disabled
proxy_config.type可选代理类型:Directly(直连)/ Http / Https / Socks5
proxy_config.proxyMode可选custom(自定义填 host/port)/ platform(使用平台代理,需填 platformProxyId)
proxy_config.host可选代理服务器地址(proxyMode=custom 时填写)
proxy_config.port可选代理端口(字符串格式,如 "8080"
other_config.cloudSync可选云同步,默认 true
other_config.blockImages可选阻止图片加载,默认 false(设为 true 可提升速度)
other_config.blockVideos可选阻止视频加载,默认 false
other_config.multiWindow可选多窗口模式,默认 false

查看完整参数文档(含 30+ 参数详细说明)→

首页Python + DrissionPage

🐉 Python + DrissionPage 接入

📅 2025-03-05⏱ 约 10 分钟推荐
强烈推荐新手使用 DrissionPage!它基于 CDP 协议,原生支持反检测,不需要单独下载 ChromeDriver,代码也比 Selenium 更简洁易懂。

准备工作(必看)

📌
在运行代码前,请先确认以下几点:
① 幻影浏览器已打开并已登录账号(不登录 API 无法使用)
② 获取 API Key:点击顶部「API管理」页面 → 生成 API Key → 复制备用
③ 确认 API 端口:点击「API & MCP → API端口配置」查看「自动化API端口」,默认是 36108
④ 确认分组 ID:有两种方式获取:
  • 方式一(界面查看):在幻影指纹浏览器「项目管理」或「窗口管理」页面,分组名称旁边的数字即为 group_id
  • 方式二(API 获取):调用 GET /api/browsers/groups 接口,返回所有分组列表及对应 ID(代码获取见下方「分组管理」章节)

安装依赖

打开命令行(Windows 按 Win+R 输入 cmd),运行:

bash · 命令行安装
pip install DrissionPage requests

① 完整基础示例(含详细注释)

下面是一个完整的示例,初学者修改 YOUR_API_KEYgroup_id 后可直接运行:

📦 导入 & 配置(只需修改这里)

💡
两个库分别是干什么的?
requests:负责和幻影浏览器的 API 通信(创建配置、启动、停止),就像你浏览器访问网页一样,只是在代码里发送请求。
DrissionPage:负责控制已经启动的浏览器,模拟人的操作(点击、输入、抓取内容等)。
关系:requests 负责"召唤浏览器",DrissionPage 负责"操控浏览器"。
python
import requests # 用来调用幻影浏览器 API(创建/启动/停止) from DrissionPage import ChromiumPage, ChromiumOptions # 用来控制浏览器操作 # ── 以下三行是你唯一需要修改的地方 ────────────────────── BASE_URL = "http://127.0.0.1:36108/api" # API 地址,端口在「API端口配置」中查看,默认 36108 API_KEY = "YOUR_API_KEY" # ← 替换成你的 API Key(在幻影「API管理」页面生成) GROUP_ID = 1 # ← 替换成你的分组 ID(方式一:幻影指纹浏览器「项目管理」分组旁数字;方式二:调用 GET /api/browsers/groups 接口获取) # HEADERS:每次请求时附带的"身份证",告诉服务器你有权限访问 # Authorization: Bearer xxx → 标准的 Token 认证格式,不需要修改格式,只改 API_KEY 即可 HEADERS = { "Authorization": f"Bearer {API_KEY}", # 身份认证,Bearer 是固定写法 "Content-Type": "application/json" # 告诉服务器我发的数据是 JSON 格式 }

第一步:创建浏览器配置

创建成功后系统自动生成指纹,返回唯一的 profile_id,后续启动/删除都需要用到它。

python
print("正在创建浏览器配置...") r = requests.post( url=f"{BASE_URL}/browsers/profiles", headers=HEADERS, timeout=90, json={ "group_id": GROUP_ID, "browser_config": { "canvas": "noise", # Canvas 指纹噪声,让每个窗口指纹不同 "webrtc": "replace" # WebRTC 替换真实 IP,防止本机 IP 泄露 }, "proxy_config": { # 不需要代理可删掉这段 "type": "Http", # Http / Socks5 / Directly(直连) "host": "1.2.3.4", "port": "8080", "username": "user", # 无认证则留空 "" "password": "pass" } } ) result = r.json() if not result.get("success"): print(f"创建失败:{result.get('error', '未知错误')}"); exit(1) profile_id = result["data"]["id"] print(f"创建成功!配置 ID = {profile_id}")

第二步:启动浏览器,获取调试端口

幻影启动真实 Chrome 进程,返回调试端口。debug_port 就是 DrissionPage 的"遥控器入口"。

⚠️
🚨 启动浏览器前必须先确认:幻影浏览器主程序已打开并已登录账号!
幻影未运行时调用启动接口会立即失败(ConnectionError)。请先双击桌面图标打开幻影浏览器,登录后再运行脚本。
⚠️
启动不了浏览器?先检查 BASE_URL 的写法:
本文档示例中 BASE_URL = "http://127.0.0.1:36108/api"(完整地址,含 /api 路径)。
如果你的脚本里只存了端口号(如 PORT = "36108"),则启动接口地址要写成:
url = f"http://127.0.0.1:{PORT}/api/browsers/launch/{profile_id}"
两种写法结果完全相同,关键是不要重复或遗漏 /api 这段路径。

🚫 报错 invalid character '<' looking for beginning of value
这说明服务器返回了 HTML 页面(404 错误页)而非 JSON,根本原因是接口路径写错了
幻影浏览器正确路径:/api/browsers/launch/{id}(browsers 有 s,用 launch)
❌ 错误写法(AdsPower 等其他平台):/api/browser/start/{id}
注意:browser(单数)≠ browsers(复数),startlaunch
python
print("正在启动浏览器,请稍等...") launch = requests.post( url=f"{BASE_URL}/browsers/launch/{profile_id}", # 如果 BASE_URL 只存了端口号,改成: # url=f"http://127.0.0.1:{PORT}/api/browsers/launch/{profile_id}", headers=HEADERS, timeout=180, # 首次启动较慢(加载内核),最多等 180 秒 params={ "headless": "false", # false=有界面窗口;true=后台无界面 "showCheckPage": "true" # true=启动后展示指纹/代理检测页 } ) launch_result = launch.json() if not launch_result.get("success"): print(f"启动失败:{launch_result.get('error')}"); exit(1) # 返回格式:http://127.0.0.1:9222,取最后的 9222 debug_port = launch_result["data"]["http"].split(":")[-1] print(f"浏览器已启动!调试端口 = {debug_port}")

第三步:DrissionPage 连接到已启动的浏览器

是"连接"到幻影已经启动的浏览器,不是重新打开一个新的浏览器窗口。

💡
这三行代码在干什么?
1. ChromiumOptions() — 创建一个"连接配置对象",相当于填写一张"我要连接哪个浏览器"的表单。
2. set_local_port(debug_port) — 在表单上填入端口号,告诉 DrissionPage:去 127.0.0.1:端口 这个地方找浏览器
3. ChromiumPage(addr_or_opts=co) — 拿着这张表单去连接,连接成功后 page 就代表那个浏览器窗口,之后所有操作都用 page.xxx() 来执行。
python
co = ChromiumOptions() # 创建连接配置对象(相当于填写"我要连哪个浏览器"的表单) co.set_local_port(debug_port) # 填入端口号,告诉 DrissionPage 去哪里找浏览器 page = ChromiumPage(addr_or_opts=co) # 连接!成功后 page 就代表那个浏览器,后续所有操作都通过它 print("DrissionPage 连接成功!开始执行操作...")

第四步:执行业务操作

💡
选择器怎么写?(如何找到页面上的元素)
"css:#kw" → 找 id="kw" 的元素(# 代表 id)
"css:.class-name" → 找 class="class-name" 的元素(. 代表 class)
"xpath://button[text()='提交']" → 找文字是"提交"的按钮
不知道选择器怎么写?在浏览器里右键元素 → 检查 → 右键该元素代码 → Copy → Copy selector,粘贴到 page.ele() 里即可。
python
page.get("https://www.baidu.com") # 打开网址,会等页面完全加载后才继续执行下一行 print(f"当前页面标题:{page.title}") # page.title 就是浏览器标签上显示的那串文字 # 示例:在百度搜索框中输入关键词并点击搜索按钮 # page.ele("css:#kw") → 找 id="kw" 的元素(即百度搜索框) # timeout=10 → 最多等 10 秒,如果 10 秒内没找到就返回 None search_box = page.ele("css:#kw", timeout=10) if search_box: # 判断是否找到了搜索框(找不到时不报错,直接跳过) search_box.input("DrissionPage 教程") # 往搜索框里输入内容(会先清空再输入) page.ele("css:#su").click() # 找搜索按钮(id="su")并点击 print("搜索成功!")

第五步:清理(必须执行)

不清理的话浏览器进程和配置数据会一直占用系统资源。想保留配置下次继续用,可以不调用删除配置那行。

⚠️
清理顺序不能搞反!必须先停进程,再删配置:
1. page.quit() — 断开 DrissionPage 的控制连接(但浏览器进程还在运行)
2. DELETE /browsers/{id} — 停止浏览器进程(关掉那个 Chrome 窗口)
3. DELETE /browsers/profiles/{id} — 删除配置数据(从幻影里彻底删除这条记录)
如果想下次复用这个浏览器配置(保留历史记录、Cookie 等),注释掉第 3 行即可。
python
print("任务完成,正在清理...") page.quit() # 第1步:断开 DrissionPage 控制连接(不会关闭浏览器窗口) requests.delete(url=f"{BASE_URL}/browsers/{profile_id}", headers=HEADERS, timeout=15) # 第2步:↑ 停止浏览器进程(真正关掉那个 Chrome 窗口) requests.delete(url=f"{BASE_URL}/browsers/profiles/{profile_id}", headers=HEADERS, timeout=60) # 第3步:↑ 删除浏览器配置(从幻影彻底删除;想保留配置可注释掉这行) print("清理完成!")

② 多线程并发(同时跑多个浏览器)

当你需要同时操作多个浏览器窗口时,使用多线程。下面的例子演示同时启动3个浏览器,每个使用不同代理:

⚠️
新手注意:多线程代码中每个线程要用 try/finally 包裹,这样即使中间出错,也能保证最后的清理代码(关闭浏览器、删除配置)一定会执行,不会留下"僵尸进程"。
💡
多线程是什么意思?
就是让电脑同时做多件事,比如一次性开3个浏览器同时工作,而不是一个一个排队等。
代码结构说明:
try: — 正常业务逻辑写在这里
except Exception as e: — 出错时打印错误信息,不让程序崩掉
finally:不管成功还是失败,这里的代码都会执行(专门放清理代码)
t.start() — 启动线程(开始并行运行)
t.join() — 等待该线程结束(主程序要等所有线程都跑完才继续)
python · 多线程并发
import random, time, requests from threading import Lock, Thread from DrissionPage import ChromiumPage, ChromiumOptions BASE_URL = "http://127.0.0.1:36108/api" HEADERS = {"Authorization": "Bearer YOUR_API_KEY"} # ────────────────────────────────────────── # 线程安全代理池 # 作用:确保多个线程不会同时使用同一个代理 # get() → 从池中取出一个代理,标记为"使用中" # release() → 任务完成后,把代理还回池中 # ────────────────────────────────────────── class ProxyPool: def __init__(self, proxies): self.pool = proxies.copy() # 可用代理列表 self.used = [] # 使用中的代理列表 self.lock = Lock() # 锁,防止多个线程同时修改列表(避免冲突) def get(self): with self.lock: # with lock 保证同一时刻只有一个线程进入这段代码 if not self.pool: # 所有代理都用完了,把"使用中"的全部重新放回可用列表 self.pool = self.used[:] self.used = [] p = random.choice(self.pool) # 随机选一个代理 self.pool.remove(p) # 从可用列表移除 self.used.append(p) # 加入使用中列表 return p def release(self, p): with self.lock: if p in self.used: self.used.remove(p) # 从使用中移除 self.pool.append(p) # 还回可用列表 # ────────────────────────────────────────── # 每个线程执行的任务函数 # tid → 线程编号(用于日志区分) # pool → 代理池对象 # target_url → 要访问的目标网址 # ────────────────────────────────────────── def worker(tid, pool, target_url): proxy = pool.get() # 从代理池取一个代理 host, port, user, pwd = proxy.split(":") # 解析代理格式 "ip:端口:用户名:密码" profile_id = None # 先初始化为 None,finally 块里判断是否需要清理 page = None try: # 第一步:创建浏览器配置(带代理) r = requests.post( f"{BASE_URL}/browsers/profiles", headers=HEADERS, timeout=90, json={ "group_id": 1, "proxy_config": { "type": "Http", "host": host, "port": port, "username": user, "password": pwd } } ) profile_id = r.json()["data"]["id"] print(f"[线程{tid}] 配置创建成功,ID={profile_id}") # 第二步:启动浏览器 # ⚠️ 前提:幻影浏览器主程序必须已打开并登录,否则此步会报 ConnectionError! # ⚠️ BASE_URL 写法:本示例 BASE_URL="http://127.0.0.1:36108/api"(含/api) # 若你只存端口号(PORT="36108"),地址改为:f"http://127.0.0.1:{PORT}/api/browsers/launch/{profile_id}" # 关键:不要重复或遗漏 /api 路径。 launch = requests.post( f"{BASE_URL}/browsers/launch/{profile_id}", headers=HEADERS, timeout=180 ) debug_port = launch.json()["data"]["http"].split(":")[-1] print(f"[线程{tid}] 浏览器已启动,端口={debug_port}") # 第三步:DrissionPage 连接 co = ChromiumOptions() co.set_local_port(debug_port) page = ChromiumPage(addr_or_opts=co) # 第四步:执行业务操作 page.get(target_url) print(f"[线程{tid}] 页面标题:{page.title}") # ↓↓↓ 在这里写你的具体业务逻辑 ↓↓↓ time.sleep(10) # ↑↑↑ 在这里写你的具体业务逻辑 ↑↑↑ except Exception as e: print(f"[线程{tid}] 出现错误:{e}") finally: # 无论成功还是失败,finally 块里的代码都会执行 if page: try: page.quit() except: pass if profile_id: # 停止浏览器进程 requests.delete(f"{BASE_URL}/browsers/{profile_id}", headers=HEADERS, timeout=15) # 删除配置(如需保留可注释掉下面这行) requests.delete(f"{BASE_URL}/browsers/profiles/{profile_id}", headers=HEADERS, timeout=60) pool.release(proxy) # 将代理归还代理池,供其他任务使用 print(f"[线程{tid}] 清理完成") # ────────────────────────────────────────── # 启动多线程任务 # proxies 列表格式:["ip:端口:用户名:密码", ...] # 没有用户名密码的代理写成 "1.2.3.4:8080::"(用户名密码留空) # ────────────────────────────────────────── proxies = [ "1.2.3.4:8080:user1:pass1", # 代理1 "5.6.7.8:8080:user2:pass2", # 代理2 ] pool = ProxyPool(proxies) # 创建3个线程,同时访问3个不同网址 target_urls = ["https://www.baidu.com", "https://www.163.com", "https://www.qq.com"] threads = [ Thread(target=worker, args=(i+1, pool, target_urls[i])) for i in range(len(target_urls)) ] for t in threads: t.start() # 同时启动所有线程(每个线程各自独立运行 worker 函数) for t in threads: t.join() # 等待所有线程都执行完毕(主程序在这里暂停,直到最后一个线程结束) print("所有线程执行完毕!")

③ 窗口大小 & 多线程自动排列

多线程时每个浏览器窗口的位置通过 --window-size--window-position 参数控制,按屏幕宽高自动分列铺满,互不重叠。

💡
为什么要设置窗口位置?
多线程同时启动多个浏览器时,如果不设置位置,所有窗口会重叠在屏幕同一个地方。
calc_positions(n) 函数会根据屏幕大小,自动像贴瓷砖一样把窗口均匀铺满屏幕。
关键启动参数说明:
--window-size=宽,高 — 每个浏览器窗口的像素尺寸,如 500,500
--window-position=x,y — 窗口左上角距屏幕左/上边缘的像素距离
--disable-blink-features=AutomationControlled — 隐藏自动化特征,防止网站检测到脚本
--disable-dev-shm-usage — 防止内存不足导致崩溃(Windows 也加上,无害)

位置计算函数(复用于所有线程)

python
import screeninfo # pip install screeninfo WIN_W = 500 # 每个窗口宽度(px),可自定义 WIN_H = 500 # 每个窗口高度(px),可自定义 def calc_positions(count): """按屏幕大小自动计算 count 个窗口的位置,列优先排列""" screens = screeninfo.get_monitors() if not screens: return [{'x': i * 100, 'y': i * 50} for i in range(count)] sw, sh = screens[0].width, screens[0].height # 主屏幕宽高,如 1920×1080 cols = max(1, int(sw / WIN_W)) # 每行最多几列,1920/500 = 3 rows = max(1, int(sh / WIN_H)) # 最多几行, 1080/500 = 2 return [ {'x': (i % cols) * WIN_W, 'y': ((i // cols) % rows) * WIN_H} for i in range(count) ] # 5 个线程 / 500×500 / 1920×1080 的排列效果: # [0](0,0) [1](500,0) [2](1000,0) # [3](0,500) [4](500,500)

在多线程中使用(启动时传入对应位置)

python
import json, requests from threading import Thread from DrissionPage import ChromiumPage, ChromiumOptions positions = calc_positions(5) # 提前计算好 5 个窗口的位置 def worker(tid, pos): """创建配置 → 启动(带窗口位置)→ 连接 DrissionPage → 执行任务 → 清理""" profile_id = None page = None try: # 第一步:创建浏览器配置 r = requests.post(f"{BASE_URL}/browsers/profiles", headers=HEADERS, timeout=90, json={"group_id": 1}) profile_id = r.json()["data"]["id"] print(f"[线程{tid}] 配置 ID = {profile_id}") # 第二步:启动浏览器(传入窗口大小和位置) # ⚠️ 前提:幻影浏览器主程序必须已打开并登录,否则此步会报 ConnectionError! # ⚠️ BASE_URL 写法:本示例 BASE_URL="http://127.0.0.1:36108/api"(含/api) # 若你只存端口号(PORT="36108"),地址改为:f"http://127.0.0.1:{PORT}/api/browsers/launch/{profile_id}" # 关键:不要重复或遗漏 /api 路径。 launch_args = json.dumps([ f"--window-size={WIN_W},{WIN_H}", f"--window-position={pos['x']},{pos['y']}", "--disable-blink-features=AutomationControlled", "--disable-dev-shm-usage" ]) launch = requests.post( f"{BASE_URL}/browsers/launch/{profile_id}", headers=HEADERS, timeout=180, params={"headless": "false", "args": launch_args} ) debug_port = launch.json()["data"]["http"].split(":")[-1] print(f"[线程{tid}] 端口 = {debug_port}") # 第三步:DrissionPage 连接 co = ChromiumOptions() co.set_local_port(debug_port) page = ChromiumPage(addr_or_opts=co) # 第四步:执行业务操作 page.get("https://www.baidu.com") print(f"[线程{tid}] 页面标题:{page.title}") # ↓↓↓ 在这里写你的具体业务逻辑 ↓↓↓ # ↑↑↑ 在这里写你的具体业务逻辑 ↑↑↑ except Exception as e: print(f"[线程{tid}] 出现错误:{e}") finally: # 无论成功还是失败,都要清理 if page: try: page.quit() except: pass if profile_id: requests.delete(f"{BASE_URL}/browsers/{profile_id}", headers=HEADERS, timeout=15) requests.delete(f"{BASE_URL}/browsers/profiles/{profile_id}", headers=HEADERS, timeout=60) print(f"[线程{tid}] 清理完成") threads = [Thread(target=worker, args=(i+1, positions[i])) for i in range(5)] for t in threads: t.start() for t in threads: t.join()

④ 常用操作速查

下面是 DrissionPage 最常用的操作,遇到不会的直接复制对应代码即可:

python · 元素查找与操作
# ── 查找单个元素 ── # timeout 是等待时间(秒),如果元素加载慢可以调大,找不到返回 None page.ele(".class-name", timeout=5) # 用 CSS 类名查找,如 <div class="class-name"> page.ele("#element-id", timeout=5) # 用 ID 查找,如 <input id="element-id"> page.ele("css:div.box > p", timeout=5) # 完整 CSS 选择器写法 page.ele("xpath://button[text()='提交']") # XPath:查找按钮文字是"提交"的按钮 page.ele("tag:input") # 按标签名查找(找第一个 input 标签) # ── 查找多个元素(返回列表)── items = page.eles(".item") # 找到所有 class=item 的元素 for item in items: print(item.text) # 遍历打印每个元素的文本 # ── 元素操作 ── el = page.ele(".my-button") el.click() # 点击元素 el.input("要输入的内容") # 在输入框里输入内容(会先清空再输入) el.scroll.to_see() # 滚动页面直到这个元素出现在屏幕上 el.text # 获取元素的文本内容 el.attr("href") # 获取属性值,如 <a href="..."> 的链接地址 el.html # 获取元素的 HTML 代码 # ── 页面操作 ── page.get("https://example.com") # 跳转到指定网址,会等页面加载完才返回 page.run_js("window.scrollTo(0, document.body.scrollHeight);") # 执行 JS,滚动到页面底部 page.scroll.down(300) # 向下滚动 300 像素 page.scroll.up(300) # 向上滚动 300 像素 page.title # 获取当前页面标题(如 "百度一下,你就知道") page.url # 获取当前页面 URL page.html # 获取整个页面的 HTML 源码
python · 实用技巧
import time # ── 技巧1:关闭弹窗广告 ── # 很多网站有关闭按钮,尝试找几种常见的关闭按钮样式并点击 close_xpaths = [ '//*[text()="×"]', '//*[text()="✕"]', '//*[text()="关闭"]', '//*[contains(@class,"close")]', '//*[contains(@class,"popup-close")]', ] for xp in close_xpaths: try: el = page.ele(f"xpath:{xp}", timeout=0.5) # 最多等 0.5 秒,快速判断 if el and el.states.is_displayed: el.click() print("已关闭弹窗") break # 找到并点击后退出循环 except: pass # ── 技巧2:进入 iframe(嵌套框架里的元素需要先切入 iframe)── # 有些验证码、登录框是放在 iframe 里的,普通 ele() 找不到,需要先进入 iframe iframe_el = page.ele("tag:iframe") # 找到 iframe 元素 frame = page.get_frame(iframe_el) # 切入 iframe frame.ele(".btn-inside-iframe").click() # 操作 iframe 里的元素 # ── 技巧3:处理新弹出的标签页 ── # 点击某些链接会打开新标签页,需要切换过去才能操作 tabs_before = page.get_tabs() # 记录点击前有哪些标签页 page.ele(".open-new-tab").click() # 点击会打开新标签的按钮 time.sleep(1.5) # 等待新标签页打开 new_tabs = [t for t in page.get_tabs() if t not in tabs_before] if new_tabs: new_tab = new_tabs[0] new_tab.get("https://new-page-target.com") # 在新标签页操作
首页Python + Selenium

🐍 Python + Selenium 接入

📅 2025-03-05⏱ 约 10 分钟
⚠️
如果你是新手,建议优先使用 DrissionPage,代码更简洁易懂。

准备工作

📌
① 幻影浏览器已打开并已登录
② 获取 API Key:点击「API管理」页面 → 生成 API Key → 复制
③ 确认 API 端口:「API & MCP → API端口配置」查看,默认 36108

安装依赖

bash
pip install selenium requests

① 完整基础示例(含详细注释)

Selenium 的连接方式是 "Attach 模式":不是自己启动 Chrome,而是连接幻影已经启动的 Chrome 进程。关键在 options.debugger_address 这一行。

📦 导入 & 配置(只需修改这里)

python
import requests from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC BASE_URL = "http://127.0.0.1:36108/api" # 端口在「API端口配置」中查看 API_KEY = "YOUR_API_KEY" # ← 替换成你的 API Key GROUP_ID = 1 # ← 替换成你的分组 ID(方式一:幻影指纹浏览器「项目管理」分组旁数字;方式二:调用 GET /api/browsers/groups 接口获取) HEADERS = { "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json" }

第一步:创建浏览器配置

告诉幻影要创建什么样的浏览器环境(指纹、代理等),系统自动生成指纹,返回 profile_id

python
print("正在创建浏览器配置...") r = requests.post( url=f"{BASE_URL}/browsers/profiles", headers=HEADERS, timeout=90, json={ "group_id": GROUP_ID, "browser_config": { "canvas": "noise", # Canvas 指纹噪声,防止被追踪 "webrtc": "replace" # WebRTC 替换真实 IP }, "proxy_config": { # 没有代理可以删掉这段 "type": "Http", # Http / Socks5 / Directly(直连) "host": "1.2.3.4", "port": "8080", "username": "user", # 无认证留空 "" "password": "pass" } } ) result = r.json() if not result.get("success"): print(f"创建失败:{result.get('error')}"); exit(1) profile_id = result["data"]["id"] print(f"创建成功!配置 ID = {profile_id}")

第二步:启动浏览器,获取调试端口

幻影启动真实 Chrome 进程并开放调试端口,debug_port 是 Selenium 接管浏览器的"入口"。

⚠️
🚨 启动浏览器前必须先确认:幻影浏览器主程序已打开并已登录账号!
幻影未运行时调用启动接口会立即失败(ConnectionError)。请先双击桌面图标打开幻影浏览器,登录后再运行脚本。
同时确认 BASE_URL 端口号正确:在幻影「API & MCP → API端口配置」中查看。
⚠️
启动不了浏览器?先检查 BASE_URL 的写法:
本文档示例中 BASE_URL = "http://127.0.0.1:36108/api"(完整地址,含 /api 路径)。
如果你的脚本里只存了端口号(如 PORT = "36108"),则启动接口地址要写成:
url = f"http://127.0.0.1:{PORT}/api/browsers/launch/{profile_id}"
两种写法结果完全相同,关键是不要重复或遗漏 /api 这段路径。

🚫 报错 invalid character '<' looking for beginning of value
这说明服务器返回了 HTML 页面(404 错误页)而非 JSON,根本原因是接口路径写错了
幻影浏览器正确路径:/api/browsers/launch/{id}(browsers 有 s,用 launch)
❌ 错误写法(AdsPower 等其他平台):/api/browser/start/{id}
注意:browser(单数)≠ browsers(复数),startlaunch
python
print("正在启动浏览器,请稍等...") launch = requests.post( url=f"{BASE_URL}/browsers/launch/{profile_id}", headers=HEADERS, timeout=180, # 首次启动较慢(加载内核),最多等 180 秒 params={"headless": "false"} # false=显示窗口;true=无界面后台运行 ) launch_data = launch.json() if not launch_data.get("success"): print(f"启动失败:{launch_data.get('error')}"); exit(1) # 返回格式:http://127.0.0.1:9222 → 取最后的 9222 debug_port = launch_data["data"]["http"].split(":")[-1] print(f"浏览器已启动!调试端口 = {debug_port}")

第三步:Selenium 连接到已运行的浏览器(Attach 模式)

只设置 debugger_address,格式为 127.0.0.1:端口(不带 http://),不要设置 binary_location

python
options = Options() options.debugger_address = f"127.0.0.1:{debug_port}" # 关键:接管幻影启动的 Chrome driver = webdriver.Chrome(options=options) # 连接幻影启动的 Chrome,不会新开浏览器 print("Selenium 连接成功!")

第四步:执行业务操作

python
driver.get("https://www.baidu.com") print(f"当前页面标题:{driver.title}") # 推荐用 WebDriverWait 等待元素,比 time.sleep() 更可靠 wait = WebDriverWait(driver, timeout=10) search_box = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "#kw"))) search_box.send_keys("Selenium 教程") search_btn = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, "#su"))) search_btn.click() print("搜索成功!")

第五步:清理(必须执行)

想保留配置下次继续用,可以不调用删除配置那行。

python
print("任务完成,正在清理...") driver.quit() # 断开 Selenium 连接(不删除幻影里的配置) requests.delete(f"{BASE_URL}/browsers/{profile_id}", headers=HEADERS, timeout=15) requests.delete(f"{BASE_URL}/browsers/profiles/{profile_id}", headers=HEADERS, timeout=60) print("清理完成!")

② 多线程并发

每个线程独立创建一个浏览器实例,互不干扰。每个线程的 profile_iddriver 都是独立的:

python · 多线程并发
import requests, time from threading import Thread from selenium import webdriver from selenium.webdriver.chrome.options import Options BASE_URL = "http://127.0.0.1:36108/api" HEADERS = {"Authorization": "Bearer YOUR_API_KEY"} def task(tid, target_url): """每个线程独立执行:创建配置 → 启动 → 操作 → 清理""" profile_id = None driver = None try: # 第一步:创建浏览器配置 r = requests.post(f"{BASE_URL}/browsers/profiles", headers=HEADERS, timeout=90, json={"group_id": 1}) # 每个线程使用相同的分组,但指纹是独立的 profile_id = r.json()["data"]["id"] print(f"[线程{tid}] 配置 ID = {profile_id}") # 第二步:启动浏览器 # ⚠️ 前提:幻影浏览器主程序必须已打开并登录,否则此步会报 ConnectionError! # ⚠️ BASE_URL 写法:本示例 BASE_URL="http://127.0.0.1:36108/api"(含/api) # 若你只存端口号(PORT="36108"),地址改为:f"http://127.0.0.1:{PORT}/api/browsers/launch/{profile_id}" # 关键:不要重复或遗漏 /api 路径。 launch = requests.post(f"{BASE_URL}/browsers/launch/{profile_id}", headers=HEADERS, timeout=180) debug_port = launch.json()["data"]["http"].split(":")[-1] print(f"[线程{tid}] 端口 = {debug_port}") # 第三步:Selenium 连接 opts = Options() opts.debugger_address = f"127.0.0.1:{debug_port}" driver = webdriver.Chrome(options=opts) # 第四步:执行任务 driver.get(target_url) print(f"[线程{tid}] 标题 = {driver.title}") # ↓↓↓ 在这里添加你的业务逻辑 ↓↓↓ time.sleep(10) # ↑↑↑ 在这里添加你的业务逻辑 ↑↑↑ except Exception as e: print(f"[线程{tid}] 错误:{e}") finally: # 无论是否出错,都要清理(避免占用资源) if driver: try: driver.quit() except: pass if profile_id: requests.delete(f"{BASE_URL}/browsers/{profile_id}", headers=HEADERS, timeout=15) requests.delete(f"{BASE_URL}/browsers/profiles/{profile_id}", headers=HEADERS, timeout=60) print(f"[线程{tid}] 清理完成") # 定义各线程要访问的目标 URL,有几个 URL 就启动几个线程 target_urls = ["https://www.baidu.com", "https://www.163.com", "https://www.qq.com"] threads = [Thread(target=task, args=(i+1, target_urls[i])) for i in range(len(target_urls))] for t in threads: t.start() # 同时启动所有线程 for t in threads: t.join() # 等待所有线程完成 print("所有线程执行完毕!")

③ 窗口大小 & 多线程自动排列

多线程时每个浏览器窗口的位置通过 --window-size--window-position 参数控制,按屏幕宽高自动分列铺满,互不重叠。

位置计算函数(复用于所有线程)

python
import screeninfo # pip install screeninfo WIN_W = 500 # 每个窗口宽度(px),可自定义 WIN_H = 500 # 每个窗口高度(px),可自定义 def calc_positions(count): """按屏幕大小自动计算 count 个窗口的位置,列优先排列""" screens = screeninfo.get_monitors() if not screens: return [{'x': i * 100, 'y': i * 50} for i in range(count)] sw, sh = screens[0].width, screens[0].height # 主屏幕宽高,如 1920×1080 cols = max(1, int(sw / WIN_W)) # 每行最多几列,1920/500 = 3 rows = max(1, int(sh / WIN_H)) # 最多几行, 1080/500 = 2 return [ {'x': (i % cols) * WIN_W, 'y': ((i // cols) % rows) * WIN_H} for i in range(count) ] # 5 个线程 / 500×500 / 1920×1080 的排列效果: # [0](0,0) [1](500,0) [2](1000,0) # [3](0,500) [4](500,500)

在多线程中使用(启动时传入对应位置)

python
import json, requests from threading import Thread from selenium import webdriver from selenium.webdriver.chrome.options import Options positions = calc_positions(5) # 提前计算好 5 个窗口的位置 def task(tid, pos): """创建配置 → 启动(带窗口位置)→ 连接 Selenium → 执行任务 → 清理""" profile_id = None driver = None try: # 第一步:创建浏览器配置 r = requests.post(f"{BASE_URL}/browsers/profiles", headers=HEADERS, timeout=90, json={"group_id": 1}) profile_id = r.json()["data"]["id"] print(f"[线程{tid}] 配置 ID = {profile_id}") # 第二步:启动浏览器(传入窗口大小和位置) # ⚠️ 前提:幻影浏览器主程序必须已打开并登录,否则此步会报 ConnectionError! # ⚠️ BASE_URL 写法:本示例 BASE_URL="http://127.0.0.1:36108/api"(含/api) # 若你只存端口号(PORT="36108"),地址改为:f"http://127.0.0.1:{PORT}/api/browsers/launch/{profile_id}" # 关键:不要重复或遗漏 /api 路径。 launch_args = json.dumps([ f"--window-size={WIN_W},{WIN_H}", f"--window-position={pos['x']},{pos['y']}", "--disable-blink-features=AutomationControlled", "--disable-dev-shm-usage" ]) launch = requests.post( f"{BASE_URL}/browsers/launch/{profile_id}", headers=HEADERS, timeout=180, params={"headless": "false", "args": launch_args} ) debug_port = launch.json()["data"]["http"].split(":")[-1] print(f"[线程{tid}] 端口 = {debug_port}") # 第三步:Selenium 连接 opts = Options() opts.debugger_address = f"127.0.0.1:{debug_port}" driver = webdriver.Chrome(options=opts) # 第四步:执行业务操作 driver.get("https://www.baidu.com") print(f"[线程{tid}] 页面标题:{driver.title}") # ↓↓↓ 在这里写你的具体业务逻辑 ↓↓↓ # ↑↑↑ 在这里写你的具体业务逻辑 ↑↑↑ except Exception as e: print(f"[线程{tid}] 出现错误:{e}") finally: # 无论成功还是失败,都要清理 if driver: try: driver.quit() except: pass if profile_id: requests.delete(f"{BASE_URL}/browsers/{profile_id}", headers=HEADERS, timeout=15) requests.delete(f"{BASE_URL}/browsers/profiles/{profile_id}", headers=HEADERS, timeout=60) print(f"[线程{tid}] 清理完成") threads = [Thread(target=task, args=(i+1, positions[i])) for i in range(5)] for t in threads: t.start() for t in threads: t.join()

④ 常用操作速查

python · 常用 API
from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.keys import Keys # ── 等待元素(推荐,比 sleep 更可靠)── wait = WebDriverWait(driver, timeout=10) # 最多等 10 秒 # 等待元素出现在 DOM 中(不一定可见) el = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, ".my-element"))) # 等待元素可见且可点击(推荐在点击操作前使用) btn = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, ".submit-btn"))) btn.click() # ── 元素查找(不等待,直接找)── driver.find_element(By.CSS_SELECTOR, ".class-name") # 找第一个匹配的 driver.find_elements(By.CSS_SELECTOR, ".item") # 找所有匹配的,返回列表 driver.find_element(By.XPATH, '//button[text()="提交"]') # XPath 查找 driver.find_element(By.ID, "username") # 用 ID 查找 # ── 输入操作 ── inp = driver.find_element(By.CSS_SELECTOR, "input[name='q']") inp.clear() # 先清空已有内容 inp.send_keys("输入的内容") # 模拟键盘输入 inp.send_keys(Keys.RETURN) # 模拟按回车键(相当于提交表单) # ── 获取信息 ── driver.find_element(By.CSS_SELECTOR, "h1").text # 获取元素文本内容 driver.find_element(By.CSS_SELECTOR, "a").get_attribute("href") # 获取属性 driver.title # 页面标题 driver.current_url # 当前 URL driver.page_source # 整个页面 HTML 源码 # ── 执行 JavaScript ── driver.execute_script("window.scrollTo(0, document.body.scrollHeight);") # 滚动到底部 driver.execute_script("arguments[0].click();", el) # JS 强制点击(普通点击被拦截时用这个) result = driver.execute_script("return document.title;") # 执行并获取返回值 # ── 截图 ── driver.save_screenshot("screenshot.png") # 截取整个可视区域 el.screenshot("element.png") # 只截某个元素 # ── 处理多个标签页 ── driver.window_handles # 获取所有标签页的 handle 列表 driver.switch_to.window(driver.window_handles[-1]) # 切换到最新打开的标签页 driver.switch_to.window(driver.window_handles[0]) # 切回第一个标签页 driver.close() # 关闭当前标签页(不关闭整个浏览器) # ── 处理 iframe(嵌套框架)── driver.switch_to.frame(driver.find_element(By.TAG_NAME, "iframe")) # 切入 iframe driver.find_element(By.CSS_SELECTOR, ".btn-in-iframe").click() driver.switch_to.default_content() # 切回主页面
访问 bot.sannysoft.com 可以验证指纹伪装是否生效,看到绿色代表通过检测。
首页Playwright

🎭 Playwright 接入

📅 2025-03-05⏱ 约 10 分钟
💡
Playwright 通过 WebSocket 地址连接幻影已启动的浏览器(Attach 模式)。它同时支持 Python 和 Node.js 两种语言,下面分别演示。

准备工作

📌
① 幻影浏览器已打开并已登录
② 获取 API Key:点击「API管理」页面 → 生成 API Key → 复制
③ 确认 API 端口:「API & MCP → API端口配置」查看,默认 36108

① Python 接入(详细注释版)

💡
Playwright 和 DrissionPage 的区别:两者都能控制浏览器,但连接方式不同。
· DrissionPage 用 HTTP 端口(data.http)连接,上手更简单
· Playwright 用 WebSocket 地址(data.ws)连接,功能更强,支持截图、网络拦截等高级特性
· 两者都是"接管"幻影已启动的浏览器,而不是自己新建一个
bash · 安装(终端执行一次即可)
pip install playwright requests playwright install chromium
python · playwright 完整示例
import requests from playwright.sync_api import sync_playwright # ══════════════════════════════════════════════════════════ # 【第零步】配置区 —— 只需修改这里的三个变量 # ══════════════════════════════════════════════════════════ # BASE_URL:幻影浏览器本地 API 地址,端口在「API & MCP → API端口配置」中查看 # API_KEY :在「API管理」页面生成并复制 # GROUP_ID:在窗口管理页面,分组名称旁边的数字就是 group_id BASE_URL = "http://127.0.0.1:36108/api" API_KEY = "YOUR_API_KEY" # ← 替换成你的 API Key GROUP_ID = 1 # ← 替换成你的分组 ID # 请求头:每次 API 请求都要带上,用来验证你的身份 # "Bearer " 是固定前缀,后面跟 API Key HEADERS = { "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json" } # ══════════════════════════════════ # 【第一步】创建浏览器配置 # # 这一步是告诉幻影:"帮我新建一个浏览器,用以下指纹和代理设置" # 成功后会返回一个 profile_id(配置ID),后续启动和删除都靠它 # timeout=90 表示最多等 90 秒,代理质量差时可能较慢 # ══════════════════════════════════ print("正在创建浏览器配置...") r = requests.post( url=f"{BASE_URL}/browsers/profiles", headers=HEADERS, timeout=90, json={ "group_id": GROUP_ID, "browser_config": { "canvas": "noise", # canvas噪声:让每个窗口的指纹都不同,防止被识别为同一台设备 "webrtc": "replace" # WebRTC替换:防止 WebRTC 泄露你的真实 IP 地址 }, # 【代理配置】如果不用代理,把整个 proxy_config 这段删掉即可 "proxy_config": { "type": "Http", # 代理协议:Http / Https / Socks5 / Directly(直连) "host": "1.2.3.4", # 代理服务器 IP(替换成你的) "port": "8080", # 代理端口 "username": "user", # 代理账号(没有认证就填空字符串 "") "password": "pass" # 代理密码(没有认证就填空字符串 "") } } ) result = r.json() if not result.get("success"): # 创建失败:通常是 group_id 不对、API Key 错误或端口号不对 print(f"创建失败:{result.get('error')}") exit(1) # 退出程序,不继续执行 # 从返回数据中提取 profile_id,这是这个浏览器配置的唯一编号 profile_id = result["data"]["id"] print(f"创建成功!配置 ID = {profile_id}") # ══════════════════════════════════ # 【第二步】启动浏览器,获取 WebSocket 调试地址 # # ⚠️ 前提:幻影浏览器主程序必须已打开并已登录账号! # 幻影未运行时此步会报 ConnectionError,请先打开幻影再运行脚本。 # ⚠️ BASE_URL 写法:本示例 BASE_URL="http://127.0.0.1:36108/api"(含/api) # 若你只存端口号(PORT="36108"),地址改为:f"http://127.0.0.1:{PORT}/api/browsers/launch/{profile_id}" # 关键:不要重复或遗漏 /api 路径。 # # 启动后幻影会真正打开一个 Chrome 窗口, # 并返回一个 ws:// 地址,Playwright 通过这个地址连接并控制浏览器 # # ws 地址示例:ws://127.0.0.1:9222/devtools/browser/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx # # 和 DrissionPage/Selenium 用 http 端口不同: # DrissionPage → 用 data["http"] 里的端口号 # Playwright → 用 data["ws"] 完整的 WebSocket 地址 # ══════════════════════════════════ print("正在启动浏览器,请稍等...") launch = requests.post( url=f"{BASE_URL}/browsers/launch/{profile_id}", headers=HEADERS, timeout=180, # 首次启动需要加载内核,可能需要 1~3 分钟,不要设太短 params={"headless": "false"} # headless=false:显示浏览器窗口;=true:无界面后台运行(省资源) ) launch_data = launch.json() if not launch_data.get("success"): # 启动失败:通常是代理不通、内核未下载完成 print(f"启动失败:{launch_data.get('error')}") exit(1) # 取出 ws 字段:这是完整的 WebSocket 地址,Playwright 必须用这个来连接 ws_url = launch_data["data"]["ws"] print(f"浏览器已启动!WS 地址 = {ws_url}") # ══════════════════════════════════ # 【第三步 ~ 第四步】Playwright 连接并操作浏览器 # # 用 try/except/finally 结构的原因: # try → 正常执行的代码放这里 # except → 如果 try 里出错了,在这里打印错误信息 # finally→ 无论 try 是否出错,这里的代码都一定会执行(用来清理资源) # # CDP(Chrome DevTools Protocol)是 Chrome 提供的远程调试协议, # connect_over_cdp 就是通过这个协议"接管"已经打开的浏览器 # ══════════════════════════════════ try: with sync_playwright() as p: # 【关键】连接到幻影已启动的浏览器(不是新开一个) # connect_over_cdp 会通过 WebSocket 握手,然后返回可操作的 browser 对象 browser = p.chromium.connect_over_cdp(ws_url) # 获取浏览器上下文(context)和标签页(page) # 上下文:可以理解为一个"浏览器会话",有独立的 Cookie、Storage # 幻影启动后自带一个上下文(contexts[0])和一个空白标签页(pages[0]) context = browser.contexts[0] # 取第一个(也是唯一的)浏览器上下文 page = context.pages[0] # 取第一个标签页 print("Playwright 连接成功!") # ════════════════════════════ # 【第四步】执行自动化操作 # ════════════════════════════ # goto:让浏览器跳转到指定 URL,默认等待页面 load 事件触发才继续 page.goto("https://www.baidu.com") # title() 注意要加括号,它是一个方法(DrissionPage 的 page.title 不加括号,这里不同) print(f"当前标题:{page.title()}") # wait_for_selector:等待某个元素出现在页面上再继续 # "#kw" 是百度搜索框的 CSS 选择器,timeout=5000 表示最多等 5 秒(毫秒单位) # 超时会抛出 TimeoutError 异常 page.wait_for_selector("#kw", timeout=5000) # fill:先清空输入框,再填入内容(比 type 快,不模拟逐字输入) # type 方法会逐字符输入(慢但更像人操作),需要的话可改成 page.type("#kw","内容",delay=50) page.fill("#kw", "Playwright 教程") # click:点击指定元素,"#su" 是百度的搜索按钮 page.click("#su") # wait_for_load_state("networkidle"):等待页面网络请求全部完成(没有新的请求发出) # 其他可用值:"load"(DOMContentLoaded后)、"domcontentloaded"(更快但页面可能未渲染完) page.wait_for_load_state("networkidle") # screenshot:截取当前可视区域并保存为图片 # full_page=True 可截取整个页面(包含滚动部分) page.screenshot(path="screenshot.png") print("截图已保存到 screenshot.png!") # ╔══════════════════════════════════════╗ # ║ 在这里替换成你自己的业务逻辑 ║ # ║ 例如:登录、数据采集、填表、点赞等 ║ # ╚══════════════════════════════════════╝ # ↓↓↓ 在这里写你的具体业务逻辑 ↓↓↓ # ↑↑↑ 在这里写你的具体业务逻辑 ↑↑↑ # browser.close() 只是断开 Playwright 和浏览器的连接,不会关闭幻影窗口 # 如果你想保留浏览器窗口继续手动操作,可以删掉这行 browser.close() except Exception as e: # 任何异常都会在这里被捕获并打印,方便排查问题 # 常见错误:连接超时(浏览器还没启动完)、元素找不到(页面结构变了) print(f"发生错误:{e}") finally: # ════════════════════════════ # 【第五步】清理(无论成功还是失败都一定执行) # # 为什么要清理? # 1. DELETE /browsers/{id} → 关闭 Chrome 进程,释放内存和 CPU # 2. DELETE /browsers/profiles/{id} → 删除配置文件,释放磁盘空间 # # 两步都要做,顺序不能反(先停进程,再删配置) # ════════════════════════════ print("任务完成,正在清理...") # 停止浏览器进程(关闭 Chrome 窗口) requests.delete(f"{BASE_URL}/browsers/{profile_id}", headers=HEADERS, timeout=15) # 删除配置数据(释放磁盘占用) requests.delete(f"{BASE_URL}/browsers/profiles/{profile_id}", headers=HEADERS, timeout=60) print("清理完成!")

② Node.js 接入(详细注释版)

💡
什么是 async/await?
Node.js 中所有网络请求都是"异步"的(不会阻塞程序等待结果)。
async function 声明一个异步函数;await 放在异步操作前,表示"等这一步完成再继续"。
简单理解:加了 await 就变成了从上到下顺序执行,像 Python 一样直观。
bash · 安装(终端执行一次)
npm install playwright axios npx playwright install chromium
javascript · playwright node.js 完整示例
const { chromium } = require('playwright'); const axios = require('axios'); // ══════════════════════════════════════════════════════════ // 【配置区】只需修改这里的三个变量 // ══════════════════════════════════════════════════════════ const BASE_URL = 'http://127.0.0.1:36108/api'; // 端口在「API & MCP → API端口配置」中查看 const API_KEY = 'YOUR_API_KEY'; // ← 替换成你的 API Key const GROUP_ID = 1; // ← 替换成你的分组 ID // 请求头:每次 HTTP 请求都要带上,axios 会自动附加 const headers = { 'Authorization': `Bearer ${API_KEY}`, // 反引号字符串可以用 ${变量} 插入变量 'Content-Type': 'application/json' }; // ────────────────────────────────────────────────────────── // 用一个自调用的 async 函数包裹所有代码,这样才能在里面使用 await // (async () => { ... })() 是 Node.js 中运行异步代码的标准写法 // ────────────────────────────────────────────────────────── (async () => { // 提前声明 profileId,let 可以在后面赋值,const 声明后不能改变 // 放在 try 外面是为了让 finally 块也能访问到它 let profileId = null; try { // ════════════════════════════ // 【第一步】创建浏览器配置 // axios.post(url, 请求体数据, 配置选项) // 返回的是 axios 响应对象,实际数据在 .data 里 // timeout: 90000 = 90 秒(单位是毫秒,注意 Python 里是秒,JS 里是毫秒) // ════════════════════════════ console.log('正在创建浏览器配置...'); const createRes = await axios.post( `${BASE_URL}/browsers/profiles`, { group_id: GROUP_ID, browser_config: { canvas: 'noise', // Canvas 指纹噪声(让每个窗口指纹不同) webrtc: 'replace' // 防止 WebRTC 泄露真实 IP }, // 没有代理就把整个 proxy_config 这段删掉 proxy_config: { type: 'Http', // Http / Https / Socks5 / Directly(直连) host: '1.2.3.4', // 代理服务器 IP port: '8080', // 代理端口 username: 'user', // 无账号密码就填空字符串 '' password: 'pass' } }, { headers, timeout: 90000 } // 注意:JS 的 timeout 单位是毫秒(90000ms = 90秒) ); // createRes.data 是响应的 JSON 对象,createRes.data.data 是里面的 data 字段 profileId = createRes.data.data.id; console.log(`创建成功!配置 ID = ${profileId}`); // ════════════════════════════ // 【第二步】启动浏览器,获取 WebSocket 地址 // // ⚠️ 前提:幻影浏览器主程序必须已打开并已登录账号! // 幻影未运行时此步会报连接错误,请先打开幻影再运行脚本。 // ⚠️ BASE_URL 写法:本示例 BASE_URL='http://127.0.0.1:36108/api'(含/api) // 若你只存端口号(PORT='36108'),地址改为:`http://127.0.0.1:${PORT}/api/browsers/launch/${profileId}` // 关键:不要重复或遗漏 /api 路径。 // // Playwright Node.js 和 Python 一样,都需要 ws 地址(不是 http 端口) // ════════════════════════════ console.log('正在启动浏览器,请稍等...'); const launchRes = await axios.post( `${BASE_URL}/browsers/launch/${profileId}`, {}, // POST 请求体为空,启动参数通过 params 传(URL 查询字符串) { headers, timeout: 180000, // 180秒超时(首次启动会下载内核,较慢) params: { headless: 'false' } // false=显示浏览器窗口;true=无界面后台 } ); // launchRes.data.data.ws 就是 WebSocket 地址,格式:ws://127.0.0.1:端口/devtools/browser/... const wsUrl = launchRes.data.data.ws; console.log(`浏览器已启动!WS = ${wsUrl}`); // ════════════════════════════ // 【第三步】Playwright 通过 WebSocket 连接浏览器 // // connectOverCDP 等同于 Python 的 p.chromium.connect_over_cdp() // 注意:是 Connect(接管已有浏览器),不是 Launch(新建浏览器) // ════════════════════════════ const browser = await chromium.connectOverCDP(wsUrl); // 获取已有的标签页 // 注意:Node.js Playwright 的 contexts() 和 pages() 都要加括号(是方法) // Python 版的 contexts 和 pages 是属性(不加括号),两者语法略有不同 const context = browser.contexts()[0]; // 第一个浏览器上下文 const page = context.pages()[0]; // 第一个标签页 console.log('Playwright 连接成功!'); // ════════════════════════════ // 【第四步】执行自动化操作(和 Python 版 API 基本一致) // ════════════════════════════ // 跳转到百度,等待页面加载完成 await page.goto('https://www.baidu.com'); // JS 里 page.title() 也需要 await(因为是异步方法) console.log(`当前标题:${await page.title()}`); // 等待搜索框出现(最多 5 秒),防止页面还没渲染就去操作 await page.waitForSelector('#kw', { timeout: 5000 }); // fill:清空后填入内容(比 type 快) await page.fill('#kw', 'Playwright 教程'); // click:点击搜索按钮 await page.click('#su'); // 等待搜索结果页加载完成(网络空闲) await page.waitForLoadState('networkidle'); // 截图并保存 await page.screenshot({ path: 'screenshot.png' }); console.log('截图已保存到 screenshot.png!'); // ╔══════════════════════════════════════╗ // ║ 在这里替换成你自己的业务逻辑 ║ // ╚══════════════════════════════════════╝ // 断开 Playwright 连接(只是断连,不会关闭幻影窗口) await browser.close(); } catch (err) { // 捕获 try 块中的所有错误 // err.message 是错误描述,err.stack 包含出错位置(调试时可打印) console.error('发生错误:', err.message); } finally { // ════════════════════════════ // 【第五步】清理(无论 try 成功还是 catch 捕获到错误都会执行) // // if (profileId):判断是否成功创建过配置,避免 profileId 为 null 时报错 // .catch(() => {}):即使清理接口报错也不抛出,防止清理失败影响后续 // ════════════════════════════ if (profileId) { console.log('正在清理...'); // 第一步:停止浏览器进程(关闭 Chrome 窗口) await axios.delete(`${BASE_URL}/browsers/${profileId}`, { headers }).catch(() => {}); // 第二步:删除配置数据(释放磁盘空间) await axios.delete(`${BASE_URL}/browsers/profiles/${profileId}`, { headers }).catch(() => {}); console.log('清理完成!'); } } })();

③ 常用操作速查(Python)

python · 常用 API
# ── 导航 ── page.goto("https://example.com") # 跳转页面,默认等页面加载完 page.goto("https://example.com", wait_until="networkidle") # 等到网络空闲(加载更彻底) page.go_back() # 后退 page.reload() # 刷新页面 # ── 等待 / 查找 ── page.wait_for_selector(".btn", timeout=5000) # 等待元素出现(5秒超时) page.wait_for_load_state("networkidle") # 等待网络空闲 # ── 点击 / 输入 ── page.click(".submit-btn") # 点击(自动等元素可点击) page.fill("input[name='username']", "user123") # 清空并填入内容 page.type("input[name='q']", "逐字输入内容", delay=50) # 模拟逐字输入(delay 单位毫秒) page.press("input", "Enter") # 按键(Enter/Tab/Escape 等) # ── 获取内容 ── page.title() # 页面标题(注意是方法,要加括号) page.url # 当前 URL(是属性,不加括号) page.inner_text("h1") # 获取元素文本(纯文本,不含HTML标签) page.inner_html(".content") # 获取元素 HTML 代码 page.get_attribute("a.link", "href") # 获取属性值 # ── 执行 JavaScript ── page.evaluate("window.scrollTo(0, document.body.scrollHeight)") # 滚动到底部 result = page.evaluate("() => document.title") # 执行并获取返回值 # ── 截图 ── page.screenshot(path="page.png") # 截取可视区域 page.screenshot(path="full.png", full_page=True) # 截取整个页面(含滚动部分) # ── 拦截请求(在请求发出前修改或屏蔽)── # 例:给所有请求加一个自定义请求头 def handle_route(route): route.continue_(headers={**route.request.headers, "X-Custom-Key": "myvalue"}) page.route("**/*", handle_route) # ── 页面初始化前注入脚本(隐藏 webdriver 特征,反检测)── page.add_init_script(""" Object.defineProperty(navigator, 'webdriver', { get: () => undefined }); """) # ── 新标签页 ── new_page = context.new_page() # 在同一上下文中打开新标签页 new_page.goto("https://example.com") new_page.close() # 关闭这个新标签页
首页Puppeteer

🎪 Puppeteer 接入

📅 2025-03-05⏱ 约 10 分钟
💡
Puppeteer 和 Playwright 有什么区别?
· 两者功能非常相似,都是 Node.js 的浏览器自动化库
· Puppeteer 是 Google 官方出品,历史更久,API 更接近 Chrome 原生;Playwright 是微软出品,支持更多浏览器(Firefox/Safari)
· 接管幻影浏览器时,两者都用 WebSocket 地址,但 Puppeteer 用 puppeteer.connect,Playwright 用 chromium.connectOverCDP

准备工作

📌
① 需要安装 Node.js(版本 16+ 推荐),从 nodejs.org 下载安装
② 幻影浏览器已打开并已登录
③ 获取 API Key:点击「API管理」页面 → 生成 API Key → 复制
④ 确认 API 端口:「API & MCP → API端口配置」查看,默认 36108

安装依赖

bash · 终端执行(项目目录下)
npm install puppeteer axios

① 完整基础示例(含详细注释)

Puppeteer 使用 puppeteer.connect 而不是 puppeteer.launch。区别是:launch 是自己新建一个 Chrome 进程,connect 是通过 WebSocket 接管幻影已经启动的 Chrome 进程,两者的浏览器对象用法完全相同。

javascript · puppeteer 完整示例
const puppeteer = require('puppeteer'); const axios = require('axios'); // ══════════════════════════════════════════════════════════ // 【配置区】只需修改这里的三个变量 // ══════════════════════════════════════════════════════════ const BASE_URL = 'http://127.0.0.1:36108/api'; // 端口在「API & MCP → API端口配置」中查看 const API_KEY = 'YOUR_API_KEY'; // ← 替换成你的 API Key const GROUP_ID = 1; // ← 替换成你的分组 ID const headers = { 'Authorization': `Bearer ${API_KEY}`, 'Content-Type': 'application/json' }; // async 自调用函数:让代码可以从上到下顺序执行,用 await 等待每一步完成 (async () => { // 提前声明,let 可以先声明后赋值;放在 try 外面是为了 finally 块也能用 let profileId = null; // 浏览器配置 ID let browser = null; // Puppeteer 浏览器对象 try { // ════════════════════════════ // 【第一步】创建浏览器配置 // 调用幻影 API,告诉它用什么指纹和代理创建一个新浏览器配置 // ════════════════════════════ console.log('正在创建浏览器配置...'); const createRes = await axios.post( `${BASE_URL}/browsers/profiles`, { group_id: GROUP_ID, browser_config: { canvas: 'noise', // Canvas 指纹噪声(让每个窗口的指纹都唯一) webrtc: 'replace' // WebRTC 替换(防止泄露真实 IP) }, // 没有代理把整个 proxy_config 删掉即可 proxy_config: { type: 'Http', // 代理类型:Http / Https / Socks5 / Directly(直连) host: '1.2.3.4', // 代理服务器地址 port: '8080', // 代理端口 username: 'user', // 没有账号密码就填空字符串 '' password: 'pass' } }, { headers, timeout: 90000 } // JS 的 timeout 单位是毫秒(90000ms = 90秒) ); profileId = createRes.data.data.id; console.log(`创建成功!配置 ID = ${profileId}`); // ════════════════════════════ // 【第二步】启动浏览器,获取 WebSocket 地址 // // ⚠️ 前提:幻影浏览器主程序必须已打开并已登录账号! // 幻影未运行时此步会报连接错误,请先打开幻影再运行脚本。 // ⚠️ BASE_URL 写法:本示例 BASE_URL='http://127.0.0.1:36108/api'(含/api) // 若你只存端口号(PORT='36108'),地址改为:`http://127.0.0.1:${PORT}/api/browsers/launch/${profileId}` // 关键:不要重复或遗漏 /api 路径。 // // 启动后幻影会真正打开一个 Chrome 窗口 // 返回值里 data.ws 是 WebSocket 地址,Puppeteer 用这个连接 // ════════════════════════════ console.log('正在启动浏览器,请稍等...'); const launchRes = await axios.post( `${BASE_URL}/browsers/launch/${profileId}`, {}, // 请求体为空,启动参数放在 params(URL 查询字符串)里 { headers, timeout: 180000, // 180秒超时,首次启动会下载内核,耐心等待 params: { headless: 'false' } // false=显示窗口;true=无界面后台运行(省资源) } ); const wsEndpoint = launchRes.data.data.ws; // 取 ws 字段:完整的 WebSocket 地址 console.log(`浏览器已启动!WS = ${wsEndpoint}`); // ════════════════════════════ // 【第三步】Puppeteer 连接到已启动的浏览器 // // puppeteer.connect 而不是 puppeteer.launch! // connect → 接管已有浏览器(这里用幻影启动的) // launch → 自己新建一个 Chrome 进程(会绕过幻影的指纹保护) // // browserWSEndpoint:指定连接的 WebSocket 地址(就是上面拿到的 wsEndpoint) // defaultViewport: null → 不强制修改视口尺寸,保持幻影设置的窗口大小 // ════════════════════════════ browser = await puppeteer.connect({ browserWSEndpoint: wsEndpoint, defaultViewport: null // null = 不覆盖窗口尺寸,用幻影的原始设置 }); console.log('Puppeteer 连接成功!'); // browser.pages() 返回当前所有已打开标签页的数组 // 幻影启动后默认有一个空白标签页,用 pages[0] 获取它 // || await browser.newPage() 是兜底:万一没有标签页,就新建一个 const pages = await browser.pages(); const page = pages[0] || await browser.newPage(); // ════════════════════════════ // 【第四步】执行自动化操作 // ════════════════════════════ // goto:跳转页面,waitUntil: 'networkidle2' 表示等待网络请求数降到 2 个以下才继续 // 其他可用值:'load'(DOMContentLoaded)、'networkidle0'(完全空闲)、'domcontentloaded' await page.goto('https://www.baidu.com', { waitUntil: 'networkidle2' }); console.log(`当前标题:${await page.title()}`); // waitForSelector:等待元素出现(最多 5 秒),避免元素还未渲染就操作报错 await page.waitForSelector('#kw', { timeout: 5000 }); // page.type 逐字符输入(模拟人工打字),delay: 50 表示每个字符之间间隔 50ms // 如果不需要模拟人工,可用 page.evaluate 直接设置 value await page.type('#kw', 'Puppeteer 教程', { delay: 50 }); // 点击搜索按钮,然后等待页面跳转 await page.click('#su'); await page.waitForNavigation({ waitUntil: 'networkidle2' }); // 截图保存到当前目录 await page.screenshot({ path: 'screenshot.png' }); console.log('截图已保存到 screenshot.png!'); // ╔══════════════════════════════════════╗ // ║ 在这里替换成你自己的业务逻辑 ║ // ╚══════════════════════════════════════╝ // browser.disconnect() 只断开连接,不关闭幻影浏览器窗口 // 如果想保留窗口继续手动操作,把这行删掉 await browser.disconnect(); } catch (err) { // 捕获并打印错误信息(方便排查问题) console.error('发生错误:', err.message); } finally { // ════════════════════════════ // 【第五步】清理(无论成功还是出错都必须执行) // // 为什么用 finally? // try 里的代码出错后会跳过后面的代码, // 但 finally 里的代码无论如何都会执行,确保浏览器进程和配置被清理 // ════════════════════════════ if (browser) { // 如果 Puppeteer 还连着浏览器,先断开(防止资源泄漏) try { await browser.disconnect(); } catch (e) {} } if (profileId) { console.log('正在清理...'); // 停止浏览器进程(关闭 Chrome 窗口,释放 CPU/内存) await axios.delete(`${BASE_URL}/browsers/${profileId}`, { headers }) .catch(e => console.log('停止浏览器失败(可忽略):', e.message)); // 删除配置文件(释放磁盘空间;如果想保留配置下次复用,注释掉这行) await axios.delete(`${BASE_URL}/browsers/profiles/${profileId}`, { headers }) .catch(e => console.log('删除配置失败(可忽略):', e.message)); console.log('清理完成!'); } } })();

② 多实例并发(同时跑多个浏览器)

使用 Promise.all 让多个任务同时执行,比串行快很多。每个任务独立创建和管理自己的浏览器:

javascript · 并发执行
const puppeteer = require('puppeteer'); const axios = require('axios'); const BASE_URL = 'http://127.0.0.1:36108/api'; const headers = { Authorization: 'Bearer YOUR_API_KEY' }; // 单个任务函数:接受目标 URL 和任务编号 // async function 返回一个 Promise,可以用 Promise.all 并发执行 async function runTask(url, taskId) { let profileId = null; let browser = null; try { // 第一步:创建配置 const r = await axios.post(`${BASE_URL}/browsers/profiles`, { group_id: 1 }, // 所有任务用同一分组,但各自有独立指纹 { headers, timeout: 90000 } ); profileId = r.data.data.id; console.log(`[任务${taskId}] 配置 ID = ${profileId}`); // 第二步:启动浏览器 // ⚠️ 前提:幻影浏览器主程序必须已打开并已登录账号! // 幻影未运行时此步会报连接错误,请先打开幻影再运行脚本。 // ⚠️ BASE_URL 写法:本示例 BASE_URL='http://127.0.0.1:36108/api'(含/api) // 若你只存端口号(PORT='36108'),地址改为:`http://127.0.0.1:${PORT}/api/browsers/launch/${profileId}` // 关键:不要重复或遗漏 /api 路径。 const launch = await axios.post( `${BASE_URL}/browsers/launch/${profileId}`, {}, { headers, timeout: 180000 } ); const wsEndpoint = launch.data.data.ws; // 第三步:连接 browser = await puppeteer.connect({ browserWSEndpoint: wsEndpoint, defaultViewport: null }); const pages = await browser.pages(); const page = pages[0] || await browser.newPage(); // 第四步:执行任务 await page.goto(url, { waitUntil: 'networkidle2' }); console.log(`[任务${taskId}] 标题 = ${await page.title()}`); // ↓↓↓ 在这里写你的具体业务逻辑 ↓↓↓ await new Promise(resolve => setTimeout(resolve, 5000)); // 等待 5 秒 // ↑↑↑ 在这里写你的具体业务逻辑 ↑↑↑ await browser.disconnect(); console.log(`[任务${taskId}] 完成!`); } finally { // 保证无论成功失败都清理 if (browser) { try { await browser.disconnect(); } catch (e) {} } if (profileId) { await axios.delete(`${BASE_URL}/browsers/${profileId}`, { headers }).catch(() => {}); await axios.delete(`${BASE_URL}/browsers/profiles/${profileId}`, { headers }).catch(() => {}); } } } // 定义要并发访问的 URL 列表 const urls = [ 'https://www.baidu.com', 'https://www.163.com', 'https://www.qq.com' ]; // Promise.all:将所有任务同时启动,等待所有完成 // urls.map 为每个 URL 创建一个 Promise(即调用 runTask) // 相当于3个任务同时执行,而不是一个接一个等待 Promise.all(urls.map((url, i) => runTask(url, i + 1))) .then(() => console.log('✅ 所有任务执行完毕!')) .catch(err => console.error('发生错误:', err));

③ 常用操作速查

javascript · 常用 API
// ── 等待 / 查找 ── await page.waitForSelector('.btn', { timeout: 5000 }); // 等待元素出现(5秒超时) await page.waitForNavigation({ waitUntil: 'networkidle2' }); // 等待页面跳转完成 // ── 点击 / 输入 ── await page.click('.submit-btn'); // 点击 await page.type("input[name='q']", '输入内容', { delay: 50 }); // 模拟逐字输入 await page.keyboard.press('Enter'); // 按键 await page.focus('input'); // 聚焦元素 await page.select('select#country', 'CN'); // 选择下拉框选项 // ── 获取内容 ── const text = await page.$eval('h1', el => el.textContent); // 获取元素文本 const href = await page.$eval('a.link', el => el.href); // 获取链接 const all = await page.$$eval('.item', els => els.map(e => e.textContent)); // 获取所有 await page.title() // 页面标题 page.url() // 当前 URL // ── 执行 JavaScript ── await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight)); // 滚动到底部 const val = await page.evaluate(() => document.title); // 执行并获取返回值 // ── 截图 ── await page.screenshot({ path: 'page.png' }); // 截取可视区域 await page.screenshot({ path: 'full.png', fullPage: true }); // 截取整个页面(包含滚动) // ── 拦截请求(如屏蔽图片,提速加载)── await page.setRequestInterception(true); // 开启请求拦截 page.on('request', req => { // 如果是图片或视频,直接中止请求(不加载图片,页面加载更快) ['image', 'media', 'font'].includes(req.resourceType()) ? req.abort() : req.continue(); }); // ── 注入反检测脚本(在每个新页面加载前执行)── await page.evaluateOnNewDocument(() => { Object.defineProperty(navigator, 'webdriver', { get: () => undefined }); }); // ── 新标签页 ── const newPage = await browser.newPage(); // 新建标签页 await newPage.goto('https://example.com'); await newPage.close(); // 关闭标签页
首页JavaScript (fetch API)

🌐 JavaScript (fetch / axios) 接入

📅 2025-03-05⏱ 约 8 分钟
💡
纯 JS 和 Playwright/Puppeteer 有什么区别?
· 纯 JS(fetch/axios)只调用幻影的 HTTP API,负责"管理浏览器生命周期"(创建/启动/停止/删除)
· 如果只需要控制浏览器开关、不操控页面内容,用纯 JS 最轻量
· 如果需要自动化点击/输入/截图,把启动后返回的 ws 地址传给 Puppeteer/Playwright 即可组合使用
适合场景:油猴脚本、网页前端控制、Node.js 管理脚本、与其他自动化工具配合

准备工作

📌
① 幻影浏览器已打开并已登录
② 获取 API Key:点击「API管理」页面 → 生成 API Key → 复制
③ 确认 API 端口:「API & MCP → API端口配置」查看,默认 36108
④ 确认分组 ID:窗口管理页面,分组名称旁边的数字就是 group_id

① 浏览器 fetch(油猴脚本 / 页面中调用)

如果你想在网页中通过 JavaScript 控制幻影浏览器(比如写油猴脚本),使用 fetch 方式。
注意:需要幻影 API 允许跨域(CORS),如果报跨域错误请改用下方的 Node.js 方式。

javascript · browser fetch 完整示例
// ══════════════════════════════════════════════════════════ // 【配置区】只需修改这里 // ══════════════════════════════════════════════════════════ const BASE_URL = 'http://127.0.0.1:36108/api'; // 端口在「API & MCP → API端口配置」中查看 const API_KEY = 'YOUR_API_KEY'; // ← 替换成你的 API Key const GROUP_ID = 1; // ← 替换成你的分组 ID // 请求头:每次 fetch 请求都需要带上 Content-Type 和 Authorization const headers = { 'Content-Type': 'application/json', 'Authorization': `Bearer ${API_KEY}` }; // ── 封装辅助函数 apiRequest ── // 为什么要封装?每次请求都要写 fetch + res.json() + 错误判断,代码重复。 // 封装成一个函数后,调用时只需传方法、路径、请求体,其他的自动处理。 // // method: 'GET' / 'POST' / 'DELETE' // path: 路径字符串,如 '/browsers/profiles' // body: 请求体对象(GET/DELETE 不需要,传 null 即可) async function apiRequest(method, path, body = null) { const options = { method, headers }; // 如果有请求体(body),需要用 JSON.stringify 转成字符串才能发送 if (body) options.body = JSON.stringify(body); const res = await fetch(`${BASE_URL}${path}`, options); const data = await res.json(); // 把响应的 JSON 字符串解析成 JS 对象 // 幻影大部分接口返回 { success: true/false, data: {...} } // 如果 success 为 false,说明请求失败,抛出错误交给 catch 处理 if (!data.success) { throw new Error(`API 错误:${data.error || '未知错误'}`); } return data.data; // 只返回 data 字段,调用方直接拿到所需数据 } // ── 主流程 ── // (async () => { ... })() 是立即执行的异步函数,让代码可以顺序 await 执行 (async () => { let profileId = null; // 放在 try 外面,finally 块也能访问 try { // ════════════════════════════ // 【第一步】创建浏览器配置 // apiRequest 返回的是 data.data,即 { id: 123, name: "...", group_id: 1 } // ════════════════════════════ console.log('正在创建浏览器配置...'); const profile = await apiRequest('POST', '/browsers/profiles', { group_id: GROUP_ID, browser_config: { canvas: 'noise', // Canvas 指纹噪声(让每个浏览器的指纹都唯一) webrtc: 'replace' // 防止 WebRTC 泄露真实 IP }, // 没有代理把 proxy_config 这段删掉即可 proxy_config: { type: 'Http', // Http / Https / Socks5 / Directly host: '1.2.3.4', // 代理 IP port: '8080', // 代理端口 username: 'user', // 无账号密码填 '' password: 'pass' } }); profileId = profile.id; // 保存 profile_id,后续启动和清理需要用到 console.log(`创建成功!配置 ID = ${profileId}`); // ════════════════════════════ // 【第二步】启动浏览器 // // ⚠️ 前提:幻影浏览器主程序必须已打开并已登录账号! // 幻影未运行时此步会报连接错误,请先打开幻影再运行脚本。 // ⚠️ BASE_URL 写法:本示例 BASE_URL='http://127.0.0.1:36108/api'(含/api) // 若你只存端口号(PORT='36108'),地址改为:`http://127.0.0.1:${PORT}/api/browsers/launch/${profileId}?headless=false` // 关键:不要重复或遗漏 /api 路径。 // // 注意:fetch 不支持像 axios 那样的 params 参数, // 所以直接把参数拼到 URL 后面:?headless=false // headless=false → 显示浏览器窗口 // headless=true → 无界面后台运行(省资源) // ════════════════════════════ console.log('正在启动浏览器,请稍等...'); const browser = await apiRequest('POST', `/browsers/launch/${profileId}?headless=false`); console.log(`启动成功!`); console.log(` HTTP 调试地址:${browser.http}`); // DrissionPage / Selenium 用这个(提取端口号) console.log(` WS 调试地址:${browser.ws}`); // Playwright / Puppeteer 用这个(完整 ws 地址) // ════════════════════════════ // 【第三步】执行业务操作 // // 纯 fetch 方式只负责调 API,不能直接操控页面内容。 // 如果需要自动化(点击/输入/截图),把 browser.ws 传给 Puppeteer/Playwright: // const { chromium } = require('playwright'); // const pw = await chromium.connectOverCDP(browser.ws); // ════════════════════════════ // ↓↓↓ 在这里写你的业务逻辑 ↓↓↓ await new Promise(resolve => setTimeout(resolve, 3000)); // 模拟等待 3 秒 // ↑↑↑ 在这里写你的业务逻辑 ↑↑↑ // ════════════════════════════ // 【第四步】停止浏览器进程(关闭 Chrome 窗口) // ════════════════════════════ console.log('正在停止浏览器...'); await apiRequest('DELETE', `/browsers/${profileId}`); console.log('浏览器已停止'); } catch (err) { // 所有错误(网络错误、API 返回 success:false)都会在这里被捕获 console.error('发生错误:', err.message); } finally { // ════════════════════════════ // 【第五步】删除配置(释放磁盘空间) // finally 保证不管上面是否出错,都会尝试删除配置 // ════════════════════════════ if (profileId) { try { await apiRequest('DELETE', `/browsers/profiles/${profileId}`); console.log('配置已删除'); } catch (e) { console.log('删除配置失败(可忽略):', e.message); } } } })();

② Node.js axios(推荐方式,适合脚本和后端)

axios.create 创建一个配置好的实例,以后所有请求都通过它发送,不用每次都重复写 BaseURL 和请求头:

bash · 安装(终端执行一次)
npm install axios
javascript · node.js axios 完整示例
const axios = require('axios'); // ── 创建 axios 实例(相当于一个预配置好的 "HTTP 客户端")── // // baseURL:所有请求都以此为前缀,发请求时只需写 '/browsers/profiles' 而不是完整 URL // headers:每次请求自动带上这些请求头(包含 API Key 认证),省去重复设置 // timeout:超时时间(毫秒),这里设 3 分钟,够浏览器启动用 // // 好处:一处配置,所有请求复用,修改也方便 const api = axios.create({ baseURL: 'http://127.0.0.1:36108/api', // ← 端口在「API & MCP → API端口配置」中查看 headers: { 'Authorization': 'Bearer YOUR_API_KEY', // ← 替换成你的 API Key 'Content-Type': 'application/json' }, timeout: 180000 // 180000ms = 180秒(启动浏览器首次可能需要下载内核) }); (async () => { let profileId = null; // 放 try 外面,finally 块也能用 try { // ── (可选)查询分组列表 ── // 如果你不知道自己的 group_id 是多少,可以先查一下所有分组 // 注释掉这段后直接填已知的 group_id 即可 const groupsRes = await api.get('/groups/list'); console.log('所有分组:', groupsRes.data.data); // 从控制台输出中找你的分组,把对应的 id 填到下面的 group_id: 1 处 // ════════════════════════════ // 【第一步】创建浏览器配置 // // 解构赋值解释: // const { data: { data: profile } } = await api.post(...) // 等同于: // const res = await api.post(...) // const profile = res.data.data // // axios 的响应结构是:res.data → 接口返回的 JSON // 幻影接口返回格式:{ success: true, data: { id: 123, ... } } // 所以 res.data.data 才是真正的配置对象 // ════════════════════════════ console.log('正在创建浏览器配置...'); const { data: { data: profile } } = await api.post('/browsers/profiles', { group_id: 1, // ← 替换成你的分组 ID browser_config: { canvas: 'noise', // Canvas 指纹噪声(让每个窗口指纹唯一,防止被识别) webrtc: 'replace' // 防止 WebRTC 泄露真实 IP } // 没有代理就不写 proxy_config;需要代理参考上方 fetch 示例添加 }); profileId = profile.id; console.log(`创建成功!配置 ID = ${profileId}`); // ════════════════════════════ // 【第二步】启动浏览器 // // ⚠️ 前提:幻影浏览器主程序必须已打开并已登录账号! // 幻影未运行时此步会报连接错误,请先打开幻影再运行脚本。 // ⚠️ BASE_URL 写法:本示例 BASE_URL='http://127.0.0.1:36108/api'(含/api) // 若你只存端口号(PORT='36108'),地址改为:`http://127.0.0.1:${PORT}/api/browsers/launch/${profileId}` // 关键:不要重复或遗漏 /api 路径。 // // axios.post(url, 请求体, 配置项) // 第二个参数 {} 是空请求体(启动接口不需要请求体内容) // params 里的参数会自动拼接到 URL 后面变成查询字符串 // 等同于请求:POST /browsers/launch/123?headless=false // ════════════════════════════ console.log('正在启动浏览器,请稍等...'); const { data: { data: browser } } = await api.post( `/browsers/launch/${profileId}`, {}, // 空请求体 { params: { headless: 'false' } } // false=显示窗口,true=无界面后台 ); console.log(`启动成功!`); console.log(` HTTP 调试地址:${browser.http}`); // DrissionPage / Selenium 用,从中提取端口号 console.log(` WS 调试地址:${browser.ws}`); // Playwright / Puppeteer 直接用完整 ws 地址 // ════════════════════════════ // 【第三步】执行业务操作 // // 纯 axios 方式负责"管理浏览器生命周期" // 如需操控页面(点击/输入/截图),把 browser.ws 传给 Puppeteer/Playwright: // const puppeteer = require('puppeteer'); // const pw = await puppeteer.connect({ browserWSEndpoint: browser.ws, defaultViewport: null }); // ════════════════════════════ console.log('正在执行业务操作...'); // ╔══════════════════════════════════════╗ // ║ 在这里替换成你自己的业务逻辑 ║ // ║ 例如:将 browser.ws 传给 Puppeteer ║ // ╚══════════════════════════════════════╝ // ↓↓↓ 在这里写你的具体业务逻辑 ↓↓↓ await new Promise(resolve => setTimeout(resolve, 3000)); // 模拟等待 3 秒 // ↑↑↑ 在这里写你的具体业务逻辑 ↑↑↑ console.log('业务操作完成!'); } catch (err) { // axios 的错误对象有两种情况: // err.response → 服务器响应了但状态码表示失败(如 401、500) // err.message → 没有响应(超时、网络断开等) if (err.response) { console.error(`请求失败:HTTP ${err.response.status}`, err.response.data); } else { console.error('请求异常:', err.message); } } finally { // ════════════════════════════ // 【第四步】清理(无论成功失败都执行) // // .catch(() => {}) 的作用:如果清理请求失败,不抛出错误 // 避免因清理失败而覆盖了原本的错误信息 // ════════════════════════════ if (profileId) { console.log('正在清理...'); // 第一步:停止浏览器进程(关闭 Chrome 窗口,释放 CPU 和内存) await api.delete(`/browsers/${profileId}`).catch(() => {}); // 第二步:删除配置数据(彻底清除,释放磁盘空间) await api.delete(`/browsers/profiles/${profileId}`).catch(() => {}); console.log('清理完成!'); } } })();

③ API 接口速查表

所有常用接口一览,方便查找:

方法路径说明关键参数
GET/groups/list获取所有分组
POST/browsers/profiles创建浏览器配置group_id(必填)
GET/browsers/profiles获取配置列表page, pageSize
POST/browsers/launch/:id启动浏览器?headless=false
DEL/browsers/:id停止浏览器进程路径中的 :id
DEL/browsers/profiles/:id删除配置数据路径中的 :id
javascript · 接口速查(一行一个)
// 假设 api 是 axios.create({...}) 创建的实例,id 是 profile_id // 1. 获取分组列表 → 找到你的 group_id await api.get('/groups/list'); // 2. 创建浏览器配置(最少只需 group_id) await api.post('/browsers/profiles', { group_id: 1 }); // 3. 创建配置(带代理) await api.post('/browsers/profiles', { group_id: 1, proxy_config: { type: 'Http', host: '1.2.3.4', port: '8080', username: '', password: '' } }); // 4. 启动浏览器(有界面)→ 返回 ws 和 http 调试地址 await api.post(`/browsers/launch/${id}`, {}, { params: { headless: 'false' } }); // 5. 启动浏览器(无界面后台运行) await api.post(`/browsers/launch/${id}`, {}, { params: { headless: 'true' } }); // 6. 停止浏览器进程(关闭 Chrome 窗口) await api.delete(`/browsers/${id}`); // 7. 删除浏览器配置(彻底移除,释放磁盘空间) await api.delete(`/browsers/profiles/${id}`); // 8. 获取配置列表(分页) await api.get('/browsers/profiles', { params: { page: 1, pageSize: 20 } });
启动后返回的 ws 字段可直接传给 Puppeteer 的 browserWSEndpoint 或 Playwright 的 connectOverCDP,实现"JS 调 API 启动 + 自动化框架操控"的组合用法。
首页代理 IP 说明

🌐 代理 IP 说明

📅 2025-03-05

支持的代理类型

类型参数值适用场景
直连Directly本地测试、无需代理
HTTPHttp普通 HTTP 代理
HTTPSHttps加密 HTTP 代理
SOCKS5Socks5高隐私、支持 UDP
平台代理platform幻影平台内置代理服务

API 配置示例

json · SOCKS5
{ "group_id": 1, "proxy_config": { "type": "Socks5", "host": "192.168.1.100", "port": "1080", "username": "user", "password": "pass" } }
首页平台代理使用

🏪 平台代理使用

📅 2025-03-05

使用平台代理时,在 proxy_config 中设置 proxyMode: "platform" 并传入 platformProxyId

json · 平台代理
{ "group_id": 1, "proxy_config": { "proxyMode": "platform", "platformProxyId": "proxy_123456" } }
首页常见问题

❓ 常见问题

📅 2025-03-05

API 相关

API 请求返回 401,怎么办?

请检查:① 幻影浏览器是否处于登录状态;② Authorization: Bearer YOUR_KEY 格式是否正确;③ API Key 是否过期,可在 API 管理页重新生成。

为什么只需要提供 group_id?

系统为所有参数设置了合理默认值,并自动生成指纹噪声值、UA、设备信息等。只需 group_id 即可快速创建完整的浏览器配置。

API 服务默认监听哪个端口?

API 端口可自定义,在 幻影浏览器 → API & MCP → API端口配置 中查看「自动化API端口」的实际值。

自动化相关

Selenium 连接时报 session not created?

确保浏览器已通过 POST /api/browsers/launch/:id 成功启动,然后使用返回的 data.http 提取端口号(split(":"))连接。不要使用新建 ChromeDriver 实例的方式。

移动端平台会自动打开开发者工具吗?

是的,Android 和 iPhone 平台会自动添加 --auto-open-devtools-for-tabs 参数,方便调试移动端布局。

创建或启动浏览器超时(ReadTimeout)怎么办?
  • 创建配置超时(90秒):代理 IP 连通性差或 API 服务器繁忙。检查代理是否可用、网络是否正常。
  • 启动超时(180秒):第一次启动需加载内核,较慢;或代理 IP 连接不稳定,指纹服务无响应。更换代理 IP 重试。
  • 如大量超时,检查端口号是否正确(API & MCP → API端口配置)、幻影浏览器是否已登录。
为什么删除配置 API 的响应格式和其他接口不同?

DELETE /api/browsers/profiles/{id} 返回 {"code": 200, "msg": "..."},而创建/启动等接口返回 {"success": true, "data": {...}}

正确判断方式:

python
result = resp.json() if result.get('code') == 200: print("删除成功") # HTTP 404 = 配置不存在(已删除),也视为成功

指纹相关

如何验证指纹伪装是否生效?

启动浏览器后访问 bot.sannysoft.combrowserleaks.com 进行检测。

WebGL 配置会根据平台自动调整吗?
  • Windows/Android:Google Inc. (Intel/AMD/NVIDIA)
  • Mac:Apple Inc. (Intel)
  • iPhone:Apple GPU
浏览器版本是 131 还是 132?

支持 Chrome 131 和 132,系统会随机选择一个版本号并添加随机子版本号(0-200)。

首页更新日志

📝 更新日志

📅 最后更新 2025-03-05

v1.0.0 · 2025-01-08

  • 支持多平台:Windows 10/11、Mac、Android、iPhone
  • 完整指纹自动生成系统(30+ 维度)
  • 移动端手机型号配置支持
  • 移动端自动开启开发者工具
  • RESTful API 支持
  • Bearer Token 双重认证机制
  • 代理 IP:直连 / HTTP / HTTPS / SOCKS5 / 平台代理