简介Selenium 3.141.0是Python生态中主流的浏览器自动化库广泛用于Web自动化测试、爬虫数据采集和页面交互脚本。针对开发者在安装时经常遇到在线安装反复中断、官方源码下载缓慢甚至失败的问题这份离线安装包提供了完整可用的库文件解压后只需在命令行进入目录并执行即可完全离线完成安装。包内一共有104个文件主体为86个Python模块覆盖了WebDriver核心逻辑、页面元素定位、显式等待、动作链等常用功能同时附带txt说明文档、JavaScript脚本、so动态库、json配置文件与版本信息分别用于环境校验、浏览器驱动调用、底层协议支持、默认选项管理及依赖声明整个压缩包仅905KB。无需持续联网的特性使它非常适合内网隔离、网络波动频繁或需要批量部署Selenium的开发和测试场景。截至当前已有2128人学习或下载对自动化测试初学者以及需要快速搭建可复用Selenium环境的工程人员而言是一份可靠的离线安装工具。1. 还在用 Selenium 3.141.0这个版本足够稳也足够用如果你手头的自动化脚本跑一阵就崩换了最新版 Selenium 4 反而连浏览器都起不来那问题多半不在你代码里而在版本组合上。Selenium 3.141.0 是 Python 版 Selenium 里最后一个大版本发布于 2018 年至今仍被大量遗留系统和生产环境使用因为它彻底取代了早期只能调本地浏览器的 Selenium RC全面转向 WebDriver 协议。从实际落地看如果你要做的是登录态保持、翻页抓取、表单提交、批量截图这类常规 Web 自动化3.141.0 足够稳定且很多老项目的踩坑文档、源码包和补丁都基于这个版本。本篇把我拆过的 Selenium 3.141.0 资源包整理成一套能直接照着用的实战笔记从安装流程、驱动匹配、代码骨架到高频报错排查新手能照着走熟手能直接跳到自己关心的坑。2. 版本溯源与选型为什么 3.141.0 适合当生产环境默认项2.1 WebDriver 协议时代的分水岭Selenium 3.141.0 这个名字里的 3 指三代架构141 是发布序列号。它和当前主流的 Selenium 4.x 最大的区别在于3 代基于 W3C WebDriver 标准草案实现4 代则基于正式成为 W3C 标准的 WebDriver 协议。别小看这层差别在实际使用中3.141.0 的 API 稳定度反而更高因为你永远不会遇到 4.x 新版本里偶尔冒出的新特性变动和废弃警告。我拆过不少自动化项目也帮朋友排查过莫名其妙的 Selenium 报错有个高频场景生产环境跑得好好的脚本本地一升级 Selenium 4 就报InvalidArgumentException原因通常是对应的 driver 版本过旧。3.141.0 的资源包和各类云打码、验证码识别库的兼容性反而经过了多年验证很多第三方登录辅助工具至今内部还是固定引入这个版本。如果你在一家对升级进度有严格审批流的公司或是在维护一个老项目直接把 Selenium 3.141.0 设为默认项是合理的。它能解决的问题包括自动填写和提交表单、抓取动态加载数据、定时巡检页面状态。需要注意的是它不支持 CDP 协议特性也就是无法调用 Chrome DevTools 里的性能日志、网络拦截、移动端模拟等高级功能这不属于这个版本的能力边界需要时建议混用 pyppeteer 或单独写 puppeteer 服务去补齐。2.2 资源包构成与安装方式这个资源包是一个 zip 压缩包解压后里面有 selenium 目录、setup.py、PKG-INFO 等标准 Python 包文件。它是从 PyPI 上拉取的发行包不是从 GitHub 源码仓库直接 clone 的版本区别在于发行包附带了构建好的模块和元数据能直接pip install或python setup.py install源码仓库还需要你先处理依赖。常见做法是用 pip 安装离线环境建议在当前目录下建一个 local 目录把 zip 包放进去再安装。我的习惯是先在虚拟环境里装避免污染系统 Python特别是这台机器同时还有多个项目依赖不同版本 Selenium 的情况。# 解压 selenium-3.141.0.zip 到本地目录 unzip selenium-3.141.0.zip -d ./selenium_pkg # 进入解压目录后执行安装 cd selenium_pkg python -m pip install . # 验证安装结果 python -c from selenium import webdriver; print(webdriver.__version__)这段流程里python -m pip install .会读取 setup.py 里的依赖声明自动安装 selenium 运行所需的缺省依赖。最后一步用于确认版本号正确输出为 3.141.0如果输出的是其他版本号说明环境里存在多个 Selenium 实例需要显式指定路径或先隔离虚拟环境。2.3 浏览器驱动最容易翻车的配套组件很多新手拿到资源包装好后第一步写webdriver.Chrome()就直接报WebDriverException原因是 ChromeDriver 版本和本机 Chrome 浏览器版本不匹配。Selenium 本身只是一个操作抽象层真正的驾驶动作由各浏览器的 driver 执行Chrome 用 chromedriverFirefox 用 geckodriverEdge 用 msedgedriver。3.141.0 在启动时通过环境变量或直接路径找到对应 driver 的二进制文件。我的建议是先确认浏览器版本号。打开 Chrome 的关于页面记下大版本号然后去对应的 driver 版本库下载大版本一致的二进制。以常见情况举例本机 Chrome 是 115 大版本那就必须用 chromedriver 115.x 的二进制而不是最新 119 版本否则浏览器启动时会校验版本不匹配直接拒绝连接。# Linux/macOS 下常见启动方式 export PATH$PATH:/usr/local/bin/ # 或在 Python 里直接指定 driver 路径 webdriver.Chrome(executable_path/usr/local/bin/chromedriver)executable_path参数在 3.141.0 中是显式传递的等到了 Selenium 4.x 这个参数被服务类接口取代了。这也是很多旧代码在 4.x 上跑不起来的原因之一。如果你拆过别人维护的老资源大概率见过这行代码它本身没问题只是别把它当成 4.x 也能跑的通用写法就完了。3. 资源和功能拆解手把手把 Selenium 3.141.0 用起来3.1 从打开页面起步最简代码骨架装好包、驱动版本匹配后最值得先写通的脚本就是启动浏览器并打开一个页面。这个脚本越简单越好因为它验证的是环境层面的连通性Selenium 能不能找到 driver、driver 能不能拉起浏览器、浏览器能不能正常加载页面。这三层只要有一环断掉后面写再多业务代码都是白费。from selenium import webdriver import time driver webdriver.Chrome(executable_path/usr/local/bin/chromedriver) driver.get(https://example.com) time.sleep(3) print(driver.title) driver.quit()这段代码里的driver.quit()是关键一行它负责关闭浏览器和清理驱动进程。很多新手只写driver.close()只关当前标签页后台的 chromedriver 进程还在久而久之就积累出一堆僵死进程导致资源耗尽。time.sleep(3)只是让页面有时间加载完在正式场景里不建议用固定等待后面会专门讲显式等待的写法。如果首行就报各种异常先别怀疑代码回去查看 driver 二进制权限、Python 环境是否混装。3.2 定位元素find_element 家族的取舍逻辑页面打开后自动化脚本的核心工作变成定位页面上的元素并与之交互。Selenium 3.141.0 提供find_element_by_id、find_element_by_class_name、find_element_by_xpath等一组方法。这些方法在 4.x 里被统一收编成find_element(By.ID, xxx)但在 3 代里它们各自独立缺什么导入什么。实际拆资源时发现多数项目里 XPath 的使用频率最高因为很多目标页面的按钮和输入框没有可靠的 id 或 name 属性。XPath 的好处是可以按文本、属性层级组合定位缺点是写得太长太绝对时页面只要有一点结构变化就崩。我的原则是优先 id其次 name再其次 XPath 的相对路径绝不用绝对路径。# 通过 id 定位用户名输入框 driver.find_element_by_id(username) # 通过 XPath 相对路径定位搜索按钮 driver.find_element_by_xpath(//button[contains(text(), 搜索)]) # 通过 class 名定位一组商品卡片 items driver.find_elements_by_class_name(product-item)第三行的find_elements_by_class_name注意是复数形式返回的是一个元素列表常用于列表页抓取。这里的逻辑是定位范围先锁定到某一个容器元素再到其内部二次查找能显著减少外部页面对 XPath 的耦合核心是把动态区域的定位路径尽量缩短。3.3 模拟输入与点击从登陆到翻页的完整小任务配上一个需要登录态的场景某后台管理页面需要先输入账号密码然后进入列表页逐页抓取数据。这是 Selenium 最常见的落地场景核心动作就是 send_keys 输入、click 点击、等待加载。拆资源时我把这套流程拆成下面这个骨架能跑通基本就掌握了 90% 的常见操作。from selenium import webdriver from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By driver webdriver.Chrome(executable_path/usr/local/bin/chromedriver) driver.get(https://example.com/login) # 定位输入框并输入 driver.find_element_by_id(username).send_keys(your_account) driver.find_element_by_id(password).send_keys(your_password) # 点击登录按钮 driver.find_element_by_id(login_btn).click() # 等待列表页加载出第一条数据 wait WebDriverWait(driver, 10) wait.until(EC.presence_of_element_located((By.CLASS_NAME, product-item))) # 循环翻页抓取 for page in range(1, 5): items driver.find_elements_by_class_name(product-item) for item in items: print(item.text) driver.find_element_by_xpath(//a[contains(text(), 下一页)]).click() time.sleep(2) driver.quit()这段代码里有个必须解释清楚的点WebDriverWait(driver, 10)表示最长等待 10 秒直到某个条件满足。用EC.presence_of_element_located来等列表数据出现比time.sleep高明的地方在于它不会在页面加载快时白白多等也不会在加载慢时因为时间不够而误报失败。熟练以后可以把每一页的等待都替换成等待下一页按钮可点击而不是固定 sleep。这个资源包里的源码骨架通常不会写这么细的等待逻辑需要自己补上否则在稍慢的网络环境里会抓不全数据。3.4 资源包内含的模块边界这个 Selenium 3.141.0 资源包的内部结构里主要包含这些核心子模块common用来管理异常类和键值常量webdriver提供各种浏览器驱动类以及支持显式等待与动作链的模块selenium.webdriver.support里有expected_conditions和wait。如果你想做鼠标悬浮、拖拽、双击这类复杂操作还需要引入ActionChains。这里给出一份模块对应功能的速查表。子模块常用类/方法典型用途webdriverChrome, Firefox, Edge创建浏览器实例并控制页面webdriver.common.byBy.ID, By.XPATH定位策略的统一定义webdriver.support.uiWebDriverWait显式等待机制webdriver.supportexpected_conditions页面元素状态轮询判断webdriver.common.action_chainsActionChains鼠标悬浮、拖拽、键盘组合键common.exceptionsTimeoutException, NoSuchElementException按异常类型定向排查问题不推荐用driver.execute_script去操作 DOM 来替代常规输入点击它虽然能绕过一些点击被拦截的问题但会破坏你脚本对页面真实行为的模拟程度很多反爬逻辑正是通过检测自动化操作的特征来拦截的。3.141.0 没有内置的反检测伪装想藏自动化特征得配合第三方 js 注入资源包里不会主动提供。4. 一个可复用的抓取任务登录后翻页采集 统一文件输出4.1 设置合理等待与标签页控制策略把上一章那个骨架继续深化做成一个更接近生产环境的小任务。关键点是显式等待的本地化写法、翻页期间对元素状态的判断、以及最终的统一输出方式。我在拆这个资源包时把项目里常用的采集逻辑抽成了一组固定套路任何静态列表页都能套用。这个套路不是靠一条超长的 XPath 硬抓而是利用好几次轮询等待来降低失败率。import time import csv from selenium import webdriver from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By options webdriver.ChromeOptions() options.add_argument(--headless) # 无头模式不弹出浏览器窗口 driver webdriver.Chrome(executable_path/usr/local/bin/chromedriver, optionsoptions) driver.get(https://example.com/login) driver.find_element_by_id(username).send_keys(your_account) driver.find_element_by_id(password).send_keys(your_password) driver.find_element_by_id(login_btn).click() # 等待列表页第一条数据出现 WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.CSS_SELECTOR, div.product-item)) ) rows [] for page in range(1, 6): items driver.find_elements_by_css_selector(div.product-item) for item in items: title item.find_element_by_css_selector(.title).text price item.find_element_by_css_selector(.price).text rows.append([title, price]) next_btn driver.find_element_by_xpath(//a[contains(text(),下一页)]) if next_btn.is_enabled(): next_btn.click() WebDriverWait(driver, 10).until(EC.staleness_of(items[0])) # 等待旧元素失效代表新页已加载 else: break with open(output.csv, w, newline, encodingutf-8) as f: writer csv.writer(f) writer.writerow([标题, 价格]) writer.writerows(rows) driver.quit()这里的参数值得逐个说明。--headless模式适合部署在服务器上看不到浏览器界面但能正常执行所有 DOM 操作缺点是部分网站对无头模式特征有检测如果被拦就改用有头模式加 Xvfb。EC.staleness_of(items[0])的用法是代码里的一个小窍门当新页面内容渲染出来后旧列表中第一个元素就被移除失效这个等待条件触发比强制 sleep 更准确地判断翻页完成。注意这里等的是旧元素失效不是新元素出现因为新页面可能结构和旧页一样presence 检测立刻会返回 True反而绕过了实际翻页等待。4.2 数据落地时的编码与字段细节把抓取结果写入 CSV 时newline是必须写的否则在 Windows 环境下每行之间会多出空行。encodingutf-8可以保证中文不乱码但如果你后续要用 Excel 直接打开 CSV建议改成utf-8-sigExcel 对无 BOM 的 UTF-8 可能显示成乱码。这在资源包的说明文档里一般不会提到属于我自己反复被坑后沉淀下来的经验。如果抓的是金额字段强烈建议在写入前清洗一次。比如价格里可能带¥符号、逗号分隔符、或者起这种文字清洗逻辑是统一的去掉货币符号去掉千分位逗号把万换算成纯数字最后转成浮点数。这个处理写进同一个脚本里比较简单。import re def clean_price(raw): if not raw: return 0.0 text raw.replace(¥, ).replace(,, ).replace(起, ).strip() if 万 in text: return float(text.replace(万, )) * 10000 return float(text)clean_price函数在写库前调用比事后用 Excel 调整要省事得多。如果你抓的数据还有时间字段顺手统一成%Y-%m-%d %H:%M:%S格式后面做时序分析或入库时不用二次处理。很多资源里给的脚本走的是粗糙的text直存路线真实项目里去重、清洗、格式化这几步是必须的否则数据质量根本经不起下游使用。4.3 断点续抓与日志输出抓取几百条数据的简单任务不涉及断点续抓但只要你把页数放大到几十页中途任何网络抖动都可能让脚本崩掉。资源包里的代码骨架通常不处理这种异常我一般会加一层try...except包住翻页循环记录当前已经完成的页码到本地日志下次从断点继续。这个逻辑不难但能显著提升长时间任务的容错率。import json import os log_file scraper_progress.json start_page 1 if os.path.exists(log_file): with open(log_file, r, encodingutf-8) as f: start_page json.load(f).get(last_page, 1) 1 for page in range(start_page, 6): try: # 翻页抓取逻辑同上面一致 pass except Exception as e: print(f第 {page} 页出错: {e}) break else: with open(log_file, w, encodingutf-8) as f: json.dump({last_page: page}, f)这里的关键设计是start_page从上次成功抓取的页码加 1 开始避免重复写入数据。else块挂在try...except后面只有循环体完全无异常时才执行记录页码。如果你的目标网站对访问频率有要求可以在每页之间加一个随机延时比如time.sleep(random.uniform(1, 3))能降低被封的风险。5. 避坑与常见问题拆了不下十个自动化项目才总结出来的细节这一章我把从资源包里读到的、以及实际运行中最常踩的坑集中列出来每条按现象到原因到解决展开建议照着预演一遍能少走很多弯路。5.1 SessionNotCreatedException浏览器驱动版本不匹配现象调用webdriver.Chrome()时直接抛SessionNotCreatedException: This version of ChromeDriver only supports Chrome version XX。偶尔是 Firefox 配 geckodriver 时报Expected browser binary location。原因chromedriver 和 Chrome 浏览器有严格的版本对应关系大版本号必须一致小版本可以向下兼容。chromedriver 最新版不支持几十个版本之前的老浏览器同样老版本的 chromedriver 也拉不起新版 Chrome。另一类原因是 Firefox 时安装路径没被 driver 找到。解决确认浏览器大版本号后去 chromedriver 版本目录下载对应大版本的 binary替换当前路径里的旧文件。替换后建议在命令行直接执行chromedriver --version确认版本号已经更新再跑 Python 脚本。如果是 Firefox用webdriver.Firefox(executable_path/path/to/geckodriver)显式指定路径并确认浏览器安装在标准路径。5.2 NoSuchElementException元素定位不到但页面明显存在现象find_element_by_xpath报NoSuchElementException但手动打开浏览器能看到目标元素。用 Selenium 自带的截图功能把当前页面存下来发现根本没加载到那个区域。原因最常见的是异步渲染。页面框架先加载完数据接口还在请求中目标元素是数据返回后才插入的。如果你上来就 find_element自然找不到。其次可能是元素在 iframe 内需要先切换 frame 才能定位这个问题隐蔽性更强。解决在定位前加上显式等待等到元素出现再操作建议写成一个小函数wait_for_element(driver, by, value, timeout10)内部调用WebDriverWait并捕获TimeoutException给出中文提示。如果是 iframe 内元素用driver.switch_to.frame(0)操作完再driver.switch_to.default_content()切回主文档。资源包里的旧代码大多没处理这两个点升级稳定性的第一步就是给所有关键定位点加上等待。5.3 TimeoutException: Message: timeout 出现在页面加载阶段现象脚本跑到一半卡住直到超时抛异常偶尔整个浏览器白屏不动。原因部分页面存在长期挂起的连接或是driver.get()等待load事件触发完毕但页面里的统计脚本或广告脚本迟迟不返回。这个问题的根源不在 Selenium 本身而是页面设计问题。解决给driver.get()加一个上限控制把页面加载策略改成不等待完整加载capabilities webdriver.DesiredCapabilities.CHROME.copy() capabilities[pageLoadStrategy] eager driver webdriver.Chrome(executable_path/usr/local/bin/chromedriver, desired_capabilitiescapabilities)pageLoadStrategy设为eager表示 DOM 已变为可交互就返回不等待依赖资源全部加载完适合绝大多数页面。要留意的是用了这个策略后部分元素因为对应的异步资源没加载完而暂时不存在后续操作前仍需显式等待。如果改成none策略则完全不受加载过程影响但你需要自己管理所有等待逻辑新手不建议用。5.4 页面报错元素可点击但实际点击无效或跳转不对现象脚本不报错click()成功执行但页面上没有发生预期变化比如点登录后没有任何反应或跳到错误地址。原因目标元素被其他元素覆盖或点击的其实是隐藏的透明元素。另一种原因是前端框架用了事件委托点击坐标或触发方式不符合预期。还有一种是按钮处于 disabled 状态click()调用不会产生任何效果。解决先检查按钮状态用is_enabled()输出判断。再检查是否有遮挡层常见的是弹窗、遮罩层盖住按钮用ActionChains先移动鼠标到元素再点击可以绕开部分问题。终极方案是用driver.execute_script(arguments[0].click();, element)强制触发点击事件但这个方案会绕过真实用户操作路径不适合需要模拟真实交互的场景。如果点击后没有反应优先查看控制台报错和网络请求结果判断是前端逻辑阻止了事件还是接口失败导致页面无响应。5.5 ActionChains 拖拽无效浏览器缩放比例影响坐标计算现象用ActionChains做拖拽时元素被拖动到错误位置或者完全没有被拖起来。原因浏览器页面缩放比例不是 100% 时元素坐标换算发生偏差导致拖拽的目标点错位。这在高分屏且设置了缩放的系统上非常常见。解决先把页面缩放到 100%driver.execute_script(document.body.style.zoom100%)再执行拖拽。另一种做法是直接用真实位移数值而不是找目标元素位置因为目标元素位置也受缩放影响。另外HTML5 的拖拽接口某些情况下 Selenium 无法模拟这时候需要手写 JavaScript 的DragEvent来触发属于更深层的 hack 操作生产环境里尽量别依赖它优先换一种交互方案。6. 再进一步如何把静态脚本升级成可持续维护的自动化任务当你已经跑通了登录、翻页、抓取这一套链路下一步需要考虑的不是多抓几个页面而是让这套脚本能在无人值守的情况下持续运行并且出了问题能快速定位。这一章我把几个实用技巧集中起来它们是从这个资源包往生产环境过渡的跳板。6.1 用 fixture 和配置文件替代硬编码资源包里给的脚本通常是账号密码直接写在代码里。这个毛病在个人项目里还好一旦脚本要交给别人运行或者放进 CI 环境麻烦就大了。我一般会建一个 config.py把账号、密码、目标 URL、等待时长、输出路径全部抽出来配置化。# config.py ACCOUNT your_account PASSWORD your_password BASE_URL https://example.com/login WAIT_TIMEOUT 10 OUTPUT_PATH ./output.csv HEADLESS True主脚本里全部通过import config引用不出现任何硬编码值。好处是换环境时只需改配置不用把代码翻一遍找字符串。特别是WAIT_TIMEOUT这种参数每个网站的加载速度不一样运行环境不同往往要现场调放在配置里直接改比改代码风险小得多。6.2 事件监听器和页面对象模式怎么配合Selenium 3.141.0 提供了AbstractEventListener接口和EventFiringWebDriver包装类可以在元素点击前记录日志。这个能力在调试脚本时非常有用因为一旦脚本跑挂你需要知道挂在前一个动作还是后一个动作。把这个和页面对象模式结合起来可以做出接近自动化测试框架的效果。from selenium.webdriver.support.events import EventFiringWebDriver, AbstractEventListener class MyListener(AbstractEventListener): def before_click(self, element, driver): print(f即将点击元素: {element.tag_name}) def after_navigate_to(self, url, driver): print(f已完成页面跳转: {url}) base_driver webdriver.Chrome(executable_path/usr/local/bin/chromedriver) driver EventFiringWebDriver(base_driver, MyListener())EventFiringWebDriver是包装类传入原始 driver 和监听器实例之后所有操作都会触发对应的回调方法。这个打印日志的能力在你调整等待策略时特别有效看到before_click输出了但页面没反应就能判断是点击本身被拦了如果某个元素没打印点击日志说明前面的等待或定位就出了问题。配合 logging 模块输出到文件跑挂了看日志就能恢复现场。6.3 定时执行与异常自愈脚本稳定之后可以做成定时任务。Linux 下用 cron 比较简单Windows 下可以用计划任务。输出的日志要按日期分文件否则连续跑一个月后单个日志文件会膨胀到几 GB。另一个自愈技巧是在脚本入口处写一个简单重试机制如果整个流程抛出未捕获异常间隔 5 分钟后重启任务最多重试 3 次。import time def run(): # 主流程代码 pass for attempt in range(1, 4): try: run() break except Exception as e: print(f第 {attempt} 次执行失败: {e}) if attempt 3: raise time.sleep(300)这个循环的意图是一次任务里有多次重试机会而不是崩了就彻底停摆。实际用下来绝大多数临时网络抖动和页面偶发加载失败都能靠这一层扛过去。真正的后端接口挂了或页面结构改了三次重试也会失败这时再排查就行不至于半夜收到告警还得爬起来改代码。从拆这个资源包到落地多个项目我最大的教训是永远不要在没加显式等待的情况下跑超过一页的抓取任务。看着像节省了时间实际上后面排查翻车原因的时间是省下时间的十倍。从那以后我每次搭脚本骨架第一件事就是把等待模块和异常捕获按上面这套固定套路摆好再写业务逻辑。希望这篇笔记能帮你在 Selenium 3.141.0 上少踩几个坑把自动化任务稳稳跑起来。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?