☰
Selenium自动化测试实战:从安装驱动到滚动与反爬
2026/10/11 15:47:58 网站建设 项目流程

1. 为什么自动化测试绕不开Selenium?

1.1 一个“老”框架为什么到现在还在大量使用

我入行那会儿,自动化测试圈子里最响的名字就是Selenium。十几年过去,Playwright、Cypress这些新工具一个接一个冒出来,但打开招聘软件看测试开发岗的要求,Selenium依然出现在绝大多数JD里。这不是因为大家都恋旧,而是Selenium解决的问题足够基础、覆盖面足够广:跨浏览器兼容测试、回归测试、页面数据采集、甚至办公软件的网页操作自动化,它都能干。

我在多个项目里同时用过Selenium和Playwright,一个很直观的感受是:小步快跑的新项目,Playwright确实给人惊喜;但凡是接手存量系统、要兼容老版本Chrome、或者团队里只有Java/Python基础没有Node经验,Selenium反而是最不容易翻车的选择。它的API设计很直白,踩坑资料又多,出了任何报错几乎都能在社区里搜到前人的解决方案。这种积累不是新工具短时间能追上的。

再说职场价值。懂Selenium不只是会调用几个API,而是理解浏览器自动化背后的那一整套机制:页面生命周期、元素渲染时机、DOM变化、浏览器驱动和协议通信方式。这些底层认知是通用的,哪怕哪一天你切换到其他自动化工具,底层思路也一样能迁移。所以我的建议很明确:不管你用不用它做主力工具,Selenium的基本功必须扎实,尤其是它背后的“等待策略”和“元素定位”思维,值得反复咀嚼。

1.2 它能做什么,不能做什么

先说能做的:打开网页、点击按钮、输入表单、提交数据、拖拽元素、切换iframe、处理多窗口、执行JavaScript、截屏、操作Cookie、模拟键盘事件、无头模式跑批任务,这些都属于常规操作。配合WebDriverWait,还能精确控制脚本等到某个元素出现、消失或可点击,比无脑time.sleep()高出一个段位。

不能做什么,也要心里有数。Selenium本身不是万能爬虫工具,它不擅长处理复杂的图片验证码;如果不做任何特征屏蔽,它也比较容易被网站识别出自动化身份。很多人以为Selenium装上就能绕过一切反爬,这是个误解。它更准确的定位是“浏览器自动化操作框架”,而不是“反爬对抗武器”。另外,某些需要操作系统级交互的功能,比如原生文件上传弹窗,Selenium处理起来就比较笨重,通常要用AutoIt或者其他系统自动化工具配合。

所以什么人适合看这篇文章?面向测试工程师、爬虫工程师、运维工程师,以及想在Excel和网页之间打通自动化的办公人员。无论你是刚准备入行的新人,还是写了两年代码想补细节的初级开发,下面这些经验应该都能帮你省掉不少试错时间。

2. Selenium安装与驱动匹配:环境搭不好,后面全白干

2.1 安装包版本怎么选

很多人一上来就pip install selenium,装完写脚本就报错,然后一脸懵。这里有个最容易忽略的点:Selenium版本和Python版本、浏览器驱动版本之间是有对应关系的。现在主流是Selenium 4.x,安装命令还是那条:

pip install selenium

但装完之后,我建议立刻检查一下版本号:

import selenium print(selenium.__version__)

为什么强调版本?因为Selenium 4改动很大,API和3.x不兼容。比如3.x常用的driver.find_element_by_id("xxx"),在4.x里已经被废弃,必须写成driver.find_element(By.ID, "xxx")。网上很多教程还是老写法,如果你装的是新库,跑起来就会看到DeprecationWarning甚至直接报错。我自己带过的新人里,十个有八个踩过这个坑。

关于版本取向,我的建议是不要追最新。Selenium 4刚出来的时候,我试过追新版本,结果和当时的ChromeDriver配合出了一堆幺蛾子。后来养成的习惯是:优先用各分支的正式发布版本,等社区把坑填得差不多了再考虑升级。Python环境建议用虚拟环境管理,别把全局环境搞得乌烟瘴气,尤其是一个机器上同时有多个项目的场景。

2.2 浏览器驱动:版本匹配是最大的坎

Selenium本身不直接控制浏览器,它需要通过一个中间程序——ChromeDriver、GeckoDriver或EdgeDriver——和浏览器通信。这个驱动必须和你本机安装的浏览器主版本匹配,否则连接时会直接抛SessionNotCreatedException。

