内部架构
理解设计,然后有意识地打破规则。
大多数文档向你展示框架做什么。本节揭示 Pydoll 如何以及为什么以这种方式构建:塑造每一行代码的设计模式、架构决策和权衡。
为什么架构很重要
你可以在不理解其内部架构的情况下有效地使用 Pydoll。但当你需要:
- 调试跨多个组件的复杂问题
- 优化大规模自动化中的性能瓶颈
- 扩展 Pydoll 的自定义功能
- 贡献对代码库的改进
- 构建针对不同用例的类似工具
...架构知识变得不可或缺。
架构即语言
"建筑是凝固的音乐。" - 约翰·沃尔夫冈·冯·歌德
良好的架构不仅仅是让代码工作,更是让代码可理解、可维护和可扩展。理解 Pydoll 的架构将教会你适用于每个项目的模式。
六大架构域
Pydoll 的架构组织为六个内聚域,每个域都有明确的职责和接口:
1. 浏览器域
协调者:管理进程、上下文和全局状态。
浏览器域位于层次结构的顶部,协调:
- 进程管理:启动/终止浏览器可执行文件
- 浏览器上下文:隔离环境(如隐私窗口)
- 标签页注册表:Tab 实例的单例模式
- 代理认证:通过 Fetch 域自动认证
- 全局操作:下载、权限、窗口管理
关键架构模式:
- 抽象基类,适用于 Chrome/Edge/其他 Chromium 浏览器
- 管理器模式(ProcessManager、ProxyManager、TempDirManager)
- 单例注册表用于 Tab 实例(防止重复)
- 上下文管理器协议用于自动清理
关键洞察:浏览器不直接操作页面,它协调低级组件。这种关注点分离使多浏览器支持和并发标签操作成为可能。
2. 标签页域
主力军:执行命令、管理状态、协调自动化。
标签页域是 Pydoll 的主要接口,处理:
- 导航:具有可配置等待状态的页面加载
- 元素查找:委托给 FindElementsMixin
- JavaScript 执行:页面和元素上下文
- 事件协调:特定于标签页的事件监听器
- 网络监控:请求/响应捕获和分析
- IFrame 处理:嵌套上下文管理
关键架构模式:
- 外观模式:简化的 CDP 复杂操作接口
- 混入组合:FindElementsMixin 用于元素定位
- 每标签页 WebSocket:并行的独立连接
- 状态标志:跟踪启用的域(network_events_enabled 等)
- 延迟初始化:首次访问时创建请求对象
关键洞察:每个 Tab 拥有自己的 ConnectionHandler,实现跨标签的真正并行操作,无需竞争或状态泄漏。
3. WebElement 域
交互器:连接 Python 代码和 DOM 元素。
WebElement 域表示单个 DOM 元素,提供:
- 交互方法:点击、输入、滚动、选择
- 属性访问:文本、HTML、边界、属性
- 状态查询:可见性、启用状态、值
- 截图:特定于元素的图像捕获
- 子元素查找:相对元素定位(也通过 FindElementsMixin)
关键架构模式:
- 代理模式:表示远程浏览器元素的 Python 对象
- 对象 ID 抽象:CDP 的 objectId 隐藏在 Python API 后面
- 混合属性:同步(属性)vs 异步(动态状态)
- 命令模式:交互方法包装 CDP 命令
- 回退策略:多种方法提高鲁棒性
关键洞察:WebElement 维护缓存的属性(从创建时)和动态状态(按需获取),平衡性能与新鲜度。
4. FindElements 混入
定位器:将选择器转换为 DOM 查询。
FindElementsMixin 通过组合而非继承为 Tab 和 WebElement 提供元素查找功能:
- 基于属性的查找:
find(id='submit', class_name='btn') - 基于表达式的查询:
query('div.container > p') - 策略解析:针对单个或多个属性的最优选择器
- 等待机制:具有可配置超时的轮询
- 上下文检测:文档 vs 元素相对搜索
关键架构模式: - 混入模式:无需继承层次结构的共享功能 - 策略模式:基于输入的不同选择器策略 - 模板方法:通用流程,特定于策略的实现 - 工厂函数:延迟导入以避免循环依赖 - 重载模式:类型安全的返回类型(WebElement vs list)
关键洞察:混入使用鸭子类型(hasattr(self, '_object_id'))来检测 Tab vs WebElement,实现代码重用而不紧密耦合。
5. 事件架构
调度器:将浏览器事件路由到 Python 回调。
事件架构通过以下方式实现响应式自动化:
- 事件注册:
on()方法订阅 CDP 事件 - 回调调度:异步执行不阻塞
- 域管理:显式启用/禁用以提高性能
- 临时回调:首次调用后自动删除
- 多级作用域:浏览器范围 vs 标签页特定事件
关键架构模式:
- 观察者模式:订阅/通知事件驱动代码
- 注册表模式:事件名称 → 回调列表映射
- 包装器模式:自动包装同步回调以进行异步执行
- 清理协议:标签页关闭时自动删除回调
- 作用域隔离:每个标签页独立的事件上下文
关键洞察:事件是推送式的(浏览器通知 Python),而非轮询式,实现低延迟响应式自动化,无需忙等待。
6. 浏览器请求架构
混合体:具有浏览器会话状态的 HTTP 请求。
浏览器请求系统连接 HTTP 和浏览器自动化:
- 会话连续性:自动包含 Cookie 和认证
- 双重数据源:JavaScript Fetch API + CDP 网络事件
- 完整元数据:标头、Cookie、时间(并非所有通过 JavaScript 可用)
- 类
requestsAPI:具有浏览器能力的熟悉接口
关键架构模式:
- 混合执行:JavaScript 获取主体,CDP 获取元数据
- 临时事件注册:启用/捕获/禁用模式
- 延迟属性初始化:首次使用时创建请求对象
- 适配器模式:与浏览器 fetch 兼容的 Requests 接口
关键洞察:浏览器请求结合两个信息源(JavaScript 和 CDP 事件)。JavaScript 提供响应主体,CDP 提供 JavaScript 安全策略隐藏的标头和 Cookie。
架构原则
这六个域遵循一致的原则:
1. 关注点分离
每个域都有一个单一、明确定义的职责:
- Browser → 进程/上下文管理
- Tab → 命令执行和状态
- WebElement → 元素交互
- FindElements → 元素定位
- Events → 响应式调度
- Requests → 浏览器上下文中的 HTTP
优势:一个域的更改很少需要更改其他域。
2. 组合优于继承
Pydoll 使用以下方式而非深层继承层次结构:
- 混入(Tab 和 WebElement 共享 FindElementsMixin)
- 管理器(ProcessManager、ProxyManager、TempDirManager)
- 依赖注入(ConnectionHandler 传递给组件)
优势:灵活的组件重用而不紧密耦合。
3. 默认异步
所有 I/O 操作都是 async def 并且必须 await:
- WebSocket 通信
- CDP 命令执行
- 事件回调调度
- 网络请求
优势:实现多个标签页的真正并发、并行操作和非阻塞 I/O。
4. 类型安全
每个公共 API 都有类型注解:
- 函数参数和返回类型
- 作为
TypedDict的 CDP 响应 - 回调参数的事件类型
- 多态方法的重载
优势:IDE 自动完成、静态类型检查、自文档化代码。
5. 资源管理
上下文管理器确保清理:
async with Browser()→ 退出时关闭浏览器async with tab.expect_file_chooser()→ 禁用拦截器async with tab.expect_download()→ 清理临时文件
优势:自动资源清理,即使在异常情况下也能防止泄漏。
组件交互
理解域如何交互是关键:
graph TB
User[你的 Python 代码]
User --> Browser[浏览器域]
User --> Tab[标签页域]
User --> Element[WebElement 域]
Browser --> ProcessMgr[进程管理器]
Browser --> ContextMgr[上下文管理器]
Browser --> TabRegistry[标签页注册表]
Tab --> ConnHandler[连接处理器]
Tab --> FindMixin[FindElements 混入]
Tab --> EventSystem[事件系统]
Tab --> RequestSystem[请求系统]
Element --> ConnHandler2[连接处理器]
Element --> FindMixin2[FindElements 混入]
ConnHandler --> WebSocket[WebSocket 到 CDP]
ConnHandler2 --> WebSocket
EventSystem --> ConnHandler
RequestSystem --> ConnHandler
RequestSystem --> EventSystem
WebSocket --> Chrome[Chrome 浏览器]
关键交互:
- Browser 创建 Tabs → Tabs 存储在注册表中
- Tab 和 WebElement 都使用 FindElementsMixin → 共享元素定位
- 每个 Tab 拥有一个 ConnectionHandler → 独立的 WebSocket 连接
- 请求系统使用事件系统 → 网络事件捕获元数据
- 所有组件都使用 ConnectionHandler → 集中式 CDP 通信
先决条件
要充分受益于本节:
- 核心基础 - 理解 CDP、异步和类型
- Python 设计模式 - 熟悉常见模式
- OOP 概念 - 类、继承、组合、接口
- 异步 Python - 熟悉
async def和await
如果你还没有阅读基础知识,请先从那里开始。架构建立在这些概念之上。
超越架构
掌握内部架构后,你将准备好:
- 贡献代码:了解新功能的适配位置
- 性能优化:识别瓶颈和低效率
- 自定义扩展:基于 Pydoll 的模式构建
- 类似工具:将这些模式应用于其他项目
设计哲学
良好的架构是不可见的,它不应该妨碍你。Pydoll 的架构优先考虑:
- 简单性:每个组件做好一件事
- 一致性:类似的操作有类似的模式
- 明确性:没有魔法,没有隐藏行为
- 类型安全:在设计时而非运行时捕获错误
- 性能:默认异步,无锁并行
这些不是任意选择,它们是几十年软件工程经过实战检验的原则。
准备好理解设计了吗?
从浏览器域开始,了解进程管理和上下文隔离如何工作,然后按顺序浏览各个域。
这是从使用到精通的转变。
完成架构学习后
一旦你理解了这些模式,你会在软件工程的各个地方看到它们,而不仅仅是 Pydoll。这些是应用于浏览器自动化的通用模式:
- 外观模式(Tab 简化 CDP 复杂性)
- 观察者模式(用于响应式代码的事件系统)
- 混入模式(FindElementsMixin 用于代码重用)
- 注册表模式(Browser 跟踪 Tab 实例)
- 策略模式(FindElements 解析最优选择器)
良好的架构是永恒的知识。