ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Selenium网页自动化入门:从环境搭建到元素定位与工程化维护

Selenium网页自动化入门:从环境搭建到元素定位与工程化维护 做了五年多Web开发最烦的事情就是重复操作网页——填表单、翻数据、一页页点击、一遍遍核对。后来同事甩给我一个词Selenium。我用半天时间跑通了第一个自动化脚本从那以后凡是需要人工反复操作的网页流程我第一反应都是优先考虑用这套框架去解决。今天这篇Selenium入门教程就是想把从零到能自己写脚本的完整路径讲透包括环境怎么搭、元素怎么定位、脚本为什么时好时坏以及一个进阶的设计思路页面元素枚举与定位元数据管理。适合完全没有接触过网页自动化操作的小白也适合那些已经写过几个脚本但总觉得不稳的初学者。1. 先搞清楚Selenium靠什么干活再动手装环境很多人上来就执行pip install selenium装完就写代码结果连浏览器都打不开。这不怪你主要是没搞明白Selenium到底是怎么工作的。1.1 三件套的配合逻辑库、驱动、浏览器Selenium并不是一个独立运行的程序它靠三样东西配合才能工作Selenium库装在Python环境里提供API你用它写自动化代码。WebDriver一个独立的驱动程序负责把代码翻译成浏览器能理解的指令。比如Chrome有ChromeDriverFirefox有GeckoDriverEdge有EdgeDriver。浏览器本体实际执行页面渲染、点击、输入等操作的载体。三者关系很像你、外卖平台和餐厅你Selenium库通过外卖平台WebDriver下单餐厅浏览器按单出菜。光有Selenium库没有WebDriver代码不知道把指令发给谁光有驱动没有浏览器指令发过去也没人接。1.2 驱动与浏览器版本不匹配是新手第一个坎我在教学中最常看到的报错是SessionNotCreatedException或者Cannot find matching ChromeDriver。这几乎都是因为Chrome和ChromeDriver版本对不上。解决方案很直白打开Chrome访问chrome://version查看到完整版本号比如122.0.6261.x。去ChromeDriver的官方下载站点选择与主版本号完全一致的驱动。将下载的chromedriver放到环境变量可识别的目录或者在代码里直接指定路径。需要说明的是这个驱动下载站点很多时候网络访问不稳定但ChromeWebDriver的版本对应关系必须严格一致不能凑合否则后续每一步操作都可能是坑。对于新手我建议直接下载chromedriver.exe或chromedriver二进制文件放到Python同目录写脚本后用Service指定路径可以完全避开环境变量的配置麻烦。比如from selenium.webdriver.chrome.service import Service from selenium import webdriver service Service(D:/tools/chromedriver.exe) driver webdriver.Chrome(serviceservice) driver.get(https://www.example.com)1.3 第一段代码打开网页、找到东西、点一下环境齐了跑通一个最小流程会让你立刻找到感觉。这段代码就做三件事打开页面、搜索关键词、打印标题。import time from selenium import webdriver from selenium.webdriver.common.by import By driver webdriver.Chrome() try: driver.get(https://www.baidu.com) driver.find_element(By.ID, kw).send_keys(Selenium) driver.find_element(By.ID, su).click() time.sleep(3) print(driver.title) finally: driver.quit()这个例子里的核心就是find_element。By.ID、send_keys、click这三个组合基本覆盖了大部分网页自动化操作的基本动作。跑通了这个流程你就已经掌握了Selenium最基础的使用闭环。1.4 Selenium IDE插件不想写代码先录制一把很多人会搜怎么安装selenium插件这里的插件通常指两个东西一个是直接在浏览器中录制回放的Selenium IDE另一个是浏览器的WebDriver扩展但后者一般不需要单独安装。Selenium IDE是一个浏览器扩展官方出品支持Chrome和Firefox。装上之后你能在浏览器里直接录制自己的点击输入操作然后导出成Python、Java、JavaScript等语言的Selenium脚本。它非常适合快速验证一个自动化流程是否可行也适合非编程人员先体验网页自动化操作的逻辑。不过说实话Selenium IDE导出的脚本质量不算高定位方式经常是死板的XPath稳定性一般。它更适合当教学工具和快速原型真正干活的项目还是得手写代码。2. 元素定位选对方法的思路比记住八种方法更重要Selenium官方提供8种定位方式id、name、class name、tag name、link text、partial link text、xpath、css selector。很多教程喜欢让你全部背下来我反而觉得没必要。你需要的是建立一种用最少的定位信息锁定唯一元素的思路。2.1 八种定位方式的直觉分类这8种方法可以按直觉拆成三组定位方式适用场景缺点id页面里唯一标识速度最快很多前端不写idname表单字段典型属性可能重名class name、tag name同一类元素统一处理太泛通常定位多元素link text、partial link text只能用于带a的链接能力太窄css selector结构清晰、性能好复杂层级时表达式难写xpath几乎能定位任何元素性能相对慢优先浏览器验证我的习惯是id优先css其次xpath兜底。不要一上来就用XPath硬刚很多情况下给元素加一个合适的css selector反而更清爽。2.2 XPath为什么是兜底方案但不是唯一方案XPath擅长处理那些没有id、没有name、元素还长得特别像的场景。比如一个列表里有10个按钮你只点第2个XPath可以这样写//ul[classmenu]/li[2]/a这就用相对路径锁定了目标。但XPath有点消耗性能在循环里频繁执行时会影响效率。这里就是一个典型的取舍开发同学根本不管你的定位难度他们只管把功能做出来。前端只要改一个class你的整个XPath可能就废了。用XPath还有一个常见误区——直接在浏览器控制台里复制XPath。复制出来的通常是绝对路径长得像/html/body/div[3]/div/div[2]/form/input页面稍微加点广告栏、弹个浮层路径就断了。我自己很少直接用这种而更倾向自己写相对路径。2.3 一次完整的定位不到元素排查过程假设脚本在点击按钮时报NoSuchElementException请按这个顺序排查打开地址栏用浏览器手动打开目标页面按F12打开开发者工具在Elements面板里按CtrlF搜索你的定位表达式。如果表达式匹配到0个元素先看页面是否真的加载完成。如果表达式匹配到多个元素检查是否有iframe嵌套。查看元素是否被遮罩还是要先滚动到可视区域。iframe是新手最常忽略的坑。在iframe里的元素你直接在根文档里找永远找不到。这时需要先进框架操作完再切出来driver.switch_to.frame(mainFrame) # 在iframe里操作 driver.switch_to.default_content()毕竟网页自动化操作的本质是模拟用户操作那用户能做的一切脚本理论上都应该能做。定位不到元素很多时候只是因为它并不在当前窗口里。3. 等待机制脚本不稳定的头号元凶你有没有遇到过这种情况同一个脚本早上跑得好好的下午就报错或者在这台电脑上秒开换台电脑就偶发失败。最常见的元凶不是定位方式而是等待机制没做好。3.1 网速不是脚本慢的直接原因页面加载是异步的。driver.get(url)返回时并不代表页面上的所有元素都已经渲染完成。尤其是现在的前端框架很多内容是通过AJAX请求动态带出来的页面框架先出来数据后到。如果你在数据还没回来时就去查找一个动态加载的按钮结果必然是找不到。这种问题非常坑因为手动测试时你会觉得页面早就加载完了但脚本的执行速度远超人的反应速度在毫秒级别就去抓元素抓了个寂寞。3.2 三种等待的适用场景对比等待类型写法特点推荐度强制等待time.sleep(3)简单粗暴但浪费时间和性能尽量少用隐式等待driver.implicitly_wait(10)设定一个最长轮询时间针对全局所有元素查找可设但要理解其局限显式等待WebDriverWait针对某个条件反复轮询直到条件满足或超时最推荐隐式等待有一个很容易被忽视的坑它只影响find_element的查找过程等的是元素是否出现在DOM里而不是元素是否可见、是否可点击。如果一个按钮已经是DOM的成员但还处于禁用状态隐式等待照样会用找到了的结果骗过你的直觉然后点击时依然报错。3.3 显式等待的正确打开方式显式等待的做法是为每一个关键步骤设定明确的等待条件。举个例子你想要等一个按钮可点击就写成from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC wait WebDriverWait(driver, 10) login_btn wait.until(EC.element_to_be_clickable((By.ID, login))) login_btn.click()这样Selenium最长等10秒每500毫秒检查一次按钮一变成可点击状态就响应既不会空等也不会跑得太快。条件按需选择element_to_be_clickable适合按钮和链接。presence_of_element_located适合只需确认元素存在的场景。visibility_of_element_located适合需要元素可见后再操作的场景。text_to_be_present_in_element适合校验提交后页面上是否出现某个文本。显式等待的核心逻辑不是等一个固定时间而是等一个条件被满足。这个思路对构建稳定的网页自动化操作脚本非常关键也是我从脚本经常挂进阶到脚本可以放那跑一整夜的分水岭。4. 页面元素枚举与定位元数据管理从能跑走向好维护入门之后大家都会面对一个现实写的时候很爽项目一改版就炸锅。问题的根源不是自动化技术不好而是页面元素的定位数据管理得太随意。4.1 为什么脚本最怕前端改版我见过不少项目把所有find_element散落在脚本各处今天改一个登录按钮的class你就得在几十个文件里搜字符串。最要命的是同一个元素可能在多个地方被重复定位改起来特别容易漏。这与代码设计相关定位数据比如By.ID等不该散落各处应该像后端项目的常量配置一样集中管理。既然Selenium本身是测试框架那用它做工程化的时候也应该用标准软件工程的思路去约束它。4.2 只存定位元数据不存元素对象这里要引入一个设计理念页面元素枚举Element Enum与仅存储定位元数据。先说结论枚举里只存By和值定位元数据不要直接存放WebElement对象。为什么不存WebElement对象因为页面是动态的。你今天获取到的元素对象可能在一次页面局部刷新之后就变成了过期的元素。此时你对它再执行click()大概率会抛出StaleElementReferenceException。更聪明的做法是枚举里保存如何去找到这个元素的定位信息每次操作时重新去页面查找。这样不管页面刷新多少次只要定位条件仍然成立当前操作就能成功。看代码from enum import Enum from selenium.webdriver.common.by import By # 枚举核心仅存储定位元数据 class LoginPageElements(Enum): USERNAME_INPUT (By.ID, username) PASSWORD_INPUT (By.NAME, password) LOGIN_BUTTON (By.CSS_SELECTOR, button[typesubmit]) ERROR_MSG (By.XPATH, //span[classerror]) # 在使用时动态获取元素 from selenium.webdriver.remote.webelement import WebElement def find(driver, element_enum: LoginPageElements) - WebElement: by, value element_enum.value return driver.find_element(by, value)4.3 用枚举结构管理页面元素的完整示例工程化一点的做法是把同一个业务页面的所有元素封装进一个类或枚举再加上一个统一的查找函数。例如class BasePage: def __init__(self, driver): self.driver driver def element(self, locator_enum): by, value locator_enum.value return WebDriverWait(self.driver, 10).until( EC.presence_of_element_located((by, value)) ) class LoginPage(BasePage): USERNAME_INPUT (By.ID, username) def input_username(self, text): self.element(self.USERNAME_INPUT).send_keys(text)这样做的价值一是页面改版时只需要集中改一处定位元数据二是仅存储定位元数据让对象永远不会过期每次操作都拿最新状态三是代码可读性大幅提升看枚举名就知道在操作什么。这种思路还有一个额外好处枚举天然支持遍历。如果你想批量检查某个页面的所有核心元素是否都在直接遍历枚举类就行for element in LoginPageElements: by, value element.value try: driver.find_element(by, value) print(f{element.name}: OK) except Exception: print(f{element.name}: FAIL)这是一个非常实用的页面健康检查技巧。5. 把别人踩过的坑变成自己的经验调试与排查写Selenium脚本的时间往往不到20%剩下80%的时间都在处理各种奇奇怪怪的异常。不要沮丧这套框架的异常体系其实相当友好只要你学会看信息。5.1 先学会看错误信息再去看堆栈很多新手一看到红字就慌直接截图问别人。实际上Selenium的异常信息已经写得很清楚NoSuchElementException元素不存在按第2章的排查流程走。ElementNotInteractableException元素找到了但点击不进去通常是隐藏元素或透明元素先滚动到可视区域再操作。StaleElementReferenceException元素过期刷新后重新定位。TimeoutException等待条件超时先确认条件是否写错再看页面是否真的有这个状态。拿到异常先读第一行英文不明白就搜异常类名不要先怀疑自己的代码而是先怀疑页面的状态。这个习惯能省下大量时间。5.2 几个高频异常的原因与对策ElementClickInterceptedException是点击被拦截。这个坑一般是由页面顶部悬浮的遮罩层或广告引起的。最简单的处理方式是滚动到元素位置再点击element driver.find_element(By.ID, submit) driver.execute_script(arguments[0].scrollIntoView();, element) element.click()如果还是被拦截就可以用JavaScript直接触发点击这在某些特殊场景下能绕过遮罩但要注意这会跳过事件监听所以如果你需要触发前端绑定的事件要谨慎driver.execute_script(arguments[0].click();, element)另外send_keys输入中文时偶尔出现丢字。优先在输入前clear()一下input_box driver.find_element(By.ID, kw) input_box.clear() input_box.send_keys(Selenium入门教程)5.3 让脚本稳定运行的小习惯最后分享一些我自己的经验算不上高深但都能实打实减少问题每次脚本结束都要driver.quit()而不是close()。quit()会把浏览器进程整个关掉避免内存泄漏close()只关闭当前标签页可能留下僵尸进程。添加日志而不是print。用logging模块记录每一步操作出问题时能快速定位到是第几步挂了。合理使用BrowserMob这类代理工具做网络拦截调试时会顺畅很多。不过这属于进阶内容入门可以先不碰。别把真账号密码写死在代码里。从环境变量或配置文件读取否则代码一旦分享出去就是安全事故。本地验证OK的脚本放到服务器上跑之前先确认服务器上有浏览器。很多自动化部署到服务器就挂就是因为服务器根本没有安装图形界面的浏览器。我在实际项目中经常用Selenium做定时巡检每天早上自动打开业务后台检查待办数量有异常就发送通知。在多轮调试中最大的收获就是等待策略和元素定位元数据的管理。前者解决了脚本跑不通的问题后者解决了脚本改不动的问题。这两个习惯一旦养成你会发现自己写Selenium的速度越来越快巡检、批量数据录入、竞品信息收集这类需求都能稳稳妥妥地自动化完成。最后再分享一个小技巧页面元素枚举不仅可以在登录页用还可以在任意页面应用特别是那些有几十个控件的大型表单页。我做过最爽的一个项目是把一个多级审批页面的全部元素都按枚举管理起来后来前端改版两次我只花了不到半小时就完成了整体修复而不用像以前那样一个脚本一个脚本翻。这种一次设计、长期受益的体验才是网页自动化操作真正让人上瘾的地方。
返回列表