尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Selenium驱动chromedriver安装与版本匹配避坑

Selenium驱动chromedriver安装与版本匹配避坑 1. 先弄明白 chromedriver 到底卡在链路里的哪个位置1.1 从一次真实的报错说起我在带新人的时候最常见的第一个卡点不是写不出选择器也不是元素定位写错而是脚本刚跑起来第一行就炸了控制台甩出一段红字大意是当前使用的 Chrome 版本与 chromedriver 不匹配或者chromedriver 可执行文件不存在。很多人第一反应是怀疑代码写错了反复检查find_element的写法其实问题根本不在代码而在于浏览器和驱动这两个东西没有对上号。用一句话解释chromedriver 是一个独立的可执行程序它把自动化脚本发来的指令翻译成 Chrome 能听懂的操作再把浏览器的执行结果回传给你的脚本。你的 Python、Java、Node 代码从来不直接操控浏览器中间必须站着一个翻译官这个翻译官就是 chromedriver。所以安装 chromedriver 本质上是两件事第一把这个可执行文件放到系统能找到的地方第二保证它的版本号和本机 Chrome 的版本号在同一档位上。适合看这篇内容的人大概分三类。第一类是刚开始学 Selenium 做网页自动化的同学卡在环境配置上第二类是要在服务器或者 CI 流水线里跑无头浏览器的后端工程师经常遇到本地能跑、线上报错第三类是偶尔需要抓一些动态渲染页面的数据的朋友用 Selenium 当临时工具。不管属于哪一类下面这些坑基本都要踩一遍提前知道能省掉大量查资料的时间。1.2 它和 Chrome 之间到底靠什么对话稍微往深一层看这套机制在专业上叫 WebDriver 协议。你的脚本按照协议格式发一个 HTTP 请求给 chromedriverchromedriver 监听在本地的一个端口上默认是 9515收到请求后它再通过 Chrome 提供的调试接口把动作下发下去。所以你在代码里写driver.get(...)实际发生的是一次跨进程的请求往返而不是函数调用。理解这一点很有用后面排查各种超时、端口占用、连接被拒绝的问题时思路会清晰很多。也正因为中间隔了一层进程通信chromedriver 和 Chrome 之间必须约定好一套暗号这套暗号随着 Chrome 版本迭代会变。大版本变了暗号结构可能调整老版本的 chromedriver 就读不懂新版本 Chrome 返回的数据于是直接报错退出。这就是为什么版本匹配这件事被反复强调它不是洁癖而是硬性约束。还有一点容易被忽略chromedriver 不是唯一选择。Firefox 用 geckodriverEdge 用 msedgedriverSafari 用 safaridriver它们的角色完全一致。你如果换了浏览器代码逻辑几乎不用改但对应的驱动必须换。所以学会装 chromedriver 之后其他驱动基本是同一个套路把 Chrome 换成对应浏览器、把下载文件名换掉就行。1.3 什么时候必须装什么时候可以绕开不是所有自动化场景都需要自己手动装。Playwright 就自带了一套浏览器管理机制它用的是自己打包的驱动和浏览器副本命令行执行一次安装动作就会把需要的东西全部拉下来你不需要关心版本号。Puppeteer 也类似它会下载一份匹配好的 Chrome for Testing。只有当你用 Selenium 并且明确指定要驱动本机已安装的 Chrome 时才需要自己动手处理 chromedriver。还有一个常见的误区有人以为装了 Chrome 就等于装好了驱动或者以为 pip 装上 selenium 库就万事大吉。这两个是完全独立的环节。pip install selenium只是把客户端的代码装进你的 Python 环境它不含任何驱动二进制Chrome 浏览器是你用来上网的那个图形程序。三者各管一段缺一段链路就断。判断标准很简单如果你代码里出现webdriver.Chrome()或者webdriver.Chrome(service...)那你就是在直接调用 chromedriver需要自己保证它的存在和版本正确。如果你用的是 Playwright 或者 Puppeteer 的默认配置通常可以跳过手动安装这一步。搞清楚自己在哪条路上能避免做无用功。2. 版本匹配装之前先把这一步做对2.1 三步查出本机 Chrome 的准确版本版本号查错是最高频的事故来源很多人凭印象说我的是最新版结果差了七八个大版本。准确的做法有三个途径任选一个即可。第一个途径是在浏览器地址栏输入chrome://version回车之后页面会直接列出完整版本号形如131.0.6778.86。这个页面信息最全还包括用户数据目录、命令行参数等排查其他问题时也常用到。第二个途径是点击浏览器右上角的三点菜单进入帮助里的关于页面它会显示当前版本并自动检查更新。这个入口的好处是顺手就能确认是不是最新版。第三个途径是命令行查询。Windows 下可以在 PowerShell 里执行(Get-Item C:\Program Files\Google\Chrome\Application\chrome.exe).VersionInfo.ProductVersionmacOS 下执行/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --versionLinux 下执行google-chrome --version。这几个命令在服务器或者容器里特别有用因为那些环境没有图形界面打不开浏览器菜单。拿到版本号之后你只需要关注前三段数字也就是主版本、次版本和构建号通常取主版本就足够定位驱动了。比如131.0.6778.86你需要的驱动主版本就是 131。2.2 版本对应关系的三种情况这里有个分水岭需要注意从 Chrome 115 开始官方调整了发布策略驱动的主版本号与 Chrome 主版本号直接对齐。也就是说 Chrome 是 131你就找驱动 131.x.x.xChrome 是 128就找驱动 128.x.x.x一一对应找起来非常省心。第二种情况是 115 之前的版本那时候驱动版本号是一套独立的编号体系比如 2.37 对应 Chrome 66 附近114 对应 Chrome 114 但后几位数字并不一致。热词里出现的chromedriver2.37.544315就是那个年代的产物现在如果还在用这个版本号说明对应的 Chrome 已经非常老了在现代网页上大概率跑不动建议直接升级整条链路。第三种情况是驱动的次版本号可以略高于 Chrome。比如 Chrome 是 131.0.6778.86你装了 131.0.6778.130这通常没问题因为同一主版本内的差异一般是修 bug。但反过来驱动次版本明显低于 Chrome就可能出现某些新接口不认识的情况。所以选驱动的时候遵循一个原则主版本必须一致次版本和构建号选不比 Chrome 低的那个。我个人的习惯是拿到 Chrome 版本后去驱动的发布列表里筛选同一个主版本号然后挑最新的一条记录下载。这样做的理由是修补过的问题不会再复现稳定性最好。2.3 一份可复用的版本对照速查表下面这张表是我自己整理的常见情况对照方便快速判断该往哪个方向走。注意表中的对应关系列是规则描述不是穷举清单。Chrome 主版本区间驱动版本规则常见问题处理建议115 及以上主版本号完全一致次版本略低导致偶发超时选同主版本里最新的驱动70 到 114主版本号基本一致后几位不同版本号格式容易记混以驱动发布说明为准70 以下独立编号如 2.37现代网页兼容性差升级浏览器与驱动无 Chrome无对应驱动报找不到浏览器先装浏览器再装驱动提示表格里的规则会随官方发布策略调整落地前用本机版本号核对一次不要直接照抄别人博客里的具体数字。这张表的用法是先看自己 Chrome 的主版本落在哪一行再看对应的规则最后回到发布列表里挑具体文件。整个过程不超过三分钟比出问题之后再回头排查要划算得多。3. 三个平台的下载与安装实操3.1 Windows解压、改名、放对位置Windows 上最容易出的问题是下载的压缩包解压出来是一个文件夹而程序只认那个 exe 文件。驱动的下载文件通常是一个 zip 包名字里会标明平台比如带win32字样的是 Windows 版带mac64或mac-arm64的是 macOS 版带linux64的是 Linux 版。解压之后你会得到一个chromedriver.exe这才是真正需要的东西。放置位置有几种选择我按推荐程度排序。第一种是放到一个你自己建的固定目录比如D:\tools\chromedriver\然后把这个目录加进系统环境变量 PATH。这样做的好处是后续升级只要替换这一个文件不用改代码。第二种是直接丢到 Python 的 Scripts 目录比如C:\Python312\Scripts\因为这个目录通常已经在 PATH 里了省事但不够整洁而且换 Python 环境时容易漏掉。第三种是放在项目根目录代码里写相对路径适合临时脚本但不适合长期项目。具体操作上解压之后建议把文件重命名为chromedriver.exe有些下载包里名字带版本号然后复制到目标目录。接着按 Win 键搜索环境变量打开编辑系统环境变量在环境变量窗口里找到 Path点编辑把目录路径加进去。加完之后一定要重新打开一个命令行窗口因为已经开着的窗口读的还是旧的环境变量。验证是否生效在命令行里执行下面这行能打印出版本号就说明路径没问题。chromedriver --version如果提示不是内部或外部命令八成是路径没加对或者是没有重开命令行窗口。注意Windows 上不建议把驱动放到C:\Program Files下面。那个目录有权限保护某些脚本运行时可能因为没有写权限而出现奇怪的问题放到用户目录下的自建文件夹里更稳妥。3.2 macOS两种装法与权限处理macOS 上有个绕不开的门槛从网上下载的可执行文件会被系统打上隔离标记直接运行会弹窗提示无法验证开发者。这不是文件有问题而是系统的安全策略。解决办法是下载并放到目标目录后执行一次去除隔离标记的命令。# 假设驱动放在了 /usr/local/bin 目录下 xattr -d com.apple.quarantine /usr/local/bin/chromedriver chmod x /usr/local/bin/chromedriver第一条命令去掉隔离属性第二条给文件加上可执行权限。这两步都做完Selenium 才能正常拉起它。另一个需要特别注意的点是芯片架构。Apple 芯片的 Mac 和 Intel 芯片的 Mac 需要下载不同的文件。如果你的机器是 M 系列芯片却装了 Intel 版本运行时可能被系统通过转译层跑起来速度慢不说还可能在无头模式下崩溃。怎么看自己的架构执行uname -m输出arm64就是 Apple 芯片输出x86_64就是 Intel。PATH 的处理上macOS 从 Catalina 开始默认 shell 换成了 zsh配置文件是~/.zshrc不是以前的~/.bash_profile。很多人照着老教程改了 bash 的配置结果新开终端一直不生效就是踩了这个坑。加路径的写法如下echo export PATH$PATH:/usr/local/chromedriver ~/.zshrc source ~/.zshrc如果你用 Homebrew其实还有一条更省事的路线。Homebrew 提供了直接安装驱动的命令装完之后文件会自动落在 Homebrew 管理的目录里PATH 也是配好的。缺点是版本由 Homebrew 的仓库决定可能不是最新遇到 Chrome 刚更新而仓库还没跟上的时候需要等一等。所以我自己是手动装为主、Homebrew 为辅关键时刻不依赖第三方仓库的更新节奏。3.3 Linux 与服务器无头环境下的特殊处理服务器上的情况和桌面端差别很大因为大多数服务器没有图形界面Chrome 必须跑在无头模式而这个过程比桌面端多几个前置依赖。我见过太多人在服务器上装了驱动却一直报无法启动浏览器最后发现是缺了一堆系统库。先说要装的依赖。以常见的 Debian 系发行版为例下面这些包需要提前装好否则 Chrome 起不来sudo apt-get update sudo apt-get install -y fonts-liberation libasound2 libatk-bridge2.0-0 \ libatk1.0-0 libatspi2.0-0 libcairo2 libcups2 libdbus-1-3 \ libdrm2 libgbm1 libglib2.0-0 libgtk-3-0 libnspr4 libnss3 \ libpango-1.0-0 libx11-6 libxcb1 libxcomposite1 libxdamage1 \ libxext6 libxfixes3 libxkbcommon0 libxrandr2 xdg-utils这份清单看着长但缺任何一个都可能让 Chrome 在启动阶段直接退出而报错信息往往只有一句浏览器意外关闭非常不友好。所以建议一次性装齐别一个个试。驱动的放置和权限处理和 macOS 类似解压后放到/usr/local/bin/然后加上可执行权限unzip chromedriver_linux64.zip sudo mv chromedriver /usr/local/bin/ sudo chmod x /usr/local/bin/chromedriver chromedriver --version无头模式在代码里的配置也要对应调整。下面是一段可以直接参考的写法加了几个在服务器上必须设的参数from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.chrome.service import Service options Options() options.add_argument(--headlessnew) options.add_argument(--no-sandbox) options.add_argument(--disable-dev-shm-usage) options.add_argument(--disable-gpu) options.add_argument(--window-size1920,1080) service Service(/usr/local/bin/chromedriver) driver webdriver.Chrome(serviceservice, optionsoptions)--no-sandbox和--disable-dev-shm-usage这两个参数在容器或内存受限的服务器上尤其重要。前者的作用是绕开沙箱因为容器里常用的权限模型不支持沙箱所需的命名空间后者是因为共享内存分区在容器里通常很小不改的话页面加载到一半会莫名崩溃。这两个坑我都踩过而且报错信息完全看不出是内存问题只能靠经验判断。4. 让程序准确找到驱动文件4.1 PATH 到底是怎么生效的很多人对 PATH 的理解停留在加了就能用其实它的逻辑很朴素系统在执行一个命令时会按顺序遍历 PATH 里列出的目录看哪个目录里有同名的可执行文件找到第一个就用它。这意味着目录的先后顺序会影响结果。如果你在多个位置都放了名为chromedriver的文件排在前面的那个会被采用而它可能是个老旧版本。这个特性会带来一个非常隐蔽的问题你以为自己升级了驱动实际运行的还是旧的那个。排查方法是在命令行执行which chromedrivermacOS 和 Linux或者where chromedriverWindows看看系统实际找到的是哪个路径下的文件。这一步在遇到明明换了版本还报版本不匹配的时候几乎是必做的。还有一种情况是代码里写死了绝对路径。这样做的好处是可控不依赖环境变量坏处是换机器或者换目录就要改代码。我的习惯是把路径写进配置项或者环境变量代码里读配置这样本地和线上可以用同一套脚本只是配置不同。比如import os driver_path os.environ.get(CHROMEDRIVER_PATH, /usr/local/bin/chromedriver)线上部署时设置环境变量即可不用动代码也避免了把本机路径提交到版本库的尴尬。4.2 Selenium 各个版本的写法差异这里有个很实际的坑不同版本的 Selenium 客户端初始化驱动的写法不一样。Selenium 4.x 之后废弃了早期的一些参数如果你照着三四年前的教程写代码能跑但会收到一堆弃用警告某些参数直接失效。老写法大致是webdriver.Chrome(executable_path...)而 4.x 推荐用Service对象来传路径。两者的区别在于前者把路径当参数直接塞给构造函数后者把服务和配置分离职责更清晰。下面是几个版本里都能用的稳妥写法from selenium import webdriver from selenium.webdriver.chrome.service import Service from selenium.webdriver.chrome.options import Options service Service(executable_path/usr/local/bin/chromedriver) options Options() options.add_argument(--headlessnew) driver webdriver.Chrome(serviceservice, optionsoptions) try: driver.get(https://example.com) print(driver.title) finally: driver.quit()注意最后一定要quit()而不是只关窗口。quit()会通知驱动退出并回收进程只关窗口的话驱动进程可能残留在后台跑多了会积累一堆僵尸进程占内存。我在一台跑批任务的机器上就遇到过这种情况跑了几天之后内存被吃光查了半天才发现是驱动进程没退干净。4.3 自动管理驱动的方案与它的代价既然版本匹配这么麻烦有没有省事的办法有社区里有专门的工具可以自动检测本机 Chrome 版本、下载对应驱动、返回路径一行代码搞定。from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager service Service(ChromeDriverManager().install()) driver webdriver.Chrome(serviceservice)这套方案适合本地开发和快速验证但用之前要清楚它的代价。第一每次执行都会去检查是否有缓存缓存策略和下载源的速度会直接影响启动时间网络不通的时候甚至会让脚本卡住。第二它下载的版本和你在服务器上实际部署的版本可能不是同一个本地跑通了不代表线上没问题。第三CI 环境里如果每次构建都重新下载会显著拉长构建时间。所以在生产环境我更倾向于把驱动文件随项目一起打包或者预先打进基础镜像版本固定、可控、启动快。自动管理工具留在本地开发阶段用两边分开互不影响。这个取舍没有标准答案但一定要有意识地做选择而不是随手复制一段代码就上线。5. 常见报错与排查实录5.1 报错信息与实际原因的速查表驱动相关的报错有个共同特点文字描述和真实原因经常对不上。下面这张表是我这几年攒下来的对照关系按报错关键词查会比较快。报错关键词真实原因处理动作版本不匹配、only supports Chrome versionChrome 与驱动主版本不一致换同主版本驱动找不到可执行文件、No such file路径写错或文件没权限检查路径并加执行权限连接被拒绝、Connection refused驱动未启动或端口被占检查进程与 9515 端口占用浏览器意外关闭、Chrome failed to start缺少系统依赖库补齐依赖包清单元素找不到、no such element页面未加载完或选择器错误加显式等待并核对选择器会话超时、timeout页面过重或网络慢调整超时参数并检查网络这张表的价值在于缩短定位时间。遇到报错先抓关键词再对应到可能原因最后按处理动作验证比漫无目的地搜索效率高得多。注意报错信息里的版本二字要仔细看。有时候它说的是浏览器版本太低不是驱动版本不对。这两种情况的处理方式完全相反一个是升级驱动一个是升级浏览器。5.2 无头模式特有的几个坑无头模式下最典型的问题是截图是白屏、元素明明存在却定位不到、点击没有反应。这几个现象背后的原因不太一样。截图白屏通常是渲染没完成就截了需要在截图前加显式等待或者滚动一下页面触发懒加载。元素定位不到很多时候是因为无头模式下的窗口尺寸默认很小某些响应式布局会在小屏下把元素隐藏或者换位置。解决办法是启动时显式指定一个接近真实的窗口尺寸也就是前面代码里的--window-size参数。点击没反应的情况更隐蔽常见原因是元素被一个浮层挡住了而这个浮层在无头模式下的行为可能和桌面端有细微差异。处理方式是先等待元素可点击再执行点击必要时用脚本方式触发。更重要的是无头模式下要养成看页面快照的习惯出问题时把当前页面 HTML 打印出来比盯着报错文字猜有用得多。from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC wait WebDriverWait(driver, 15) button wait.until(EC.element_to_be_clickable((By.ID, submit))) button.click()显式等待比固定睡眠可靠得多因为它在条件满足时立刻返回条件不满足时最多等设定的时长。固定睡眠则是要么白等要么不够等两头不讨好。5.3 容器与持续集成环境里的额外事项在容器里跑驱动有几个和普通服务器不同的点。第一个是用户身份容器默认常以 root 运行而 Chrome 在 root 下必须加--no-sandbox否则直接拒绝启动。这个前面提过但值得再强调一次因为它是容器场景最高频的失败原因。第二个是共享内存容器默认的/dev/shm往往只有 64MB页面一复杂就撑爆。除了加--disable-dev-shm-usage也可以在启动容器时用--shm-size1g扩大共享内存后者性能更好但需要改容器启动参数。两种方式可以叠加使用。第三个是时区和字体。如果脚本涉及日期渲染或者截图里要显示文字容器里缺字体会导致显示为方块时区不对会造成页面内容和预期不符。这两个问题排查起来很费时间因为脚本本身不报错只是结果不对所以最好在构建镜像阶段就把字体包和时区配置一次性设好。第四个是资源限制。无头浏览器对内存的消耗比想象中大给容器配上合适的内存上限很重要否则跑到一半被系统杀掉日志里只有一句含糊的退出信息。我一般会给单实例至少 1GB 内存的余量具体看页面复杂度调整。6. 用久了才总结出来的几条经验6.1 版本升级的节奏怎么把握Chrome 的更新频率很高几乎每隔几周就推一个新版本而且默认是静默自动更新。这意味着你的驱动会在某一天突然就不匹配了而你可能完全没改过任何东西。这个特性对本地开发影响不大对线上定时任务影响很大因为你不知道它什么时候会挂。我的做法是两条线分开。线上环境锁定浏览器版本不让它自动升级只在计划好的时间点统一升级升级前先在测试环境验证一遍。本地环境允许自动更新但每次更新后第一件事是核对驱动版本顺手把脚本跑一遍冒烟测试。如果确实没法锁定浏览器的版本那至少要把驱动路径和版本检查写进启动流程。启动时先执行一次版本比对不一致就记录清晰的日志并告警而不是等到执行任务时报一个含糊的错。这点小的前期投入能省掉大量半夜爬起来排查的时间。6.2 目录结构和团队协作的约定单人开发时驱动放哪都行多人协作时就需要约定。我推荐在项目里建一个明确的目录比如tools/chromedriver/按平台分子目录然后在配置文件里按系统类型选择路径。同时把驱动文件加到版本库的忽略列表只提交一个说明文件写明下载来源和版本要求避免几个 G 的二进制文件把仓库撑大。更进一步的做法是写一个初始化脚本新同学拉下代码之后执行一次自动检测系统类型、检查驱动是否存在、版本是否正确、权限是否配好有问题就给出明确提示。这个脚本我一般会用几十行 shell 或者 Python 实现投入不到一小时但能让每个新加入的人少踩一遍所有的坑。提示初始化脚本里不要写死驱动版本号而是读一个配置文件。这样升级驱动时只改一处脚本和文档都能保持一致不会出现文档说 131 而配置文件写 128 的情况。说到底chromedriver 的安装和配置是个典型的一次性投入、长期受益的事。真正花时间的从来不是那几条命令而是遇到报错时的定位过程。把版本对应关系、路径查找逻辑、依赖库清单这三件事搞清楚后面八成的相关报错都能自己解决。我现在遇到新机器配环境基本十分钟内能全部搞定靠的不是记住了多少命令而是知道每个环节出问题时会表现成什么样。
返回列表