我常用Chrome,所以拿它举例子。首先在浏览器地址栏输入chrome://version/,看到版本号,比如120.0.6099.130。然后去ChromeDriver的下载页找对应的版本号。这里有个细节:驱动版本不需要精确到最后三位,前两段“主版本+次版本”一致基本就能用。比如浏览器是120.0.6099.130,下载120.0.6099.x系列的驱动即可。

驱动下载后,可以把它放到系统PATH目录,也可以直接在脚本里指定路径。Selenium 4推荐的写法是这样的:

from selenium import webdriver from selenium.webdriver.chrome.service import Service options = webdriver.ChromeOptions() options.add_argument('--headless') # 无头模式,不弹出浏览器窗口 service = Service(executable_path='/your/path/to/chromedriver') driver = webdriver.Chrome(service=service, options=options) driver.get('https://example.com') print(driver.title) driver.quit()

另外,Selenium 4内置了Selenium Manager,会自动尝试下载匹配的驱动。这个功能在断网环境或者受限网络下不一定好使,所以我还是建议手动下载并管理驱动路径,至少知道你跑起来的那一瞬间用的是哪个驱动。

2.3 第一次运行前的检查清单

我每次在新环境里搭Selenium,都会花两分钟过一遍清单,省得被报错追着跑:

  • 确认Python是64位还是32位,驱动和架构要匹配。
  • 确认浏览器没开着多个测试实例,有时残留进程会占用端口。
  • 写一个最简单的打开页面脚本先跑通,再往上加业务逻辑。
  • 如果项目里有多个人的环境,把驱动版本要求写进README,别让下一个接手的人猜。

这套流程看着简单,但能筛掉一大半环境问题。真正写业务逻辑之前,先确保“能打开浏览器、能打开页面、能正常退出”这三件事。

3. 从定位元素到模拟操作:核心细节解析

3.1 八种定位方式,优先用哪个

Selenium里定位元素的方式有很多,但实际工作中我几乎只用三种:ID、CSS Selector、XPath。理由是ID最快最稳定,CSS Selector简洁易读,XPath灵活但性能稍差,适合处理复杂结构。其他几种,比如ClassName、Name、TagName,不是不能用,而是在现代前端框架下太容易撞车,定位到的元素经常不是你想的那个。

举个例子,很多前端框架的动态列表里,class是重复的,用ClassName定位会拿到一坨元素,还要自己遍历过滤。这时候用CSS Selector或者XPath指定索引会更可控。不过XPath也不能一上来就写绝对路径,那种/html/body/div[3]/div[2]/form/input的写法,前端稍微加一层div就全废了。我自己写XPath的习惯是:从目标元素的属性、文本内容、层级关系里挑一到两个稳定特征,写成相对定位。比如:

from selenium.webdriver.common.by import By username = driver.find_element(By.ID, "username") submit_button = driver.find_element(By.CSS_SELECTOR, ".login-form .submit-btn") item = driver.find_element(By.XPATH, "//div[@class='item' and contains(text(),'目标内容')]")

这里有个容易忽略的点:定位元素不是一次性的事,页面动态刷新后,之前拿到的元素引用会失效。所以写长脚本时,不要养成“拿到元素就到处复用”的习惯,必要的时候重新查一遍。

3.2 等待策略:别再用sleep硬扛

新手最爱写time.sleep(3),我当年也这么干过。问题是网络一慢,3秒不够;网络快的时候,3秒又白白浪费时间。而且这种固定等待根本解决不了元素延迟渲染的问题,只会让脚本变得又慢又脆。

真正靠谱的是显式等待。Selenium里有个WebDriverWait,配合expected_conditions,可以等到某个条件成立再继续。这个机制不管页面加载快慢,都能自适应。我常用的等待条件有:

  • element_to_be_clickable:等待元素可点击,适合按钮。
  • visibility_of_element_located:等待元素可见,适合动态加载的内容。
  • presence_of_element_located:等待元素出现在DOM中,适合判断元素是否已经生成。
  • staleness_of:等待某个元素从DOM中消失,常用来判断页面是否刷新完成。

代码大概长这样:

from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By wait = WebDriverWait(driver, 10) button = wait.until(EC.element_to_be_clickable((By.ID, "submit-btn"))) button.click()

这里(By.ID, "submit-btn")是一个元组,注意别漏括号。有个隐藏好处:显式等待的报错信息会告诉你超时等待的是哪个条件,排查问题比time.sleep过后直接NoSuchElementException清晰得多。不要迷信一种等待策略,老项目里经常是隐式等待和显式等待混用的,但要清楚它们的机制,避免出现等待时间叠加的问题。

