Selenium定位元素时XPath的灵活度最高,能解决几乎所有复杂、动态、无唯一属性的场景。
绝对路径和相对路径,选后者
绝对路径从根节点开始,页面结构稍有变动就会失效:
python
# 脆弱,不推荐
driver.find_element(By.XPATH, "/html/body/div[1]/div[2]/span")
相对路径用 // 跳过中间层级,适应性强得多:
python
driver.find_element(By.XPATH, "//button[@data-testid='submit']")
定位属性和文本
单属性准确一致
//input[@id='username']
多属性组合(动态 id 时很实用)
//input[@type='text' and @placeholder='手机号']
部分属性一致
python
# id 前缀固定、后缀随机:'btn_123abc'
driver.find_element(By.XPATH, "//button[starts-with(@id, 'btn_')]")
# class 包含 'active',可能同时有多个 class
driver.find_element(By.XPATH, "//div[contains(@class, 'active')]")
文本定位
精确文本://a[text()='立即注册']
模糊文本://a[contains(text(), '注册')]
但 text() 只能一致直接子文本节点,遇到 <a><span>注册</span></a> 就会一致失败,这时换成点的形式:
//a[contains(., '注册')]
消除空格干扰normalize-space
//button[normalize-space() = '登 录'] 能一致到“登 录”、“ 登 录 ”等多余空格。
轴元素关系定位
当目的元素本身没什么特征,但和某个已知元素存在亲属关系时,轴是最好解法。
parent::* : 父节点
ancestor::div : 祖先中的 div
child::* : 直接子元素
descendant::span : 所有后代 span
following-sibling::div[1] : 紧跟的兄弟 div
preceding-sibling::* : 前面的兄弟元素
following::* : 之后的全部元素
preceding::* : 之前的全部元素
场景通过文字找到同级输入框
python
# 找到文本为“密码”的 span,再定位它兄弟中的 input
driver.find_element(By.XPATH, "//span[text()='密码']/following-sibling::input")
从已知元素往上找容器:
python
# 找到“提交”按钮所在的整个卡片 div
driver.find_element(By.XPATH, "//button[text()='提交']/ancestor::div[@class='card']")
索引和位置函数
XPath 索引从 1 开始(不是 0)。
(//div[@class='item'])[3] 取第三个 item。
//ul/li[last()] 取最后一个 li。
//ul/li[position()>2] 取第三个之后的所有 li。
括号必不可少:(//div[@class='item'])[1] 表示在所有一致的 div 中取第一个;而 //div[@class='item'][1] 则是每个父元素下的第一个item。
组合和取反
not() 排除特定元素
//div[contains(@class, 'item') and not(contains(@class, 'disabled'))]
| 合并多个途径
//input[@id='search'] | //input[@name='q'] 会返回两者的并集。
在已定位元素内继续查找
使用 . 前缀,表示在当前节点下执行 XPath:
python
article = driver.find_element(By.XPATH, "//div[@class='article']")
title = article.find_element(By.XPATH, ".//h1")
这比在全文档再搜一次更快且思路清晰。
调试和测试
浏览器控制台直接试:$x("//button[contains(., '登录')]") 按回车,会直接高亮一致的元素(Chrome/Firefox 均可)。
Copy XPath 的坑:浏览器右键复制的 XPath 一般是冗长的绝对途径,只能当参考,需要手工改写为短小的相对途径。
等待元素:动态页面必须用显式等待,避免 NoSuchElementException:
python
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
element = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.XPATH, "//*[contains(., '结果')]"))
)
性能优化
尽量用具体标签名替代 *://div[@id='content'] 比 //*[@id='content'] 快。
减少从根节点开始的全文扫描,先用唯一元素缩小范围:
//div[@id='main']//a[contains(., '详情')]
text() 函数会遍历文本节点,复杂页面中 normalize-space() 或 . 可能更高效且更健壮。
浏览器只支持 XPath 1.0,像 matches()(正则)、lower-case() 等函数不可用,需要用 contains/starts-with/translate 等组合替代。