发生于一次交流会发现行业内都基于WebDriver BiDi协议做自动化控制了,于是想对于微软的Edge浏览器做一个自己的框架;(预期是做这个的人少,自用被自动化检测的概率低)基于这个目的,对浏览器自动化框架相关的知识做了一个了解和梳理;
问:浏览器自动化框架的本质是什么?
答: 本质上,浏览器自动化框架是对浏览器内核 (如 Chromium、Gecko、WebKit)的远程控制 。内核就像提供底层能力的"基类",它暴露了调试协议(如 WebDriver、CDP、WebDriver BiDi)。自动化框架通过 WebSocket 或 HTTP 等通道实例化一个远程会话 ,然后调用内核支持的各种方法(导航、执行脚本、模拟输入、拦截网络等)。这些调用本质上是在操作内核内部的对象状态(DOM 树、JavaScript 堆、事件循环等)。框架代码并不是直接调用内核的 C++ 方法,而是将用户的高层操作(如 page.goto(url))翻译成符合协议的 JSON 命令,通过网络发送给内核,内核执行后返回结果。因此,逻辑上可以视为"远程调用内核对象的方法"。
问:目前主流的浏览器自动化协议和框架有哪些?
答: 市面上存在三大类协议及对应的成熟框架:
1. 传统 WebDriver 协议(HTTP 同步)
-
协议特点:基于 HTTP 请求-响应,单向控制,浏览器无法主动上报事件。
-
代表框架:
-
Selenium:跨语言、跨浏览器,生态最庞大。
-
WebdriverIO(Node.js):封装优雅,支持多种服务端。
-
-
适用场景:大规模跨浏览器测试、CI/CD 集成。
2. Chrome DevTools Protocol(CDP)
-
协议特点:基于 WebSocket 双向通信,由 Chrome 发明,功能极其强大(网络、性能、调试等),但非 W3C 标准。
-
代表框架:
-
Puppeteer(Node.js,Google 官方):CDP 鼻祖,性能极致。
-
Pydoll(Python):异步直连 CDP,内置拟人化行为。
-
DrissionPage(Python):国产框架,语法简洁,无 WebDriver 依赖。
-
nodriver(Python):专为绕过风控设计。
-
-
适用场景:爬虫、UI 自动化、需要深度控制浏览器的场景。
3. WebDriver BiDi 协议(W3C 新一代标准)
-
协议特点:基于 WebSocket + JSON‑RPC 2.0,双向通信,融合了 WebDriver 的标准化与 CDP 的实时事件能力。
-
代表框架:
-
Playwright (微软官方):支持所有主流浏览器,API 现代,内置等待、网络拦截、移动端模拟。https://playwright.nodejs.cn/
-
Selenium 4+:实验性支持 BiDi。
-
WebdriverIO:同时支持 WebDriver、CDP 和 BiDi。
-
-
适用场景:未来浏览器自动化的统一方向,兼顾标准化与高性能。
另外还有一些非典型但特色鲜明的框架,如:
- ruyiPage (Python):专为 Firefox 设计,基于 WebDriver BiDi,强调拟人化、高隐蔽性、风控对抗。https://github.com/LoseNine/ruyipage
问:有哪些核心名词需要理解?
答:
| 名词 | 解释 |
|---|---|
| 浏览器内核 | 负责解析 HTML/CSS、执行 JavaScript、渲染页面的底层引擎,如 Chromium(Chrome/Edge)、Gecko(Firefox)、WebKit(Safari)。 |
| 调试协议 | 内核暴露的远程控制接口,允许外部程序发送命令、接收事件。常见的有 CDP、WebDriver BiDi。 |
| WebSocket | 全双工通信协议,浏览器自动化中用于实现双向实时消息交换。 |
| JSON‑RPC 2.0 | 一种轻量级远程调用协议,WebDriver BiDi 采用其消息格式(id、method、params、result、error)。 |
| 远程对象引用 | 执行 script.evaluate 返回的 DOM 元素或 JavaScript 对象的句柄(如 handle 或 sharedId),后续操作通过引用指向该对象。 |
| 动作链 | 一系列连续的输入事件(鼠标移动、点击、键盘按键),可模拟复杂的人机交互。 |
| 网络拦截 | 在请求发送前或响应返回后修改其内容,实现 Mock 数据、修改请求头、模拟失败等。 |
| 用户上下文 | 浏览器中隔离的会话环境,不同上下文拥有独立的 Cookie、Storage、缓存,用于多账号并行操作。 |
问:浏览器自动化的技术线路有哪些?如何选择?
答: 主要有三条线路,可根据需求选择:
| 线路 | 协议 | 优点 | 缺点 | 推荐场景 |
|---|---|---|---|---|
| 官方 WebDriver 线路 | WebDriver(HTTP) | 标准化、跨浏览器、资料丰富 | 单向阻塞、无法监听事件 | 传统 Web 自动化测试 |
| CDP 线路 | CDP(WebSocket) | 速度快、功能强、隐匿性好 | 非标准、主要面向 Chromium | 爬虫、高风控对抗、性能分析 |
| WebDriver BiDi 线路 | WebDriver BiDi | 标准化 + 双向实时、未来主流 | 部分模块仍在完善 | 新项目、希望统一技术栈 |
选择建议:
-
如果追求稳定成熟、团队熟悉 → Selenium(WebDriver)
-
如果追求极致性能、绕过风控 → Puppeteer(Node)或 Pydoll / DrissionPage(Python)
-
如果希望拥抱未来、兼顾标准化 → Playwright(首选)
问:一个完整的自动化框架由哪些组件构成?
答: 无论采用何种协议,框架通常包含以下核心组件:
- 浏览器启动器(Launcher)
- 负责以特定参数启动浏览器进程(如指定用户数据目录、开启调试端口、设置代理)。
- 连接管理器(Session)
- 建立与浏览器的 WebSocket/HTTP 连接,管理会话 ID。
- 协议编码器/解码器
- 将用户 API 调用转换为符合协议的 JSON 消息,并解析返回的响应和事件。
- 命令调度器
- 发送命令、等待响应、处理超时和重试。
- 事件分发器
- 订阅感兴趣的事件(网络、日志、页面生命周期),并分发给注册的回调函数。
- 页面/元素抽象层(Page & Element)
- 封装
goto、click、evaluate等常用操作,隐藏协议细节。
- 封装
- 等待机制
- 智能等待元素出现、导航完成、网络空闲等,替代硬编码
sleep。
- 智能等待元素出现、导航完成、网络空闲等,替代硬编码
- 资源管理
- 及时释放远程对象引用,关闭浏览器进程,避免内存泄漏。
问:如何设计一个基于 WebDriver BiDi 的自研框架?(上层控制与底层依赖)
答: 以下是一套完整的设计方案,涵盖底层依赖、协议实现和上层 API。
底层依赖
-
语言:Python ≥ 3.8(异步优先,也可选 Node.js/Java/Rust)
-
核心库:
-
websockets:建立 WebSocket 连接
-
aiohttp 或 requests:获取浏览器的 WebSocket URL(通过 HTTP 端点)
-
asyncio:管理异步命令与事件循环
-
-
浏览器:Edge / Chrome ≥ 96,或 Firefox ≥ 120
核心实现步骤
1. 启动浏览器并获取 WebSocket URL
以 Edge 为例:
bash
msedge.exe --remote-debugging-port=9222 --user-data-dir=./profile
然后通过 HTTP 获取 webSocketDebuggerUrl:
python
import requests
resp = requests.get("http://localhost:9222/json/version")
ws_url = resp.json()["webSocketDebuggerUrl"]
2. 实现 BiDi 会话类
python
import asyncio, json, websockets
class BiDiSession:
def __init__(self, ws_url):
self.ws_url = ws_url
self._id = 0
self._pending = {}
self._handlers = {}
async def start(self):
self.websocket = await websockets.connect(self.ws_url)
asyncio.create_task(self._recv_loop())
async def _recv_loop(self):
async for msg in self.websocket:
data = json.loads(msg)
if "id" in data: # 响应
future = self._pending.pop(data["id"])
future.set_result(data.get("result"))
elif "method" in data: # 事件
for cb in self._handlers.get(data["method"], []):
await cb(data["params"])
async def send(self, method, params):
self._id += 1
msg = {"id": self._id, "method": method, "params": params}
future = asyncio.Future()
self._pending[self._id] = future
await self.websocket.send(json.dumps(msg))
return await future
def on(self, event, callback):
self._handlers.setdefault(event, []).append(callback)
3. 封装 Page 类(上层控制)
python
class Page:
def __init__(self, session, context_id):
self.session = session
self.context_id = context_id
async def goto(self, url):
await self.session.send("browsingContext.navigate", {
"context": self.context_id,
"url": url
})
async def evaluate(self, js):
res = await self.session.send("script.evaluate", {
"expression": js,
"target": {"context": self.context_id},
"awaitPromise": True
})
return res["result"]["value"]
async def click(self, selector):
# 先获取元素引用
ref = await self.evaluate(f"document.querySelector('{selector}')")
if not ref:
raise Exception("Element not found")
# 调用原生 click
await self.session.send("script.callFunction", {
"functionDeclaration": "(elem) => elem.click()",
"target": {"context": self.context_id},
"arguments": [{"handle": ref["handle"]}],
"awaitPromise": True
})
4. 支持网络拦截(高风控场景)
python
# 订阅网络事件
await session.send("session.subscribe", {
"events": ["network.beforeRequestSent"]
})
# 添加拦截规则
await session.send("network.addIntercept", {
"phases": ["beforeRequestSent"],
"urlPatterns": [{"type": "pattern", "pattern": "https://api.example.com/*"}]
})
# 在事件回调中修改请求或返回 Mock 数据
session.on("network.beforeRequestSent", lambda params: mock_response(params))
上层 API 设计目标
-
自然语义 :page.goto(url), element.click(), page.on("network.response", callback)
-
异步优先:充分利用 WebSocket 双向通信,避免阻塞
-
自动等待 :内置 wait_for_selector、wait_for_navigation 等
-
链式动作 :actions.move_to(element).click().perform()
问:自研框架与现有成熟框架的生态对比如何?
答:
| 维度 | 自研框架(基于 WebDriver BiDi) | Playwright | Selenium |
|---|---|---|---|
| 标准化程度 | 高(W3C 标准) | 高(BiDi + CDP) | 高(传统 WebDriver) |
| 开发工作量 | 大(需实现协议细节) | 零(直接使用) | 零 |
| 可定制性 | 完全可控 | 中等(黑盒) | 中等 |
| 社区支持 | 无 | 强大(微软维护) | 极强大 |
| 学习价值 | 极高(深入理解原理) | 低 | 低 |
| 生产就绪 | 需大量测试 | 是 | 是 |
结论:
-
如果目标是学习浏览器自动化底层原理、打造完全自主的轻量框架,自研 WebDriver BiDi 是非常值得的投入。
-
如果目标是快速解决业务问题(如爬虫、测试),直接使用 Playwright 或 Pydoll 是更高效的选择。
问:实践建议 ------ 从零开始实现一个最小化 BiDi 框架需要多少代码?
答: 一个能实现"启动 Edge、打开百度、输入关键词、点击搜索"的最小框架大约 200 行 Python 代码。核心步骤:
-
启动 Edge 调试端口(可用 subprocess)
-
获取 WebSocket URL
-
实现 BiDiSession 类(约 80 行)
-
实现 Page 类(goto, evaluate, query_selector, click,约 60 行)
-
实现 Element 类(约 30 行)
-
编写示例脚本(约 30 行)
完成后,你就拥有了一个可扩展的基础,可以逐步增加网络拦截、动作链、多标签管理等功能。
附:WebDriver BiDi 协议前世今生
1. 背景困境(2015‑2020)
-
WebDriver(经典):W3C 标准,跨浏览器,但单向 HTTP,无法接收事件。
-
CDP(Chrome DevTools Protocol):功能强大、双向实时,但仅限 Chromium,非标准。
2. 立项启动(2020)
-
**WebDriver BiDi:**2020年,W3C 浏览器测试与工具工作组正式立项,
-
目标:WebDriver 的标准化 + CDP 的双向实时能力。
3. 厂商跟进与框架集成(2021‑2024)
-
Firefox 陆续实现核心 BiDi 模块,Chrome/Edge 通过
chromium-bidi翻译层支持 BiDi。 -
Selenium 4 和 Puppeteer 提供实验性支持,Playwright 原生支持 BiDi(微软主导)。
-
至此,主流浏览器与自动化框架初步完成 BiDi 集成,为后续标准化奠定基础。
4. 标准化与生态成熟(2024‑2026)
-
2024年11月,W3C 发布 WebDriver BiDi 首个公开工作草案。
-
2025年,Firefox 宣布逐步弃用 CDP 转向 BiDi,Puppeteer 23+ 稳定支持 Firefox + BiDi。
-
2026年,Firefox 141 正式移除 CDP,BiDi 成为事实上的新一代标准。
-
目前 Chrome、Edge、Firefox 全支持,Selenium、Puppeteer、Playwright、Cypress 等主流框架均已集成,未来将继续完善设备模拟、响应体获取等模块,有望统一浏览器自动化生态。