3.3 表单、提交、拖拽与截图操作

定位到元素之后,真正模拟用户操作也有不少坑。输入框用send_keys(),这个都知道。但有几类操作值得单独记一下:

下拉框,尤其是原生select标签,可以用Select类处理。新版前端框架里很多下拉框是自定义组件,实际上是一堆div,那用Select就没用,得先点击展开,再点击选项列表里的某个元素。判断方法很简单:看页面源码里有没有<select>标签。

拖拽用ActionChains。比如列表排序、地图标记、滑块交互等操作,可以通过click_and_hold()、move_by_offset()、release()这些方法组合实现。注意,拖拽之前元素必须已经进入可视区域,否则坐标不对,很容易拖到别的地方。

文件上传是另一个经典坑。如果页面里的input标签是type=file,那直接用send_keys("本地文件路径")就能搞定,不需要真的去操作系统弹窗。但如果页面自己封装了上传组件,点击后引入系统文件选择器,Selenium就控制不了了,这个时候常见做法是绕过前端校验,直接把文件路径塞给隐藏的input元素,或者借助第三方工具。

截图操作我也用得很多。driver.save_screenshot("screen.png")只能截当前窗口,想要整页截图,很多浏览器驱动已经支持了,但不同浏览器表现不一。我一般更倾向于自己控制滚动、分段截图,再拼起来,这样能保证关键区域不遗漏。

3.4 网页左右滑动:从横向滚动条到元素可见

热搜里“selenium 网页左右滑动”和“selenium 左右滚动可见”在测试场景里特别常见。多数人的第一反应是滑动垂直滚动条,实际上横向滚动同样是个高频需求。典型场景包括:宽表数据表格、轮播图、横向布局的图表区域、类似股票K线的横向时间轴。

Selenium里没有专门的“向右滑动”方法,得通过JavaScript或者键盘事件来实现。几种常用手段:

  • 滚动整个窗口:driver.execute_script("window.scrollTo(document.body.scrollWidth, 0);")
  • 滚动某个容器:先定位到那个容器,再设置scrollLeft。例如:
table = driver.find_element(By.CSS_SELECTOR, ".data-table-wrapper") driver.execute_script("arguments[0].scrollLeft = 800;", table)
  • 让某个元素横向进入可视区域:driver.execute_script("arguments[0].scrollIntoView({inline: 'center'});", element)
  • 键盘左右箭头:如果元素是焦点型容器,也可以用element.send_keys(Keys.RIGHT)来触发横向滚动。

很多朋友说“横向滚动不见效”,绝大多数原因是容器选择错了。页面上可能外层有窗口滚动条,内层有div滚动条,你得先看清楚真正能滚动的容器是哪一个。开发工具里用Elements面板找到有overflow-x: auto或overflow-x: scroll样式的节点,那个才是真正需要控制的容器。判断元素是否真正可见,除了element.is_displayed()之外,还要考虑它在滚动容器的可视范围内,这通常要结合坐标来计算,或者用scrollIntoView强行把它捞出来。

左右滑动还牵涉到懒加载。横向长图、展示型列表,很多时候首屏只渲染一部分,滚动之后才加载更多。这就是为什么滑动之后要立刻配合显式等待,等目标元素出现,再继续下一步操作。如果你发现滑动后元素还是找不到,多半是内容还没加载完,硬等几秒没有逻辑可循,不如用WebDriverWait盯着页面变化。

4. Python + Selenium:当自动化测试遇到反爬虫

4.1 反爬到底在检测什么

“Python selenium反爬虫”这个话题,是测试岗位面试里经常被问到的。要弄清楚怎么应对,先得知道对方在检测什么。我拆过一些常见的检测逻辑,大致分这么几类:

  • 请求特征:User-Agent、Accept-Language、Accept-Encoding是否像真实浏览器。
  • JavaScript环境特征:浏览器驱动会注入一些自动化标记,比如navigator.webdriver的值在自动化环境里通常是true。
  • 浏览器指纹:Canvas指纹、WebGL参数、字体列表、插件列表,这些略微有差异就可能被标记。
  • 行为特征:访问速度、鼠标轨迹、点击频率、页面停留时间,短时间密集操作很容易暴露。
  • CDP特征:通过Chrome DevTools Protocol连接时,某些调用痕迹可以被检测到。

我这里说“检测”,不是说所有自动访问都违法,而是很多网站为了防刷会做风控。作为测试人员,我们经常需要模拟真实用户访问自己的系统,或者验证第三方页面是否有反爬机制,掌握这些检测点是有实际用处的。但必须要强调:这些方法只用于你有权测试的目标,不要拿去绕别人的登录授权,更不要用来爬隐私数据。

