Undetected Chromedriver:浏览器层替代方案
按变更归属、更新面和授权测试能提供的证据,对比 undetected-chromedriver 与浏览器层配置。
BotBrowser Team
“不被检测的浏览器”描述的是检测方给出的结果,不是浏览器二进制的稳定属性。公开项目 undetected-chromedriver 将自己描述为经过修改的 ChromeDriver,而浏览器层配置文件是提供配置的另一种方式。两种方式的归属、更新面和证据要求都不同。比较它们时,先确认哪个层负责改变数值,再只在自己拥有或获得授权的页面上测量。BotBrowser 文档说明浏览器配置文件和自动化一致性的边界;它不会修改 undetected-chromedriver,不会维护该包的兼容性,也不承诺网站或检测方的结果。
摘要
undetected-chromedriver的 README 描述了自动下载并修改驱动。这是项目的公开描述,不是对每个版本和目标的独立验证。- 浏览器包、WebDriver 驱动和浏览器核心属于不同层。改变一层不能证明另一层改变,也不能证明网站接受会话。
- 记录包和浏览器的确切版本、请求参数、返回的 capabilities、测试页面来源、读取的 API 和预期值比较。示例记录必须明确标为示例。
- 只使用自己拥有或明确获准使用的页面。页面不提供检测规避或第三方挑战处理做法。
navigator.webdriver是该浏览器会话可见的一个属性,不是身份、授权或检测结论的证明。
内容
公开项目描述的范围
项目 README 把 undetected-chromedriver 描述为修改 ChromeDriver 的项目。README 说该包会自动下载并修改驱动二进制;示例用 headless=True 和 use_subprocess=False 创建 Python 驱动。这些内容可以说明项目公开展示了什么、示例调用了什么,但不能构成当前兼容性矩阵,不能证明每个安装中的修改都已执行,也不能说明另一个网站会如何处理会话。
README 还对 anti-bot 服务使用了很强的结果表述。可以准确地说 README 包含这段表述,但它不是普遍测量:没有列出所有服务版本、浏览器构建、账号、网络、配置文件和日期。这里不测试这些服务,因此不把 README 的表述当作结果。检测结果属于检测方的配置和当时的运行条件。
项目页面和已安装的包也代表不同时间。安装后,仓库默认分支可能继续变化。因此,可复现记录要分开保存 Python 包版本、源代码 commit、下载的驱动、Chrome 版本和会话参数。仅有包名不足以重现配置或解释失败。
Selenium 分别记录 Chrome 驱动行为、浏览器选项以及网站响应。Chrome 文档解释浏览器专用驱动集成,WebDriver 文档解释会话创建和 capabilities。这些来源支持“请求配置”和“协商后会话”之间的边界,但不能验证第三方修改,也不能决定远程网站的接受策略。
MDN 将 navigator.webdriver 定义为只读属性,用于表示用户代理是否受自动化控制。它只是浏览器 API 暴露的一个信号,不是浏览器身份、权限令牌,也不是远程服务可能收集的全部数据。页面可以报告它的值,但不能仅凭这个值还原驱动或包的全部实现。
来源和观察必须分开。源代码可以证明项目存在某条代码路径,却不能单独证明这条路径在某个进程中执行。自有页面可以证明页面读到了什么,却不能证明另一个网站读取了相同字段或采用相同解释。每个结论都要保留来源和测量条件。
三个层有不同的归属
“浏览器修改”太宽泛,无法说明改了什么。可将页面 JavaScript、WebDriver 或驱动包代码、浏览器核心行为分开比较。每一层的输入和更新责任都不同;检测方实际读取的信号清单和Selenium 配置文件集成参考覆盖相邻的浏览器可见面。
| 层 | 通常归属 | 它的材料可以证明什么 | 更新面 | 它本身不能证明什么 |
|---|---|---|---|---|
| 页面脚本 | 应用或注入脚本 | 哪段 JavaScript 被加载、尝试包装哪些 API | 网站 bundle、扩展或预加载脚本、执行顺序 | 原生浏览器行为或远程结论发生变化 |
| 驱动或包 | WebDriver 实现及其包 | 请求的会话选项、驱动代码、可能的可下载驱动版本 | 包版本、驱动下载、Selenium 与浏览器兼容性 | 浏览器核心数值发生变化或网站接受会话 |
| 浏览器核心 | 浏览器构建及支持的配置 | 给定启动条件下的数值和行为 | 浏览器版本、构建参数、配置文件和平台 | 每个页面都读取这些数值或赋予相同含义 |
页面包装器在 JavaScript 上下文中运行。它可以影响被包装对象的读取,但页面还可能有其他上下文、原生路径或浏览器控制的数值。只有在范围明确且获得授权的页面上,才能判断这些路径是否重要。补丁文件中的 API 名称不等于每个消费者都看到修改后的结果。
驱动包负责建立会话和管理它分发或修改的驱动材料。Selenium options 描述创建会话时发送的请求,返回的 capabilities 描述远端协商后的会话。二者是不同证据。要知道页面真正可见的行为,仍需在明确的浏览器构建和运行配置下测量页面。
浏览器核心是另一条边界。配置文件可以在启动时提供受支持的数值或设置,而不是只在文档开始执行后由页面脚本替换 API。这说明配置从哪里进入,不表示每个属性都受支持、各平台行为相同或检测方会接受结果。受支持的字段以产品配置文件文档为准,不能从“配置文件”这个词推断未记录行为。
三个层可以同时参与一次运行。Selenium 可以创建会话,浏览器可以加载配置文件,页面也可以执行自己的脚本。记录时分别归类:requestedOptions 属于 WebDriver 请求,returnedCapabilities 属于协商会话,profileInput 属于浏览器启动,pageReads 属于自有页面。把它们合称为“不被检测状态”会掩盖诊断所需的边界。
更新面跟随归属。页面 bundle 可以在浏览器不变时更新,驱动包可以在浏览器二进制不变时更新,浏览器版本也可以在 Python wrapper 不变时改变 API 或默认值。结果发生变化时,先比较有版本的材料,否则容易把原因归给没有变化的组件。
版本变化会产生不同的维护工作
驱动自动下载很方便,但自动化必须知道选中了哪个材料。README 说包会下载并修改驱动,所以要记录解析后的包版本、下载决策、最终驱动路径、驱动版本和浏览器版本。不能从 import 语句推断下载了哪个 executable。
至少保留这样的运行清单:
| 字段 | 示例含义 | 用途 |
|---|---|---|
packageVersion | 已安装 Python 分发版本 | 标识包代码,不一定是选中的 Chrome 二进制 |
sourceRevision | 从源代码安装时的 tag 或 commit | 区分移动分支与已发布版本 |
driverVersion | 选中 WebDriver 的报告版本 | 排查启动和兼容性 |
browserVersion | 浏览器会话返回的版本 | 标识页面运行环境 |
requestedOptions | 去掉秘密后的显式选项 | 记录客户端请求 |
returnedCapabilities | 选取的公开会话字段 | 记录协商返回值 |
fixtureRevision | 自有页面或 fixture 的 commit | 固定被观察对象 |
observedAt | 时间和环境标识 | 连接不同运行 |
README 中“测试到当前 Chrome beta”的说法不能变成对某个 Chrome、操作系统、驱动或 CI 镜像的保证。重要组合应在受控环境中单独记录和检查。没有来源的字段保持未知,不要用推断填充。
包管理的下载也影响可复现性。干净的 CI 机器可能因包版本、缓存或浏览器选择不同而下载另一个材料。记录解析路径和二进制版本,但不要公开机器秘密或私有路径。获得政策允许时,稳定 hash 可以区分二进制;它仍不能说明二进制对远程服务做了什么。
版本变化至少可能产生四类失败:解析变化,包选择或下载了另一个驱动;会话协商变化,Selenium 无法创建会话或 capabilities 不同;浏览器执行变化,会话成立但自有页面读到的值不同;应用结果变化,页面读对了值但应用检查因别的原因失败。403 不能证明某个指纹信号是原因。记录最早失败的边界和该层需要的数据。
即使浏览器版本不变,也要审查包变化,因为包可能改变驱动解析或会话构造。反过来,包代码不变时浏览器也可能改变结果。两侧都要记录。
在自有页面上测量但不输出结论
自有 fixture 可以检查测试工具是否读到了预期值,但不应向检测器发送请求,也不应给商业保护服务分类。可以使用本地应用或团队控制的内部测试域,自己控制标记、API 调用和预期结果;尽量只读并使用合成数据。
第一次运行建立单一浏览器配置的 baseline,第二次只改一个变量,例如浏览器构建或文档化配置文件输入。不要同时改包、浏览器、配置文件、页面 bundle、viewport 和操作系统,否则无法判断差异来自哪里。
下面的 JSON 是建议的回执结构,不是实测结果。尖括号是占位符;这里不声称运行了 BotBrowser 或 undetected-chromedriver。
{
"fixtureOrigin": "https://owned-fixture.example.test",
"fixtureRevision": "<commit-sha>",
"package": { "name": "<package-name>", "version": "<resolved-version>", "sourceRevision": "<release-tag-or-commit>" },
"browser": {
"driverVersion": "<reported-driver-version>",
"browserVersion": "<reported-browser-version>",
"platformName": "<reported-platform>"
},
"requestedOptions": ["<sanitized-option-name>"],
"observations": [
{
"surface": "navigator.webdriver",
"expected": "<fixture-specific-expectation>",
"observed": "<actual-fixture-value>",
"match": false
}
],
"applicationResult": { "httpStatus": 200, "fixtureSchemaValid": true },
"observedAt": "<UTC-timestamp>"
}
在把回执当作证据前,用真实运行数据替换全部占位符。预期值只属于这个 fixture;布尔预期不是其他会话的策略。缺失字段用 null 并解释原因。不要把示例标为成功,不要伪造时间,也不要把示例 JSON 写成观察结果。
Fixture 应返回小而明确的响应,不要收集完整浏览器指纹。例如 endpoint 可以返回 fixture revision 和 navigator.webdriver 观察值,服务端日志另记页面是否加载。比较包和浏览器不需要收集 canvas、字体、本地地址、账号标识或无关设备属性。数据最小化让测试紧贴验收项。
| 运行 | 改变的输入 | 包与浏览器标识 | 页面观察 | Fixture 结果 | 解释 |
|---|---|---|---|---|---|
| A | Baseline | 已记录的 package、driver、browser 版本 | 自有页面返回的准确值 | HTTP 与 schema | 只描述 baseline |
| B | 一个文档化输入 | 除变量外保持相同 | 自有页面返回的准确值 | HTTP 与 schema | 差异属于改变的输入,不是第三方结论 |
只有在结果可能受非确定条件影响时才重复,并记录每次尝试。受控 fixture 报错时,回执要写 HTTP 状态和 schema 是否满足。HTTP 成功不等于数据有效,拒绝也可能准确表示 fixture 拦截了未授权请求。
按失败层阅读记录
从最早失败的边界开始。浏览器未启动,就没有页面观察;会话创建失败,就不能把旧页面值当成新会话输出;fixture 加载但 schema 失败,要把抽取问题与浏览器版本分开。
| 证据 | 支持什么 | 仍未知什么 |
|---|---|---|
| 包 lock 与源 revision | 安装了哪个包版本或源码快照 | 该包每条分支是否执行 |
| 启动日志与驱动版本 | 哪个驱动启动、会话是否推进 | 远程网站如何解释会话 |
| 返回的 capabilities | 远端返回哪些会话字段 | 每个选项是否改变页面输出 |
| 自有页面观察 | 页面在记录条件下读到什么 | 其他服务收集或决定了什么 |
| Fixture HTTP 与 schema | 受控应用是否返回接受的记录 | 访问第三方网站是否被允许 |
如果请求和返回 capabilities 不同,检查会话创建。如果会话存在但 fixture 值变化,比较浏览器、配置文件、系统和 fixture revision。外部网站出现拒绝页时停止并遵守其公开策略;仅凭回应无法知道责任信号。
日志片段要短,并删除 cookie、授权 header、账号数据、配置文件内容和机器秘密。保留 source revision、清理后的 options、结果分类和足以连接 CI job 的时间。比较两个包时使用同一 fixture 和应用数据;不同的驱动配置要明确写出,不要与浏览器升级混在一起。
浏览器层配置的位置
浏览器层配置回答的是不同于修改 WebDriver 包的维护问题。它在启动或配置文件中提供文档化设置,而不是只依赖页面 wrapper 或包管理的驱动修改。选择取决于受支持的配置、自动化接口、平台覆盖和更新策略。
BotBrowser 配置文件文档说明支持的输入,自动化一致性文档说明产品边界。除非有明确集成文档,这些来源不能证明 Selenium、某个 Grid 或 undetected-chromedriver 受支持。可启动浏览器 executable 不等于已验证 WebDriver 集成。
比较时问三个问题:哪个组件负责改变?要跟踪哪个版本?哪份证据确认自有页面读到了什么?这是工程取舍,不是普遍的检测结果。
BotBrowser 通过配置文件提供文档化的浏览器层数值;它不会修改第三方驱动,也不保证检测结果、网站响应、身份、授权或访问。配置文件存在不证明已经加载,加载声明需要产品支持的证据。页面 fixture 仍是观察值的证据。
这不是检测规避教程。没有到达受保护页面的步骤,也不建议不断修改信号直到目标接受会话。网站拒绝访问时,遵守其策略并使用获准的集成路径。
常见问题
README 能证明当前版本到处有效吗?
不能。README 说明项目目标和实现概览,并没有包含服务版本、账号、浏览器构建和环境的完整日期矩阵。
返回的 capabilities 能证明配置文件已加载吗?
不能。它只是 WebDriver 会话数据,配置文件加载是另一个启动事实,需要产品支持的证据。
navigator.webdriver 是检测评分吗?
不是。它是表示自动化控制的浏览器属性,不编码供应商的模型或决定。
本地 fixture 能预测商业保护服务的决定吗?
不能。它只能在记录条件下验证自己的契约。
版本比较应记录什么?
记录包和源 revision、驱动与浏览器版本、清理后的 options、fixture revision、观察到的 API,以及分别记录 HTTP 和 schema 结果。每次只改一个输入。
延伸阅读
页面脚本修改的边界见页面补丁的限制,配置文件输入见 Selenium 浏览器配置文件集成。
来源
- undetected-chromedriver 公开仓库与 README
- Selenium:Chrome
- Selenium:浏览器选项
- BotBrowser 自动化一致性
- BotBrowser 配置文件
- MDN:Navigator.webdriver
实践结论
先比较归属,再比较说法。README 描述下载并修改驱动的包;Selenium 记录会话协商;浏览器配置文件提供文档化启动值;自有页面只报告读取到的内容。分别跟踪这些版本,每次只改一个受控输入,并让回执区分浏览器启动、会话协商、页面观察和应用结果。这些记录都不能证明身份,也不能预测第三方结论。面向授权工作选择可维护、有文档的集成,并遵守网站访问边界。
本文属于指南: 浏览器自动化入门:首次运行地图