无头浏览器与chromedriver兼容性实战:从原理到国产系统部署全解析 1. 先说清楚无头浏览器和chromedriver到底是什么关系很多刚接触自动化测试或者爬虫的朋友第一次听到“无头浏览器”这个词的时候多少会有点懵。它并不是一种全新的浏览器而是让浏览器在“不弹出界面”的状态下运行。也就是说Chrome 还是那个 Chrome页面还是那个页面JS 照常执行Cookie 照常写入只是你看不到窗口而已。配合 chromedriver你就可以用 Python、Java 这类语言去操控这个“隐形”的 Chrome打开网页、点击按钮、填写表单、获取接口返回值甚至截图、导出 PDF。这套组合在服务端自动化测试、数据采集、定时巡检、页面快照生成等场景里属于最常用的技术方案之一。但这里有个非常现实的问题这套方案对运行环境是有挑剔的尤其是当你的目标环境是国产操作系统麒麟、统信 UOS 等的时候坑要比想象中多得多。网上很多教程默认你用的是 Windows 或 Ubuntu直接下载 chromedriver 扔到 PATH 里就能跑但换到国产系统上各种诡异的问题就会接踵而至。我在实际项目里就遇到过接手一个内部自动化平台领导要求“必须部署到国产化环境”结果光一个 chromedriver 的启动问题就折腾了两天。所以这篇文章我不想只写“怎么用”更想把这套方案从原理到实操、从正常环境到国产系统特殊环境的完整链路讲透帮大家少走那些我已经踩过的弯路。2. 核心概念拆解这套组合的运行原理和选型逻辑2.1 无头浏览器的演进从 PhantomJS 到 Headless Chrome无头浏览器这个概念并不是 Chrome 先提出来的。早期做服务端页面渲染时最常用的方案是 PhantomJS它基于 WebKit 内核天生就是无头的。我在 2015 年前后接触过它接口不太友好而且和现代 Web 标准的兼容性时不时要打折扣——遇到一些用了 ES6 新特性的页面渲染结果经常不对。后来 Chrome 从 59 版本开始官方支持--headless模式情况才彻底改变。因为 Chrome 本身就是事实标准它的无头模式跟有头模式共享同一套内核和渲染引擎页面兼容性问题少了非常多。从 Chrome 96 开始无头模式的内部实现又经历了一次大升级更推荐用--headlessnew参数来启动新的无头模式。两者在 GPU 加速、字体渲染、WebGL 支持上有不少差异新模式下这些功能更接近有头浏览器。chromedriver 的作用是作为浏览器和测试脚本之间的“翻译官”。它实现了 WebDriver 协议脚本通过 HTTP 请求向 chromedriver 下发命令chromedriver 再通过 DevTools 协议驱动 Chrome 执行具体的动作。理解这条链路很重要因为后面很多问题排查追根究底都是在追这条链路里的某一个环节出了问题。2.2 为什么选 chromedriver 而不是别的方案做浏览器自动化市面上并不是只有 chromedriver 一个选择。Selenium 体系下还可以用 Firefox 的 geckodriver也可以直接用 Puppeteer 或 Playwright 这类更现代的库。每个方案都有自己的适用场景。对于绝大部分中文技术团队来说选 chromedriver Selenium 的理由其实很朴素生态老、资料多、同事们都会。Selenium 本身支持多语言Java、Python、C# 都有官方绑定这在企业内部的测试平台里尤为重要——团队里写 Java 的就用 Java 写用例写 Python 的就用 Python 写用例互不耽误。另外 Selenium Grid 支持分布式执行可以在多台机器的不同浏览器上跑用例这是很多测试平台的基础能力。Playwright 其实在很多方面已经比 Selenium 做得更好比如自动等待、多页面上下文隔离、内置 Trace 调试它内置的浏览器下载管理也省了版本匹配的烦恼。但它的语言支持重心在 JavaScript 和 Python对 Java 的支持相对弱一些。如果你的团队经常跟 Java 打交道、有历史框架需要兼容还是绕不开 chromedriver。而且从政府、金融、国企等对技术选型有明确偏好国内很多是 Java 技术栈的项目来看Selenium 体系仍然是事实上的标配。2.3 无头方案的常见应用场景很多人以为无头浏览器只是用来跑自动化测试的其实它的应用范围远远不止于此。我列几个我实际做过或者见过的场景方便大家对号入座。数据采集方面有些网站的数据不是一次性渲染在 HTML 里的而是通过 Ajax 动态请求拿到数据后再渲染到页面上。用 requests 直接请求拿不到完整内容必须等 JS 执行完才能看到真实的数据。无头浏览器就是为了这种场景而生的。页面截图/快照方面比如内部系统要每天定时给自己的报表页面截图存档或者生成一个包含图表渲染结果的 PDF 报告。这些功能在有头浏览器里做很顺手但服务端没有显示器无头模式就成了唯一选择。自动化巡检方面对线上系统做定时冒烟测试每隔十分钟跑一次核心流程发现页面报错就报警。这种场景不需要界面窗口只需要脚本在后台跑无头浏览器一点毛病都没有。总结一句话只要是需要“真实浏览器环境”但又不能弹出真实窗口的场景无头浏览器chromedriver 就是最直接的技术手段。3. 版本匹配90% 的“启动失败”都是栽在这里3.1 chrome driver 和 Chrome 的版本对应关系这个必须单独拿出来说因为它是初学者最容易忽略、却也是最致命的一个细节。chromedriver 和 Chrome 的版本映射关系非常严格chromedriver 的主版本号必须和 Chrome 的主版本号一致否则大概率会启动失败。比如你本机装的是 Chrome 120那你就必须用 120.x.x 的 chromedriver用 119 的驱动去驱动 120 的浏览器很多时候直接抛session not created异常。为什么要这么严因为 chromedriver 毕竟是通过 DevTools 协议和浏览器通信的不同主版本之间协议可能发生变更。比如 Chrome 111 对 WebDriver BiDi 协议有了大规模支持Chrome 132 起对旧版无头模式做了限制。版本不匹配驱动发过去的命令和浏览器实际能解析的逻辑对不上自然就跑不通。我见过一个真实案例同事在 Windows 上测试得好好的部署到 Linux 服务器以后一直报错查了半天最后发现是服务器上装的是 Chrome 114可部署脚本里下载的 chromedriver 还是 110 版本。只差一个小版本白白排查了一下午。3.2 如何快速准确地下载对应版本的 chromedriver下载 chromedriver 有几个渠道但每个渠道的史和使用方式不太一样我的建议是这样**Chrome for TestingCfT**是目前官方主推的方式它保证版本和 Chrome 完全同步。地址是https://googlechromelabs.github.io/chrome-for-testing/页面上有个 JSON 文件列出了所有可用版本。实际上更精准的做法是直接拿 Chrome 的版本号去拼 CfT 的下载 URL比如用 114.0.5735.90 这个版本号可以拼出https://edgedl.me.gvt1.com/chromedriver/114.0.5735.90/chromedriver_linux64.zip这样的链接。这个域名是 Google 的 CDN很多企业在内网环境访问起来比较费劲。国内镜像站是更稳妥的选择。阿里云开源镜像站、华为云镜像站、npmmirror 都有 chromedriver 的镜像。这里我比较推荐 npmmirror 的路径https://cdn.npmmirror.com/binaries/chromedriver/它把历史版本都列出来了文件名规整下载速度快内网环境也常常能访问。还有一个更省事的方法用 npm 包chromedriver直接npm install chromedriver它会自动检测你本机的 Chrome 版本并下载对应的驱动。npm 仓库里其实有整个版本映射表安装的时候自动匹配。不过这只适合开发机到了生产环境多半还是得靠手动下载一个固定的版本。如果你不知道当前系统里 Chrome 的具体版本Linux 下可以执行google-chrome --version # 或者 chromium --version拿到类似Google Chrome 120.0.6099.109的输出后直接去对应镜像目录里找120.0.6099.109目录下的 chromedriver_linux64.zip 就行。3.3 如何验证 chromedriver 是否可用下载解压后建议先做一次单独验证而不是急着上代码。执行./chromedriver --version如果正常输出ChromeDriver 120.0.6099.109 (xxxxx)说明驱动本身没问题。接着启动服务模式./chromedriver --port9515然后在另一个终端用 curl 验证curl http://127.0.0.1:9515/status返回 JSON 里有ready: true就说明驱动已经准备好接受命令了。这一步很关键因为如果你一上来就直接用 Selenium 启动报错了还得从 Python 调用链一路排查到系统级问题而单独验证驱动能直接把“驱动本身是否可用”这个问题先排除掉。注意在企业内网环境--port9515这个默认端口可能在防火墙策略里被禁了。如果是这种情况可以换成别的端口比如--port9516Selenium 里对应写成service_args[--port9516]或者在Service里指定。4. 实操Python Selenium Headless Chrome 的完整落地4.1 环境准备和基础依赖安装这套方案最常用的语言是 Python因为它上手快Selenium 库也维护得不错。假设你已经在 Linux 服务器上先安装 Python 库pip install selenium注意Selenium 4.x 和 3.x 的 API 有不少差异下面的代码是基于 4.x 写的。如果项目里还在用 3.x需要改几个导入和初始化的地方建议直接升到 4.x。然后是 chromedriver 的放置。我的习惯是统一放到/opt/chromedriver/目录下做一个软链到/usr/local/bin/mkdir -p /opt/chromedriver unzip chromedriver_linux64.zip -d /opt/chromedriver/ chmod x /opt/chromedriver/chromedriver ln -s /opt/chromedriver/chromedriver /usr/local/bin/chromedriver这样做的好处是版本升级时只改软链指向新版本就行。如果直接拷贝到/usr/local/bin/升级的时候覆盖旧文件可能留下一些权限或缓存问题。不过这里要说一句下载解压之后最好自己先跑一遍 chromedriver 的启动测试不要直接扫进 bin别问我为什么——系统全局命令多了一个“看起来能用但绝不能用”的废料真的很烦。4.2 最小可运行的 headless 脚本一个最基础的 Python 脚本长这样from selenium import webdriver from selenium.webdriver.chrome.options import Options options Options() options.add_argument(--headlessnew) options.add_argument(--no-sandbox) options.add_argument(--disable-gpu) options.add_argument(--disable-dev-shm-usage) driver webdriver.Chrome(optionsoptions) try: driver.get(https://example.com) print(driver.title) driver.save_screenshot(/tmp/example.png) finally: driver.quit()这里有几个参数值得细说。--headlessnew是新版无头模式自 Chrome 96 起推荐使用。相比老的--headless它更接近有头模式WebGL、Canvas 等特性的兼容性更好。如果你的页面对登录态、Session 比较敏感用new模式体验更稳定。当然如果你的 Chrome 版本比较老比如 80 多那这个参数要写成--headless没有new。--no-sandbox是因为在容器或普通用户下运行 ChromeUbuntu/Debian 的默认 sandbox 配置经常会报错。这个参数本质上是关掉了 Chromium 的沙箱机制在公网环境跑自动化时务必慎用沙箱本身是有安全价值的。但在企业内网、Docker 容器里几乎是标配因为容器内通常没有配置好setuid sandbox。--disable-dev-shm-usage也是容器场景里必须加的。Docker 默认的/dev/shm只有 64MBChrome 的渲染进程写临时文件时经常会冲爆这个目录导致页面崩溃或者截图白屏。加上这个参数后Chrome 会把共享内存文件写到/tmp下面避开这个坑。--disable-gpu其实在现代 Chrome 里已经不绝对必要了因为无头模式对 GPU 依赖本来就很小。但保留它有一个好处避免在一些内核配置比较特殊的系统上触发图形栈相关的报错。特别是国产系统上显卡驱动往往并不完整这个参数能很大程度上降低启动失败的概率。4.3 常用的实用参数和设置在实际项目中有几个参数我用得非常多列在表格里方便大家查阅参数/设置作用使用场景--window-size1920,1080设置页面视口大小截图时需要固定尺寸--ignore-certificate-errors忽略证书错误测试系统使用自签名 HTTPS 证书时--disable-extensions禁用扩展减少环境干扰--disable-web-security关闭同源策略调试跨域接口时用生产环境慎用--user-agentxxx自定义 UA模拟特定浏览器或设备--proxy-serverxxx设置代理流量走抓包工具或指定出口--langzh-CN设置语言页面内容国际化验证--blink-settingsimagesEnabledfalse禁用图片加载提升抓取速度、节省带宽这里说一个我自己常用的小技巧如果目标页面的登录态是走 Cookie 的可以在普通浏览器里登录一次然后把 Cookie 导出在脚本里通过driver.add_cookie()注入这样就能免登录访问。不过要留意 Cookie 的 Domain 必须匹配否则会注入失败。4.4 等待策略别再用傻乎乎的 sleep很多新手写 Selenium 脚本元素找不到就time.sleep(3)页面加载慢就time.sleep(5)。这种方式不是不能用但效率低而且容易误判——页面可能 0.5 秒就加载完了白白等 3 秒也可能页面卡住了等 5 秒也不够。Selenium 4 内置了三种等待方式隐式等待implicitly_wait、显式等待WebDriverWait和原生等待页面 readyState。我推荐的做法是from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By driver.implicitly_wait(5) # 全局兜底等待 element WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.ID, submit-btn)) )混用的时候有个坑隐式等待和显式等待叠加会翻倍。比如隐式等待设了 5 秒显式等待设了 10 秒最坏情况是 15 秒。这在一些性能较差的页面上会导致测试时间成倍拉长。我的做法是只在一处设置隐式等待通常在浏览器初始化后其余全部用显式等待。5. 国产操作系统环境为什么“不支持”以及真实的兼容性分析5.1 “不支持”的真相是什么回到标题里的那句话“目前不支持国产操作系统”。这个说法我见过很多次内部群、需求文档、甚至厂商的项目反馈里都有。但作为一个踩过坑的人我必须说“不支持”不等于“绝对跑不起来”它的真实含义是——官方没有做针对性的适配和测试你的运行环境大概率会在某些环节出问题。这句话的翻译再直白一点官方保证的是 Windows/macOS/Linux主要是主流的 Ubuntu、Debian、CentOS 发行版能跑。至于麒麟、UOS 这类系统不在官方测试矩阵里没有人为你踩过坑出了问题只能自己解决。但问题的本质不是“国产系统不能用”而是国产系统通常和官方支持的 Linux 发行版之间存在几个关键差异这些差异会导致 chromedriver 正常工作的条件被破坏。5.2 核心差异CPU 架构、系统库和内核国产操作系统的兼容性难点主要出在三个层面。CPU 架构层面。这是最硬性的差异。银河麒麟、统信 UOS 通常运行在飞腾FT-2000、腾锐 D2000、鲲鹏 920、龙芯、兆芯等国产 CPU 上。这里飞腾和鲲鹏是 ARM 架构龙芯是 LoongArch旧版是 MIPS兆芯是 x86。chromedriver 官方提供的 Linux 版本只有两个linux64x86_64和linux-aarch64ARM64。如果你的机器是龙芯 LoongArch官方的 chromedriver 根本没有任何可匹配的版本直接判死。glibcGNU C 库版本。chromedriver 是编译好的二进制它对运行环境的 glibc 版本有最低要求。新版 Chrome比如 120 以上编译时用的 glibc 版本比较高如果在旧内核和旧版 glibc 的系统上运行会直接报version GLIBC_2.29 not found这类错误。国产操作系统的系统库版本往往偏保守为了稳定性不会跟最新版所以容易出现这个兼容性问题。桌面环境和浏览器生态。国产系统的默认浏览器通常是奇安信浏览器、360 安全浏览器国产版或者 Chromium 的定制版本这些浏览器和官方 Chrome 的版本节奏未必一致。chromedriver 要驱动的目标最好就是官方 Chrome 或 Chromium。如果说系统里装的是奇安信浏览器它的内核版本往往会落后官方好几个大版本这时候你反而得找一个对应老版本的 chromedriver 去匹配它很麻烦。把这三个差异拼起来看你就会明白“不支持”的真正意思是在 ARM 架构上用官方 chromedriver 也许能跑在 LoongArch 上几乎没有可能即使 CPU 架构没问题系统库和浏览器版本也可能有一个拖后腿。5.3 我实测过的国产系统部署方案下面我分享几个在国产环境上实测有效的方案按推荐程度排序。首选方案在系统里安装官方 Chromium而不是 Chrome。很多国产系统虽然没有官方 Chrome但软件源里可能带了 Chromium或者你可以从系统自带的软件商店里装一个 Chromium 内核的浏览器。Chromium 和 Chrome 的内核是同时期的chromedriver 对 Chromium 的驱动效果几乎一致。装好之后执行chromium --version看版本号再去下载对应版本的 chromedriver注意架构要匹配arm64 机器选 linux-aarch64。这一套路径是官方支持范围内最“正”的成功率最高。备选方案如果只是做轻量化的页面抓取用 Playwright 直接调内置 Chromium。Playwright 会直接管理自己的 Chromium 浏览器文件而且它的下载渠道和 Selenium 是分开的。比如在你的 Python 环境里执行from playwright.sync_api import sync_playwright with sync_playwright() as p: browser p.chromium.launch(headlessTrue) page browser.new_page() page.goto(https://example.com) print(page.title()) browser.close()这样它用的是自己下载的那个内部 Chromium无论是架构匹配还是版本控制都跟系统环境解耦了。唯一需要注意的是 Playwright 的浏览器文件体积较大首次下载也挺花时间但在内网环境配置好离线包后完全能用。兜底方案如果你的架构是 x86兆芯可以硬上官方 Chrome 对应 chromedriver。兆芯 CPU 是 x86 架构指令集兼容性比较好日常用官方 Chrome 是可行的。装上官方 Chrome 后用google-chrome --version看版本号再下载对应版本的 chromedriver选 linux64通常能跑起来。但有个前置条件系统里必须安装了足够新的 glibc。检验方法ldd --version如果输出显示 2.28 以上基本没问题如果是 2.17 这种老版本大概率跑不起来。5.4 一个真实案例的排查过程参考我帮朋友排查过一个在麒麟 V10x86上跑 chromedriver 的案例。现象是执行业务脚本时Chrome 启动后马上退出Selenium 报chrome not reachable。排查步骤1. 先跑 chromedriver --version正常 2. 再用命令行手动启动 chrome --headless --dump-dom https://www.baidu.com发现 Chrome 启动后直接崩溃 3. 在终端看崩溃日志发现缺少 libnss3.so 的某个符号 4. 用 yum install nss -y 补上这个库 5. 再跑居然还报缺少 libatk-1.0.so 之类 6. 继续用 yum 装 atk 相关的依赖 7. 最后补齐了所有依赖脚本成功跑通。这个案例告诉我们国产系统上 Chrome 起不来一大半原因其实是运行依赖没装全。Chrome 对系统动态库的依赖比较多包括 nss、atk、gtk3、libx11 等等。在 Ubuntu 上这些库通常是现成的但国产系统的精简安装版可能没有。专门看官方文档不如自己跑一遍报什么缺什么就装什么效率反而最高。6. 常见问题与排查技巧6.1 初学者最容易遇到的一批错误我把实际工作中见过的高频错误整理成一张速查表方便大家按图索骥错误信息常见原因解决方案session not created: This version of ChromeDriver only supports Chrome version 114chromedriver 和 Chrome 版本不匹配下载与 Chrome 同版本的 chromedriverunknown error: cannot find Chrome binary不知道 Chrome 装在哪里用options.binary_location指定 Chrome 可执行文件路径chrome not reachableChrome 启动后崩溃排查缺失的动态库或加--no-sandboxDevToolsActivePort file doesnt existChrome 进程启动失败通常是缺少依赖或磁盘空间不足看系统日志selenium.common.exceptions.WebDriverException: Message: Service chromedriver unexpectedly exitedchromedriver 启动被拦截检查权限、SELinux 策略、端口占用Failed to move to new namespace容器沙箱权限问题加--no-sandbox或正确配置容器权限version GLIBC_2.29 not found系统 glibc 版本过低换低版本 chromedriver或者升级系统库6.2 排查思路三步法定位问题遇到自动化跑不起来我一般会按下面的顺序排查而不是一上来就改代码。第一步验证浏览器能不能独立启动。命令行直接执行google-chrome --headless --disable-gpu --dump-dom https://example.com如果这一步报错说明浏览器本身就有问题和 chromedriver 无关。浏览器起不来问题出在系统环境不是脚本。第二步验证 chromedriver 能不能独立启动。按前面写的./chromedriver --version和/status检查。第三步验证 Selenium 能不能正常拉起浏览器。这时候再跑最小脚本如果报错看报错信息定位是元素定位问题还是连接问题。这个方法能帮你快速把问题归到三层里的某一层避免在三层之间来回打转。6.3 补充几个不常见但很头疼的坑坑一内存不足。无头浏览器看着轻量其实很吃内存。一个 Chrome 实例常有多个进程每个进程几百 MB。如果在内存只有 2G 的服务器上同时跑三个并发任务很容易 OOM。解决方案是在代码里控制并发数或者用--single-process参数不推荐不够稳定配合调整。坑二乱码。在服务器上跑脚本有时页面内容是 UTF-8 但终端显示乱码。这多半不是浏览器的问题而是系统缺少中文字体。无头浏览器渲染中文时如果找不到对应字体会出现方块字或空白。解决方法是安装字体包yum install -y fontconfig yum install -y wqy-microhei或者下载 Noto Sans CJK 字体放到/usr/share/fonts/目录下执行fc-cache -f刷新字体缓存。基于麒麟环境的机器人流程自动化平台在这个坑上栽过的同学绝对不在少数。坑三截图一片空白。页面截图出来是空白的多半是页面内容本身就是 Canvas 绘制或者 WebGL 渲染的在无头模式下 GPU 支持不完整。解决办法是先driver.set_window_size(1920, 1080)再等页面里的 Canvas 绘制完成最后截图。有时候还需要配合driver.execute_script(return document.readyState)去判断页面加载状态。6.4 关于“克隆式复制”的提醒还有一个我观察到的小问题很多团队的 chromedriver 部署不出来是因为他们直接从同事的机器上把二进制文件拷到服务器上再传到国产系统上。这种做法有风险——chromedriver 是编译好的二进制不同架构之间不能混用。从 x86 的 Ubuntu 拷贝到 ARM 的麒麟上会直接报Exec format error。所以部署前务必先确认三件事uname -m # 查看 CPU 架构 ldd --version # 查看 glibc 版本 cat /etc/os-release # 查看操作系统发行版确认了这三项再决定下载哪个平台的 chromedriver以及是不是需要额外补依赖。7. 替代方案当 chromedriver 真的用不了你还能用什么如果试遍了上面的方案还是解决不了别死磕。换个工具可能是更理智的选择。方案一Playwright。前面已经提过它自带浏览器下载管理和系统环境解耦。在国产系统上能用 Python 装 Playwright 的情况下直接用它启动内置 Chromium 是最省心的。我对 Playwright 的评价是它能解决 90% chromedriver 的版本和依赖噩梦而且写代码比 Selenium 顺手自带自动等待极少出现元素找不到的情况。如果你不是被现有框架绑死我建议优先用这个。方案二Puppeteer。这是 Node.js 生态下的无头浏览器方案。如果你的技术栈是 JavaScript 或者做爬虫可以考虑。它的 API 设计和 Playwright 有点像但对浏览器版本的管理没有 Playwright 那么自动化。方案三无头 Firefox geckodriver。有些对内核兼容性比较敏感的场景Firefox 可能是个备选。在国产系统上 Firefox 的安装包通常比 Chrome 更完整动态库依赖也更少。geckodriver 和 Firefox 的版本匹配规则没有 chromedriver 那么严格。但在国产 ARM 系统上Firefox 的可用性和性能并不乐观我没有大量实际验证过所以只作为思路提供。方案四放弃浏览器自动化改用 HTTP 协议模拟。如果你的目标只是抓取动态页面先去分析一下页面里的 Ajax 接口直接模拟接口请求解析返回的 JSON。这比跑无头浏览器快几个量级资源占用也小得多。遇到加密参数比如常见的token、sign可以尝试用 Node.js 或 Python 执行相应的加密逻辑来生成。这个方案不算技术含量拉满但确实是资源受限环境下的最优解。8. 我的实操心得和几点经验杂谈最后分享几个我在反复折腾这套方案过程中沉淀下来的心得体会吧。第一个心得是版本锁定要趁早。一旦确认了线上用的 Chrome/Chromium 版本就要把对应的 chromedriver 版本固定在部署脚本里。不要每次部署都自动下载“最新版”因为万一 Chrome 出了一版新的驱动和浏览器不匹配你第二天上线就炸了。正确做法是写部署脚本时把版本号写死升级时手动确认后再改。第二个心得是尽量在容器里跑。如果你用的是 Docker可以直接拿现成的 Selenium 镜像比如selenium/standalone-chrome镜像里已经装好了 Chrome、chromedriver 以及所有依赖版本都是匹配好的。哪怕目标环境是国产系统只要这台机器能跑 Docker镜像内部的环境就是一致的。虽然国产系统的 Docker 支持也可能有坑但至少比直接在宿主机上装一套浏览器省心得多。第三个心得是排查问题先看日志再看代码。很多人报错第一时间去看 Python 脚本其实大多数问题不在脚本层面而是浏览器进程压根没起来。Selenium 启动失败的时候你去看浏览器的 stderr 输出往往能看到比 Selenium 自身报错更接近真相的信息。为了这个我习惯在本地调试时手动把service_log_path打开from selenium.webdriver.chrome.service import Service service Service(executable_path/opt/chromedriver/chromedriver, service_log_path/tmp/chromedriver.log) driver webdriver.Chrome(serviceservice, optionsoptions)这样 chromedriver 会记录完整的通信日志两个进程之间发生了什么一目了然。排查问题的时候翻这个日志比猜测哪里出的问题高效十倍不止。第四个心得是在国产系统上不要追求“跟官方保持一致”追求“能跑就行”。官方文档不可能覆盖到麒麟、UOS 的每一个特判所以你的目标应该是找到一条能可靠运行的最小路径然后把它钉死做进自动化部署里。哪怕这条路径在某些人眼里不够“正统”只要稳定就不丢人。无头浏览器 chromedriver 这套组合如果只是在自己电脑上跑通真的不难难的是在各种各样的环境里都能稳定跑通。希望这篇文章能帮你减少一些试错时间尤其是在国产系统面前少走几步弯路。如果后面你有更好的国产系统兼容性方案也欢迎来交流。