4.2 降低自动化特征:代码层能做什么

如果你只是在内部测试系统上跑脚本,但系统恰好加载了一套线上的风控SDK,那就要适当降低自动化特征,否则连登录都会被拦。最常见的三个操作:

第一,修改User-Agent。真实浏览器的UA和默认Selenium的UA有明显区别,可以先从请求头开始:

options = webdriver.ChromeOptions() options.add_argument("--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ...")

第二,去掉自动化控制标记。Chrome浏览器启动时如果带--enable-automation开关,会有明显特征。可以用排除开关的方式处理:

options.add_experimental_option("excludeSwitches", ["enable-automation"]) options.add_experimental_option("useAutomationExtension", False) options.add_argument("--disable-blink-features=AutomationControlled")

第三,在页面加载前覆盖navigator.webdriver属性。Selenium 4里可以用CDP注入脚本:

driver = webdriver.Chrome(service=service, options=options) driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", { "source": """ Object.defineProperty(navigator, 'webdriver', { get: () => undefined }); """ })

这个脚本会在每次页面加载新文档之前执行,所以它比页面里的检测代码更早生效,这是我测试下来比较有效的一招。但要注意,CDP是Chrome系浏览器特有的能力,换成Firefox就用不了。

4.3 行为层面的模拟:别把自己当成永动机

光是特征屏蔽还不够,很多风控看的是行为。如果脚本每一秒执行一次点击、每次点击间隔完全一致、页面停留时间都是精确的2.5秒,那就算webdriver属性被覆盖了,也很容易被行为序列分析出来。

我的做法是引入随机化。不是伪随机糊弄人,而是让操作间隔更像真人。比如登录之后,先滚两下页面,再挪动鼠标,再点击,每一步之间加一个随机延迟。Python里可以这样:

import random import time def human_delay(): time.sleep(random.uniform(0.5, 1.5))

注意,随机延迟不是万能的,只是避免固定节奏。更关键的是要把操作顺序做得像人类:先看页面,再滚动,再点击目标按钮,而不是一上来就直奔按钮。这些细节在内部测试时看不出来,但在风控严格的页面上差别很大。

4.4 合规边界:技术能力要用在正道上

说到反爬,必须把边界讲清楚。我见过有些人学了几个技巧就跑去爬别人的订单数据、用户信息,最后惹上法律风险,完全没有必要。自动化技术的正确用法是:对自己负责的系统做压力测试,对公开页面做有限的信息采集,对测试环境做完整的流程模拟。在动手前,要看清楚目标网站的Robots协议和服务条款,必要时先取得授权。

这一点不只是法律问题,也是职业素养问题。面试的时候,面试官也喜欢问“你怎么看待爬虫和反爬”。我的回答角度一直是:我会从测试、风控、数据采集三个维度去拆解,但核心前提是合规。这个前提想不清楚,技术越好越危险。

5. Selenium VBA:Excel老用户也能玩转网页自动化

5.1 为什么要在VBA里用Selenium

很多公司内部办公系统还是老旧网页,业务人员每天要手动把网页上的数据复制到Excel里,或者把Excel里的内容填到网页表单里。纯VBA用InternetExplorer对象操作浏览器,老式网站还能应付,但遇到现代网页就经常失败。这时候“selenium vba”就成了个实用的关键词——VBA里调用Selenium,就能复用Chrome等现代浏览器来跑自动化。

SeleniumBasic是一个把Selenium封装成COM组件的库,安装之后VBA里可以像操作普通对象一样控制浏览器。典型的代码长这样:

Dim driver As New Selenium.ChromeDriver driver.Get "https://example.com" driver.FindElementById("username").SendKeys "testuser" driver.FindElementById("login-btn").Click driver.Quit

当然,具体API要根据SeleniumBasic的版本来调整。早期版本对Chrome支持不稳定,后来新增了ChromeDriver支持后才好用起来。如果有条件,更推荐的方式是:把复杂的网页操作逻辑用Python写好,VBA只负责调用Python脚本并等待返回值。这样既保有VBA在Excel里的灵活性,又不用在COM组件和JavaScript之间来回折腾。

5.2 典型办公场景:Excel和网页之间的数据搬运

我做过的实际案例是:财务同事每天要从内部系统导出一张对账单,但系统不支持直接导出Excel,只能页面展示。之前他们的方案是复制粘贴,一天两小时。后来我用SeleniumBasic写了一个VBA宏,自动打开网页、跳转到指定页面、提取表格数据、粘贴到Sheet里。运行一次只要一分钟,一周能帮同事省出半天的重复劳动。

关键步骤也很朴素:先记录网页URL和登录方式,再定位表格区域,把innerText读出来拆分到单元格。需要注意,网页表格经常有合并单元格和分页,逻辑写起来比较繁琐,一定要先理清结构再动手。

5.3 VBA方案的三条经验

  • 确认Office是32位还是64位。SeleniumBasic的COM注册表项和位宽有关,装完第一次运行报“找不到组件”的情况,十有八九是位宽不匹配。
  • 建议把浏览器驱动文件放到固定目录,并在VBA里用绝对路径引用。办公室电脑环境各异,依赖全局路径太容易翻车。
  • 网页一改版,VBA脚本大概率要跟着改。所以我建议在代码里把关键选择器统一放在模块顶部的常量区,改起来一目了然。

职场里很多不需要专门自动化测试师的场景,VBA方案反而是同事们最欢迎的,因为它不依赖额外环境,打开Excel就能用。

6. 常见问题与排查技巧实录

6.1 高频报错:一份速查表

写Selenium脚本这几年,我把遇到最多的几类报错整理成了一张表,也分享给团队里的新人:

报错类型常见原因处理思路
NoSuchElementException元素没加载出来,或选择器写错先手动打开页面确认元素是否存在,再改用显式等待
TimeoutException等待条件一直不满足检查条件类型是否符合预期,查看页面是否有弹窗遮挡
ElementClickInterceptedException按钮被其他元素遮挡先scrollIntoView,或者关闭弹窗/悬浮层,再点击
StaleElementReferenceException页面刷新后旧引用失效不要复用旧元素引用,重新定位
SessionNotCreatedException驱动版本和浏览器版本不匹配下载匹配版本的驱动,最好固定浏览器版本
InvalidArgumentException传入的参数格式不对检查命令参数,比如路径必须是绝对路径
WebDriverException浏览器进程异常退出检查是否有残留Chrome进程,清理后重试

这些报错信息不是看一遍就能记住的,最好的习惯是每次遇到就记录到自己的笔记里,尤其是当时页面状态和报错堆栈。时间长了你会形成直觉:看到哪种报错,大概知道是哪一类问题。

6.2 调试自动化脚本的三个动作

第一,把无头模式关掉,开着浏览器窗口跑。很多问题在无头模式下表现得很诡异,比如元素明明存在但点击无效,开着浏览器能看到真实的交互过程,问题一下子暴露出来。

第二,放慢速度。在关键步骤前加一个time.sleep(0.5),或者用调试模式逐行跑。自动化脚本调的不是功能逻辑,而是操作时序,慢下来才能看清每一步发生了什么。

第三,用截图和页面源码定位。报错的瞬间,先driver.save_screenshot()存张图,再把driver.page_source写成html文件。很多时候只看报错信息想破头,一打开页面快照就能发现问题,比如元素被弹窗挡住了,或者页面还在加载中。

6.3 工作里特别值得坚持的三个小技巧

第一个技巧,把选择器集中管理。不要在每个函数里直接写一长串XPath,最好定义一个类,把每个页面元素的选择器、预期等待条件都收拢到一起。这就是大家常说的Page Object模式。一开始写感觉费时间,等脚本规模大了,改一个前端字段名只需要改一个地方,那种幸福感是实实在在的。

第二个技巧,始终保证driver会被关闭。脚本出错不可怕,可怕的是出错后浏览器进程还挂在服务器上吃内存。我一般用try/finally结构或者with上下文管理器,保证driver.quit()一定执行。写长任务脚本时,还要定期加个长度检查,做看门狗,防止浏览器假死拖垮任务。

第三个技巧,多花十分钟手动走一遍页面再写代码。很多人一上来就写自动化,结果连按钮是点击后弹窗还是跳转新页面都没搞明白,脚本怎么调都不对。先手动操作目标页面,把每一步涉及的元素、跳转、加载等待都记录下来,等于先有了一张流程图,后面写代码只是把这套动作翻译成Selenium API。这个习惯能大幅减少反复调试的时间。

最后说一句我个人的体会:Selenium这门手艺,平时看不出来多值钱,但真到排查一个诡异报错或者帮同事省掉半天重复劳动的时候,你会庆幸自己当时没偷懒把底层细节搞明白。希望这篇指南,能让你在自动化测试这条路上少踩几个坑。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询