XPath 实战速查:从基础语法到稳定元素定位
把 XPath 当作一条穿过 DOM 的路径,直观看懂基础语法、属性与文本筛选、关系轴、表格定位和稳定性优先级。
本文目录 10 节 · 点击展开
从宽到窄,逐步缩小范围;目标不是“能找到”,而是“稳定且唯一地找到”。
从稳定的父级或业务区域开始。
//form[@id='loginForm']沿 DOM 结构向下寻找候选。
.//button用属性或文本过滤候选元素。
[@type='submit']借助父级、同级关系找到关联元素。
following-sibling::input结果可见、可交互,并且只命中一个。
(.//button)[1]<html><body><main id="app"><form id="loginForm"><div class="field"><label>用户名</label><input id="username" /></div><div class="actions"><button type="submit">登录</button></div></form></main></body></html>
//form[@id='loginForm']//button[@type='submit']优先使用具备业务或测试语义的属性。
//*[@data-testid='login']单一条件不够时,再补必要范围。
//form[@id='loginForm']//button可读性高,但要考虑文案变化和误匹配。
//button[contains(.,'登录')]页面缩放、窗口和布局变化都会让它失效。
click(560, 420)一、五个基础符号,分别控制路径的哪一段?
基础语法不需要孤立记忆。把它们放回一条路径中,就能看见每个符号负责什么。
起点、搜索范围、当前上下文和属性条件,共同决定 XPath 从哪里走到哪里。
/从根开始绝对路径从文档根节点逐层向下。
//搜索后代从当前上下文中寻找任意深度的后代节点。
.当前节点表示当前上下文,也可读取当前节点完整字符串值。
..父节点从当前节点返回上一层父节点。
@属性在条件中读取 id、name、data-* 等属性。
/html/body/div[2]/div[1]/form/button依赖完整层级和位置,页面插入一层容器就可能失效。
//form[@id='loginForm']//button[@type='submit']直接表达“登录表单里的提交按钮”,结构变化时更容易维护。
最常见的候选搜索很简单:
//button
它会找到当前文档中的所有按钮。若已经拿到某个容器元素,则用 .// 从当前容器继续搜索,避免重新扫整个页面:
.//input
二、属性定位:先找不会轻易变化的业务特征
属性定位的关键不是“看到什么就写什么”,而是判断哪个属性真正表达元素用途。
稳定且有业务含义的属性优先;展示文案和随机样式名只适合作为补充条件。
//*[@data-testid='submit-order']//input[@name='username']//*[@aria-label='关闭']//*[@id='main']“唯一”不等于“稳定”,仍要确认它不是运行时随机生成。
//*[contains(@class,'button')]可能误命中 button-group,要结合业务范围并检查数量。
<div class="button primary large">//*[contains(concat(' ', normalize-space(@class), ' '), ' button ')]多个条件只有在确实需要消除歧义时再组合:
//input[@type='text' and @name='username']
条件不是越多越稳定。多写一个会变化的条件,反而多一个失效点。
三、文本定位:text() 和 . 到底差在哪?
文本定位最常见的坑,是页面看起来写着“提交”,但文本实际包在子节点中。
text() 只检查当前元素的直接文本节点;. 的字符串值会包含所有后代文本。
<div class="dialog-action">
<span>提交</span>
</div>//div[text()='提交']0 个结果div 自身没有值为“提交”的直接文本节点。
//div[normalize-space(.)='提交']1 个结果. 读取了子节点 span 中的文字,并清理首尾空白。
//button[normalize-space(.)='登录']//*[contains(normalize-space(.),'登录')]模糊文本的范围更宽,复杂页面中可能同时命中按钮、弹窗和外层容器。能精确匹配时,不要先用 contains()。
四、三个常用函数,分别处理什么变化?
函数的作用可以归纳成三类:匹配片段、匹配前缀、清理空白。
函数不是为了让 XPath 变复杂,而是为了把不可控变化压缩成稳定片段。
contains(@class,'active')适合动态属性中仍保留固定业务片段的情况。
starts-with(@name,'user')后缀会变化,但前缀仍能表达同一类字段。
//button[normalize-space(.)='提交']处理换行、缩进和多余空格,比直接比较文本更稳定。
五、关系定位:先找到锚点,再沿关系轴移动
复杂后台页面里,目标元素不一定有好用属性,但它附近通常有稳定标签、标题或按钮。此时不要从页面根部硬写长路径,而是先找已知元素,再沿 DOM 关系移动。
关系轴表达“向上找父级”或“在同一层向后找兄弟”,比依赖固定位置更容易读懂。
<div class="product-card">商品卡片<span>价格</span>已知文本<input name="price">目标输入框<button>购买</button>已知按钮//button[normalize-space(.)='购买']/ancestor::div[1]从“购买”按钮回到它所在的第一层 div 卡片。
//span[normalize-space(.)='价格']/following-sibling::input从“价格”标签移动到同一父级下后面的输入框。
关系定位依赖页面结构,但它依赖的是“业务关系”,不是“第几个 div”。仍然要在页面改版后复查锚点与目标是否保持同一层级关系。
六、表格定位:先锁定行,再读取列
表格定位最清晰的方式,是把动作拆成两步:先通过业务文本找到目标行,再从该行读取指定列。
先把搜索范围缩小到一行,再取列;不要直接依赖整张表里的绝对位置。
| 商品 | 价格 | 状态 |
|---|---|---|
| 键盘 | 100 | 在售 |
| 鼠标 | 50 | 在售 |
//tr[td[normalize-space(.)='键盘']]//tr[td[normalize-space(.)='键盘']]/td[2]如果列顺序也可能变化,应该先根据表头确定列号,再组合 XPath,而不是永久写死 td[2]。
七、动态页面:匹配稳定片段,不复制随机值
现代前端经常为 class 或 id 生成随机前后缀。复制一次完整值,往往只能保证“现在能用”。
先对比刷新前后,识别稳定片段;再结合业务范围和唯一性验证。
<div class="a8f31_button"><div class="b91kx_button">button//*[@class='a8f31_button']//section[@data-module='actions']//*[contains(@class,'_button')]contains() 只是放宽匹配条件,不会自动让选择器变稳定。稳定片段是否唯一、所在范围是否正确,仍然需要调试验证。
八、XPath 调试:先看结果是 0、1 还是多个
调试 XPath 时,第一步不是点击,而是查看匹配数量和实际节点。
$x("//button[normalize-space(.)='登录']")检查语法、加载状态、iframe 和弹窗上下文。
继续确认文本、属性和业务语义是否正确。
补充业务容器或必要条件,不直接点击第一个。
在影刀或其它 RPA 工具中,也应该先获取候选集合并检查数量。把“0 个”和“多个”变成明确错误,比静默点击错误元素更安全。
九、实际项目中,按什么顺序选择定位方式?
定位优先级取决于稳定性和业务语义,不取决于语法看起来是否高级。
先用最稳定的信息完成定位;只有前一层不足以唯一命中时,才进入下一层。
- 1稳定业务 / 测试属性优先
data-*、name、aria-label - 2稳定且有意义的 id确认不是随机生成,并且页面中唯一稳定
- 3固定文本使用可读
normalize-space(.)处理空白与子节点 - 4业务范围 + 关系轴父级、同级、表格行列等结构关系需复查结构
- 5class 片段匹配限定范围,并验证 0 / 1 / 多个较宽松
- 6坐标点击受缩放、窗口大小和元素位置影响最后手段
如果一个定位必须依赖位置,优先回头确认页面是否存在更稳定的父容器、业务字段或可读文本。
十、常用 XPath TOP 10
最后把高频写法按用途收拢。复制前先替换示例值,并在实际页面验证匹配数量。
//*[@id='xxx']//*[contains(@class,'xxx')]//*[normalize-space(.)='xxx']//*[contains(normalize-space(.),'xxx')]//input[@placeholder='xxx']//div[@data-module='xxx']//span//tr[td[normalize-space(.)='xxx']]//xxx/ancestor::div[1]//xxx/following-sibling::xxx(//xxx)[last()]在影刀、浏览器自动化和爬虫项目中,能写出稳定定位,比写出复杂 XPath 更重要。