Selenium 4元素定位:告别find_element_by_*,用By新写法解决问题
说实话最近在技术交流群里看到最多的报错截图就是AttributeError: WebDriver object has no attribute find_element_by_id。凡是把 Selenium 升级到 4.x 之后再去跑旧脚本的基本都会被这一串错误迎面砸中。项目里几十个页面对象、几百条定位语句同时失效一时分不清到底是环境坏了还是代码写错了。其实原因很直接Selenium 4 正式把这批find_element_by_*系列方法从 API 里移除了。再加上 Chrome 浏览器版本更新频繁、chromedriver 动不动就要跟着换定位元素这件事在升级之后就成了自动化测试和爬虫脚本里最高频的拦路虎。这篇文章就围绕这个报错把整件事讲透官方为什么非要移除旧接口、新写法到底长什么样、存量代码怎么高效迁移以及替换过程中你大概率会撞上的几个关联报错。不管你是刚装好 Selenium 想学自动化测试的新手还是维护了好几年爬虫脚本的老手只要代码里还在用find_element_by_id这篇内容都能帮你规避升级后的一大堆坑。1. 报错根源Selenium 4 的接口清理1.1 旧版 find_element_by_* 为什么会被废弃从 Selenium 3 时代走过来的朋友应该都有感觉以前定位元素非常直观driver.find_element_by_id(login_btn)、driver.find_element_by_name(username)、driver.find_element_by_class_name(form-item)只要记清楚结尾的定位方式基本不会出错。但在框架维护者眼里这套 API 有一个很尴尬的设计问题每增加一种定位策略就要在WebDriver接口上新增一个方法长期下来接口越来越臃肿而且所有定位逻辑都堆在基类里职责划分很不清晰。Selenium 4 把定位方式收敛到By这个类里统一交给find_element(By.ID, value)这样的形式处理本质上就是把“方法后缀不同”变成了“参数值不同”。这个调整对底层架构而言是合理的因为框架内部可以对定位器做统一的校验、缓存和错误处理后续加入新定位策略时也不用再动WebDriver类的签名。但对开发者来说升级就变成了一场批量的代码改造官方文档里简简单单一段迁移说明跟实际项目里几十上百处调用比起来完全是两码事。我在帮别人排查问题时发现很多人一看到这个报错就以为是 selenium 装坏了或者 chromedriver 驱动有问题于是反复卸载重装折腾半天也没用。其实只要在控制台执行pip show selenium看一下版本号发现是 4.x再对比代码里还在用旧写法基本就能锁定问题。版本升级带来的 API 移除不是靠重装环境能解决的它是代码层面的不兼容。1.2 新旧接口完整对照表先给一张最实用的对照表把你可能用到过的所有旧方法都列出来方便直接查。旧写法新写法find_element_by_id(id)find_element(By.ID, id)find_element_by_name(name)find_element(By.NAME, name)find_element_by_xpath(//div)find_element(By.XPATH, //div)find_element_by_css_selector(a.link)find_element(By.CSS_SELECTOR, a.link)find_element_by_class_name(cls)find_element(By.CLASS_NAME, cls)find_element_by_tag_name(div)find_element(By.TAG_NAME, div)find_element_by_link_text(登录)find_element(By.LINK_TEXT, 登录)find_element_by_partial_link_text(登)find_element(By.PARTIAL_LINK_TEXT, 登)多元素定位也是同样的规律find_elements_by_id(list)改成find_elements(By.ID, list)其他方法名照葫芦画瓢就行。这里有个新手特别容易忽略的细节使用新写法前必须先导入By类from selenium.webdriver.common.by import By如果没有这一行运行时会直接报NameError: name By is not defined这又是一个独立于主题的报错很容易让人误判成定位器写法问题。所以我建议在所有用到 Selenium 的脚本文件顶部统一把By的导入加上避免后续每个文件单独处理。2. 从错误理解到正确使用八种定位方式逐个过一遍2.1 每种定位器的适用场景与写法要点By.ID在页面里理论上应该是唯一的所以它是最快也最稳定的定位方式。浏览器对 DOM 进行匹配时id 的命中率最高脚本可读性也好。如果你的团队自己能控制前端代码尽量要求前端给关键元素加上稳定且有语义的 id。唯一要小心的是动态页面里 id 可能被 Vue、React 这类框架在列表渲染时重新生成这种场景下 id 就不能直接当作稳定属性来用。By.NAME一般用于表单定位比如输入框、下拉框、单选框的 name 属性。但 name 在页面里经常不是唯一的比如一组 radio 按钮共用同一个 name这时候需要用find_elements拿到列表再按索引选择。用 name 之前先按 F12 打开开发者工具确认一下页面里有多少个同名元素这是个好习惯。By.XPATH功能最强几乎能定位到任意元素特别是元素没有 id、没有稳定 class 的时候xpath 几乎是最后的硬手段。但 xpath 匹配的代价也高尤其用//开头的绝对路径浏览器要先遍历整个文档定位速度明显变慢。我见过太多新人写出/html/body/div[3]/div[1]/div[2]/form/input这种路径页面结构随便改一下用例当场报废。除非万不得已我不会在维护性要求高的项目里使用绝对 xpath。By.CSS_SELECTOR是我日常项目里最推荐优先使用的定位方式。它的可读性很好支持.class、#id、[attributevalue]这些组合定位速度比 xpath 快韧性也比绝对路径强很多。举个例子要定位一个按钮它的 class 带动态变化但 id 是固定的可以直接用button#submit这种写法。By.CLASS_NAME用起来简单但有一个经典坑如果元素的 class 属性是btn btn-primary直接传By.CLASS_NAME会报错因为class_name只接受单个类名。遇到多类名时要么用By.CSS_SELECTOR写成.btn.btn-primary要么先用另一个稳定属性定位。By.TAG_NAME通常只在find_elements场景里使用比如想统计页面上有多少个 input、多少个 div直接获取一个列表。单独定位一个元素的情况比较少因为同标签元素太多很容易定位错对象。By.LINK_TEXT和By.PARTIAL_LINK_TEXT只对a标签生效。前者需要精确匹配完整链接文本后者用部分文本匹配。这两个方法主要用在验证导航链接、翻页按钮这类元素上比如页面底部有个“下一页”链接用By.PARTIAL_LINK_TEXT会非常方便。2.2 新写法对代码结构的实际改善把定位方法换成find_element(By.ID, xxx)之后你会发现一个意想不到的好处定位器本身变成了数据可以像变量一样传递和复用。这是旧 API 很难做到的事情。举个例子from selenium.webdriver.common.by import By username_locator (By.ID, username) password_locator (By.NAME, password) login_button_locator (By.CSS_SELECTOR, button.btn-login) def fill_input(driver, locator, text): el driver.find_element(*locator) el.clear() el.send_keys(text) fill_input(driver, username_locator, test_user) fill_input(driver, password_locator, 123456)这种写法的可组合性明显更强。定位器可以放进配置、可以按页面归类、可以在函数之间传递配合WebDriverWait和expected_conditions也更加自然。说到底By的引入不只是改改语法它让定位器从一个“挂在 driver 上的方法”变成了“可以被管理的数据”这对页面对象模式Page Object Model的落地非常有用。3. 存量项目的高效迁移方案3.1 用全局搜索快速定位所有旧调用在真实项目里尤其是维护了一两年的代码库find_element_by_*可能散落在几十个文件里。逐个人工排查非常低效我的做法是先用 IDE 的全局搜索功能搜索find_element_by_这个前缀把旧调用全部列出来。具体分三步走。第一步全局搜索find_element_by_会列出所有旧调用位置注意find_elements_by_也要一起搜这两个前缀是同一批问题。第二步根据匹配结果逐个跳转判断当前是单元素还是多元素再对应改成find_element(By.XXX, ...)或find_elements(By.XXX, ...)。第三步全部改完以后用python -m py_compile或者 IDE 自带的语法检查跑一遍先保证没有语法错误再做基础功能回归。如果你有大量文件要改也可以尝试写脚本做文本级替换。比如把find_element_by_id(正则替换成find_element(By.ID,但这类替换在括号不对称、字符串拼接、换行等场景下很容易漏掉。我的经验是简单的替换可以用脚本但替换完必须靠人工 review 把关这种钱省不得。3.2 临时兼容层让旧代码先跑起来如果项目确实太大短期内改不完又急着让业务跑起来可以考虑做一个临时兼容补丁把旧方法动态挂回 WebDriver 类上。我在一个历史遗留项目里用过这个方案成功让团队里几十个老用例先恢复正常运行然后再逐步迁移。from selenium.webdriver.remote.webdriver import WebDriver from selenium.webdriver.common.by import By _MIGRATION_MAP { find_element_by_id: By.ID, find_element_by_name: By.NAME, find_element_by_xpath: By.XPATH, find_element_by_class_name: By.CLASS_NAME, find_element_by_tag_name: By.TAG_NAME, find_element_by_css_selector: By.CSS_SELECTOR, find_element_by_link_text: By.LINK_TEXT, find_element_by_partial_link_text: By.PARTIAL_LINK_TEXT, } def _old_finder(by): def wrapper(self, value): return self.find_element(by, value) return wrapper for _name, _by in _MIGRATION_MAP.items(): if not hasattr(WebDriver, _name): setattr(WebDriver, _name, _old_finder(_by))注意find_elements_by_*系列也要做类似处理逻辑完全一样只是把内部的find_element换成find_elements。但必须说清楚这只是过渡手段不推荐长期依赖。等所有用例迁移完成一定要把这个补丁文件删掉否则团队里会出现新旧写法夹杂的情况后续维护更痛苦。3.3 顺手统一等待策略迁移过程中如果只改定位器写法不检查等待策略依然会在运行时报TimeoutException或者NoSuchElementException。很多老脚本原来依赖隐式等待如果环境网络变化较大固定等待时间就显得不够灵活。我建议在迁移时顺手做三件事全局统一设置一个隐式等待兜底比如driver.implicitly_wait(5)对关键元素用WebDriverWait做显式等待对出现频率高的动态元素统一封装 click 和 input 方法内部自带显式等待。这样做的目的是把等待逻辑跟定位逻辑分开。定位器只负责描述“元素在哪”等待条件只负责描述“元素什么时候可用”两层职责分离后后续排查问题会清晰很多。4. 升级后常见的关联报错与排查思路4.1 明确报错类型再动手别把三类错误混为一谈我经常在交流群里看到有人把三种不同的报错混在一起排查浪费了不少时间。第一种就是本篇核心的AttributeError: WebDriver object has no attribute find_element_by_id这个属于 API 不存在多半是版本升级导致解决方案就是改成新的find_element(By.ID, ...)写法。第二种是NoSuchElementException: Message: no such element: Unable to locate element这个是指定位器没有找到任何元素说明页面里根本没有这个 id 或 class或者元素在 iframe 里、处于隐藏状态、需要滚动才会渲染。遇到这个错要检查的是页面结构和定位器本身而不是版本问题。第三种是TimeoutException: Message: timed out waiting for element这个是在使用WebDriverWait时等待超时说明元素迟迟没有达到期望状态多半是前端渲染太慢、接口返回异常或者等待条件写错。我的习惯是看到AttributeError先看 selenium 版本看到NoSuchElementException先打开 DevTools 手动查元素看到TimeoutException先看网络请求和等待条件。把报错类型分清楚排查方向立刻就明确了。4.2 与 find_element 紧密相关的 WebDriverWait 旧写法报错还有一个隐藏很深的问题升级前如果你写过wait.until(EC.presence_of_element_located_by_id(username))这种旧版 expected_conditions 写法在 Selenium 4 里同样被移除了。而且报错信息不是直接提示旧方法没了而是AttributeError: module selenium.webdriver.support.expected_conditions has no attribute presence_of_element_located_by_id。这个报错和find_element_by_id报错非常像如果不仔细看上下文很容易误判成定位问题。实际上改法和元素定位一致需要统一改成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, 10) wait.until(EC.presence_of_element_located((By.ID, username)))这里有一个新手高频踩坑点传入的必须是一个元组(By.ID, username)。如果少了这一层括号写成EC.presence_of_element_located(By.ID, username)会直接报TypeError: __init__() takes 3 positional arguments but 4 were given。这个错误和主题报错无关但经常在迁移后紧接着出现容易让人发懵。4.3 驱动版本与浏览器版本不匹配导致的“伪定位报错”继续排查过程中还会碰见一种“伪定位报错”明明定位器没写错、代码也是新的find_element(By.ID, ...)但运行时报SessionNotCreatedException: This version of ChromeDriver only supports Chrome version xxx。这个其实跟定位毫无关系是 chromedriver 和浏览器版本不匹配导致的。升级 selenium 的人通常会顺手把浏览器也升级chromedriver 如果没跟上就会在启动 session 时直接失败。解决办法是打开 Chrome 浏览器“关于”页面查看版本号再下载对应版本的 chromedriver并确保它在 PATH 环境变量里或者在创建 WebDriver 时用Service显式指定驱动路径。Selenium 4 自带 selenium-manager 可以尝试自动下载匹配驱动但实测在不同网络环境下不一定稳定手动指定仍然是更可控的方案。5. 防坑清单与长期实践建议5.1 环境层面锁定版本减少意外版本引发的连锁问题最好的解决方式不是每次报错后去搜解法而是从环境层面控制变量。项目里用requirements.txt或pyproject.toml精确记录 selenium 的版本号避免同事拉代码时装到不同版本有条件的把自动化环境跑在虚拟环境里别跟全局 Python 的包混着装。升级 selenium 之后第一件事是跑一个最小用例确认 driver 能正常启动、页面能正常打开再跑完整回归。chromedriver 的版本建议写进项目的 README 或者 CI 配置里。团队多人协作时“这个报错你那边没出现”的情况八成就是浏览器版本或驱动版本不一致。把这些环境信息固化下来能减少很多无效沟通。5.2 写定位器时的一些通用原则这些原则和版本无关但从这次版本升级事件里暴露出来的教训值得重新强调。第一能不用 xpath 就不用尤其不要用绝对路径。页面结构稍微调整脚本就得跟着改维护成本极高。第二id 和 name 都不够稳定时优先用 CSS_SELECTOR或者配合>

相关新闻

梅花雪景古装人像横构图全攻略:从踩点到雪天曝光

梅花雪景古装人像横构图全攻略:从踩点到雪天曝光

梅花和雪景同时出现,往往是冬天给摄影人出题出得最狠的一次:既要追花期,又要赌天气,还要安排人物服装、场景构图全部适配。我最近刚拍完一组梅花雪景古装人像,标题里那串“横构图2”其实就是第二组的意思——第一组竖构…

2026/10/1 3:37:36 阅读更多 →
ESP32接大模型不算AI硬件?8个工程问题才是关键

ESP32接大模型不算AI硬件?8个工程问题才是关键

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 3:37:36 阅读更多 →
Spring Boot个人记账系统毕设:从数据库设计到前后端部署全攻略

Spring Boot个人记账系统毕设:从数据库设计到前后端部署全攻略

这份经历,严格讲应该算是“帮一个学弟收拾毕业设计源码”的副产品。他拿到的项目编号是 70415,标题就是 Spring Boot 个人记账系统,功能不算复杂,但作为计算机毕业设计来说,覆盖面很全:后端有登录鉴权、收支…

2026/10/1 3:36:35 阅读更多 →

最新新闻

从零手搓AI工程:推理服务显存管理与并发优化实战

从零手搓AI工程:推理服务显存管理与并发优化实战

1. 从零手搓AI工程:为什么我不建议你直接调包很多人一听到“AI工程”这四个字,第一反应就是打开某个云平台,拖几个组件,调几个API,然后跑通一个Demo,就觉得自己已经入门了。我刚开始接触这个方向的时候也是…

2026/10/1 4:17:55 阅读更多 →
树莓派5代架构跃迁:RP1芯片如何重构单板机IO确定性

树莓派5代架构跃迁:RP1芯片如何重构单板机IO确定性

1. 这不是升级,是重构:树莓派5代到底带来了什么?如果你去年刚入手树莓派4B,现在看到Raspberry Pi 5的宣传图第一反应可能是“又来?”——但这次真不一样。我拆过三块树莓派5的工程样机,也把手上四台不同代际…

2026/10/1 4:17:55 阅读更多 →
EQ频段逐段拆解:20Hz-20kHz混音必知的频率处理技巧

EQ频段逐段拆解:20Hz-20kHz混音必知的频率处理技巧

玩混音的人,很少有不碰EQ的。无论你是刚拿起DAW的新手,还是已经在棚里泡了好几年的老手,EQ都是最基础也最耐琢磨的工具。我见过太多人对着频谱面板一通乱拉,结果声音越调越怪,最后干脆把曲线全部平直,感叹&…

2026/10/1 4:17:55 阅读更多 →
Ubuntu 24.04启动盘制作:ZFS+UEFI适配指南

Ubuntu 24.04启动盘制作:ZFS+UEFI适配指南

1. 为什么Ubuntu 24.04的U盘制作不能只靠“下载镜像拖进U盘”?Ubuntu 24.04 LTS(代号Noble Numbat)是Canonical在2024年4月正式发布的长期支持版本,它首次默认启用ZFS根文件系统作为可选安装方案,内核升级至6.8&#x…

2026/10/1 4:17:55 阅读更多 →
网优面试硬核:从LTE协议栈到5G波束管理

网优面试硬核:从LTE协议栈到5G波束管理

简介:面向移动联通电信三大运营商网络优化岗位求职者的LTE与5G理论面试汇总,聚焦无线网优面试中的高频考点与实战应答思路,适合正在备考的应届生及转岗人员。内容以题库形式呈现,系统梳理LTE网络架构(eNB/MME/S-GW功能…

2026/10/1 4:17:55 阅读更多 →
AI写代码时代,程序员的“快”为何不再是护城河?

AI写代码时代,程序员的“快”为何不再是护城河?

我刷到那条动态时其实没什么情绪:一个同事转发的行业见闻,配文是“对不起,那个写代码最快的 00 后,刚刚被裁了”。真正让我停下来的是评论区里几乎一边倒的困惑——为什么团队里敲键盘最快、功能出得最多的人,反而成了…

2026/10/1 4:16:55 阅读更多 →

日新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 1:01:17 阅读更多 →

周新闻

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解 【免费下载链接】spirula-studio Cross-vendor 3D Gaussian Splatting trainer - video to splat to mesh, Vulkan or CUDA. 项目地址: https://gitcode.com/GitHub_Trending/sp/spirula-studio Sp…

2026/9/30 13:14:22 阅读更多 →
SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南 模板网站太丑不够用?别急着加滤镜,那是治标不治本。很多老板盯着后台流量掉得眼红,却还在纠结首页Banner的圆角是不是3像素。这就像穿着西装去挖土,姿势不对,努力白费。我整理这份 速查手册…

2026/9/30 18:13:06 阅读更多 →
FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏 【免费下载链接】FireRed-OpenStoryline FireRed-OpenStoryline is an AI video editing agent that transforms manual editing into intention-driven directing through natural language …

2026/9/30 13:14:49 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 1:01:17 阅读更多 →