Selenium 并不是一个单一的项目,而是一个包含多个组件的生态系统,它的 Python 绑定 (selenium) 只是其中的一部分。

我们将从以下几个方面来解析其源码:
- Selenium 生态系统概览:了解各个组件如何协同工作。
- Python 绑定 (
selenium) 的核心架构:分析selenium库的源码结构。 - 核心流程剖析:以
driver.get()为例,看懂一次请求的完整生命周期。 - WebDriver 协议:Python 库与浏览器驱动沟通的“普通话”。
- 如何阅读和调试源码:提供实用的方法。
Selenium 生态系统概览
要理解 Python Selenium 的源码,首先必须理解它的架构,Selenium 采用了客户端-服务器模型。
-
你的测试脚本 (Client):
- 这就是你用 Python (或 Java, C#, 等) 编写的自动化测试脚本。
- 它通过
selenium这个 Python 库与 Selenium 的 WebDriver 协议进行交互。
-
WebDriver 绑定库:
(图片来源网络,侵删)- 这就是
seleniumPython 包本身,它不直接操作浏览器,而是: a. 将你的操作(如find_element,click())转换成标准的 JSON 命令。 b. 通过 HTTP 请求将这些命令发送给浏览器驱动。 c. 接收来自浏览器驱动的响应(如元素信息、执行结果)。
- 这就是
-
浏览器驱动:
- 这是真正的“翻译官”和“执行者”,每个浏览器都有自己的驱动(如
chromedriver,geckodriver)。 - 它是一个独立的小程序,你的测试脚本启动它。
- 它监听来自
selenium库的 HTTP 请求。 - 它将这些 JSON 命令转换成浏览器可以理解的、底层的自动化 API 调用(在 Chrome 中是使用 Chrome DevTools Protocol)。
- 它执行命令,并将结果(如页面截图、元素属性)返回给
selenium库。
- 这是真正的“翻译官”和“执行者”,每个浏览器都有自己的驱动(如
-
浏览器:
最终的目标,它接收来自驱动的指令,并执行相应的操作(导航、点击、输入等)。
关键点:你的 Python 脚本 从不直接与浏览器通信,它始终通过浏览器驱动这个中间层,这就是为什么你需要为每个浏览器下载并配置对应的驱动程序。

Python 绑定 (selenium) 的核心架构
selenium 库的源码在 GitHub 上是开源的:https://github.com/SeleniumHQ/selenium/tree/trunk/py
我们可以通过其目录结构来理解它的设计思想:
selenium/
├── __init__.py # 包的入口点,导出最常用的类和函数
├── api/ # 高级 API,我们日常使用的接口都在这里
│ ├── webelement.py # WebElement 类,代表页面上的一个元素
│ └── webdriver.py # WebDriver 类,代表整个浏览器会话
├── common/
│ ├── exceptions.py # 定义了所有 Selenium 的异常类
│ ├── utils/ # 各种工具函数,如文件路径处理、等待等
│ └── ... # 其他通用组件
├── remote/
│ ├── webdriver.py # **核心!** 远程 WebDriver 的实现
│ ├── connection.py # **核心!** 处理与驱动的 HTTP 连接
│ └── webelement.py # 远程 WebElement 的实现
└── ...
核心类解析
-
selenium.webdriver.remote.webdriver.WebDriver:- 这是所有操作的起点,是我们最常接触的
driver对象。 - 它负责管理整个会话的生命周期(
start_session,quit)。 - 它提供了所有高级操作,如
get(),find_element(),execute_script()等。 - 它内部持有一个
RemoteConnection对象,用于与驱动通信。
- 这是所有操作的起点,是我们最常接触的
-
selenium.webdriver.remote.connection.RemoteConnection:- 这是通信层的核心,它封装了与浏览器驱动的所有 HTTP 交互。
- 它有一个
execute()方法,这是将命令发送给驱动的底层方法。 - 它处理了请求的序列化(将 Python 对象转为 JSON)和响应的反序列化(将 JSON 转为 Python 对象)。
-
selenium.webdriver.remote.webelement.WebElement:- 代表页面上的一个 DOM 元素。
- 它本身并不包含元素数据,而是通过
driver.execute()向驱动发送命令,并附带一个elementID(由驱动在find_element时返回)。 - 当你调用
element.text时,它内部会向驱动发送一个getElementText命令,并附上这个元素的 ID。
-
selenium.webdriver.common.by.By:- 一个简单的枚举类,定义了定位元素的方式(
ID,XPATH,CSS_SELECTOR等)。
- 一个简单的枚举类,定义了定位元素的方式(
核心流程剖析:driver.get("https://www.google.com")
让我们以最常用的 driver.get() 方法为例,追踪其源码执行流程,感受整个“客户端-驱动-浏览器”的交互过程。
步骤 1:你的 Python 脚本调用
from selenium import webdriver
driver = webdriver.Chrome()
driver.get("https://www.google.com") # <-- 调用这里
步骤 2:WebDriver.get() 方法 (在 selenium/webdriver/remote/webdriver.py)
当你调用 driver.get(url) 时,执行的是 WebDriver 类中的 get 方法。
# 简化后的源码
class WebDriver:
# ...
def get(self, url: str) -> None:
"""
Loads a web page in the current browser session.
"""
self.execute(Command.GET, {'url': url}) # <-- 关键!
可以看到,get 方法非常简洁,它只是调用了 self.execute() 方法,并传入了两个参数:
Command.GET:这是一个预定义的命令常量,代表“导航”操作。{'url': url}:一个包含 URL 的字典,作为命令的参数。
步骤 3:execute() 方法 (在 selenium/webdriver/remote/webdriver.py 或 connection.py)
execute() 方法是真正的核心,它负责与驱动通信。
# 简化后的源码
class WebDriver:
# ...
def execute(self, driver_command, params=None):
"""
Sends a command to be executed by a command.CommandExecutor.
"""
# self.command_executor 是一个 RemoteConnection 对象
# 它负责实际的 HTTP 通信
response = self.command_executor.execute(driver_command, params)
# ... 处理响应,检查错误等 ...
return response
这里,self.command_executor 通常就是 RemoteConnection 的一个实例,真正的执行被委托给了它。
步骤 4:RemoteConnection.execute() 方法 (在 selenium/webdriver/remote/connection.py)
这是将命令打包成 HTTP 请求并发送的地方。
# 简化后的源码
class RemoteConnection:
# ...
def execute(self, command, params=None):
"""
Send a command to the remote server.
"""
# 1. 准备请求体
payload = {
'url': self._url, # 驱动的地址,如 'http://127.0.0.1:9515'
'sessionId': self.session_id, # 当前会话的 ID
'command': command, # 'get'
}
if params:
payload['parameters'] = params # {'url': 'https://www.google.com'}
# 2. 序列化并发送 HTTP POST 请求
response = self._request('POST', '/session/$sessionId/' + command_info[0], body=payload)
# 3. 处理响应
if response['status'] == 0:
return response['value']
else:
# 抛出异常
raise WebDriverException(response['value'], response['status'])
这个方法做了三件大事:
- 构建载荷:将命令名 (
command)、参数 (params)、会话 ID (sessionId) 等信息打包成一个字典。 - 发送请求:通过
_request()方法(内部使用requests库)向驱动发送一个 HTTP POST 请求,请求的 URL 类似于http://127.0.0.1:9515/session/<session_id>/url。 - 处理响应:接收驱动的 JSON 响应,如果响应状态码不为 0(表示成功),则抛出异常;否则,返回响应体中的
value字段。
步骤 5:浏览器驱动执行
chromedriver 接收到了这个 HTTP POST 请求,它解析出 url 参数,然后使用 Chrome 的自动化接口(通常是 CDP)让 Chrome 浏览器打开 https://www.google.com,完成后,chromedriver 会返回一个成功的 JSON 响应。
步骤 6:响应返回
RemoteConnection 收到响应,WebDriver.get() 方法执行完毕,你的 Python 脚本继续往下运行。
WebDriver 协议
从上面的流程可以看出,Python 库和驱动之间通过一套标准化的协议(WebDriver Protocol)通信,这个协议是基于 HTTP 和 JSON 的。
- 命令:如
get,findElement,click,executeScript等。 - 端点:如
/session,/session/{sessionId}/element,/session/{sessionId}/element/{elementId}/click。 - 请求/响应格式:统一的 JSON 结构。
Selenium 的 Python 库实现了一套JSON Wire Protocol(较旧)和一套更现代的W3C WebDriver Protocol,现在默认使用 W3C 协议,你可以通过阅读 selenium/webdriver/remote/common/command.py 来看到所有支持的命令定义。
如何阅读和调试源码
-
安装源码: 在你的虚拟环境中,使用
pip install -e .的方式安装selenium的源码,这样你就可以在 IDE 中直接跳转到源码并进行调试。git clone https://github.com/SeleniumHQ/selenium.git cd selenium/trunk/py pip install -e .
-
使用 IDE 调试:
- 在 VS Code 或 PyCharm 中,设置断点。
- 运行你的测试脚本。
- 当执行到断点时,单步步入 (
Step Into),你会清晰地看到调用栈如何从你的脚本一层层深入到WebDriver->execute->RemoteConnection。
-
打印日志: Selenium 提供了强大的日志功能,可以让你看到所有发送给驱动的命令和接收到的响应,这对于调试非常有用。
from selenium import webdriver import logging # 配置日志 logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger('selenium.webdriver.remote.remote_connection') logger.setLevel(logging.DEBUG) # 创建驱动时传入服务日志 service = webdriver.chrome.service.Service(log_path='chromedriver.log') driver = webdriver.Chrome(service=service) # 现在运行你的脚本,你会在控制台看到详细的命令/响应日志 driver.get("https://www.example.com") driver.find_element("id", "some-id").click() driver.quit()这些日志会精确显示
POST /session/...请求的内容和200 OK响应的内容,让你能直接看到底层协议的交互。
理解 Selenium Python 库的源码,关键在于把握其分层架构和代理模式:
- 分层:高级 API (
WebDriver,WebElement) -> 命令转换 -> HTTP 通信 (RemoteConnection)。 - 代理:
selenium库是用户的代理,而浏览器驱动是selenium库与浏览器之间的代理。
通过阅读源码,你会发现它设计得非常清晰和模块化。WebDriver 类关注“做什么”,而 RemoteConnection 类关注“怎么做”,这种解耦使得 Selenium 能够支持多种编程语言和多种浏览器。
