教程

XPath 实战速查:从基础语法到稳定元素定位

把 XPath 当作一条穿过 DOM 的路径,直观看懂基础语法、属性与文本筛选、关系轴、表格定位和稳定性优先级。

作者:黄撑 更新于 2026-08-22
本文目录 10 节 · 点击展开
01 · XPATH ROUTE MAPXPath 路径地图

从宽到窄,逐步缩小范围;目标不是“能找到”,而是“稳定且唯一地找到”。

1
锚点

从稳定的父级或业务区域开始。

//form[@id='loginForm']
2
后代搜索

沿 DOM 结构向下寻找候选。

.//button
3
条件筛选

用属性或文本过滤候选元素。

[@type='submit']
4
关系定位

借助父级、同级关系找到关联元素。

following-sibling::input
5
唯一目标

结果可见、可交互,并且只命中一个。

(.//button)[1]
DOM 结构样本蓝色行为当前目标
  1. <html>
  2.   <body>
  3. <main id="app">
  4. <form id="loginForm">
  5. <div class="field">
  6.           <label>用户名</label>
  7. <input id="username" />
  8.         </div>
  9. <div class="actions">
  10. <button type="submit">登录</button>
  11.         </div>
  12.       </form>
  13.     </main>
  14.   </body>
  15. </html>
最终路径//form[@id='loginForm']//button[@type='submit']
定位稳定性优先级越往下,维护风险越高
稳定业务属性

优先使用具备业务或测试语义的属性。

//*[@data-testid='login']
结构 + 属性组合

单一条件不够时,再补必要范围。

//form[@id='loginForm']//button
文本或部分匹配

可读性高,但要考虑文案变化和误匹配。

//button[contains(.,'登录')]
坐标点击(最后手段)

页面缩放、窗口和布局变化都会让它失效。

click(560, 420)
先确定范围,再选择特征,最后验证唯一性。路径越能表达业务含义,后期越容易维护。

一、五个基础符号,分别控制路径的哪一段?

基础语法不需要孤立记忆。把它们放回一条路径中,就能看见每个符号负责什么。

02 · PATH TOKENS五个符号,一条路径

起点、搜索范围、当前上下文和属性条件,共同决定 XPath 从哪里走到哪里。

/从根开始

绝对路径从文档根节点逐层向下。

//搜索后代

从当前上下文中寻找任意深度的后代节点。

.当前节点

表示当前上下文,也可读取当前节点完整字符串值。

..父节点

从当前节点返回上一层父节点。

@属性

在条件中读取 id、name、data-* 等属性。

不优先 · 绝对路径/html/body/div[2]/div[1]/form/button

依赖完整层级和位置,页面插入一层容器就可能失效。

更稳定 · 语义路径//form[@id='loginForm']//button[@type='submit']

直接表达“登录表单里的提交按钮”,结构变化时更容易维护。

最常见的候选搜索很简单:

//button

它会找到当前文档中的所有按钮。若已经拿到某个容器元素,则用 .// 从当前容器继续搜索,避免重新扫整个页面:

.//input

二、属性定位:先找不会轻易变化的业务特征

属性定位的关键不是“看到什么就写什么”,而是判断哪个属性真正表达元素用途。

03 · ATTRIBUTE ANCHORS属性就是路径的锚点

稳定且有业务含义的属性优先;展示文案和随机样式名只适合作为补充条件。

优先业务 / 测试属性//*[@data-testid='submit-order']//input[@name='username']//*[@aria-label='关闭']
确认稳定后使用唯一且有意义的 id//*[@id='main']

“唯一”不等于“稳定”,仍要确认它不是运行时随机生成。

需要验证class 片段匹配//*[contains(@class,'button')]

可能误命中 button-group,要结合业务范围并检查数量。

class 是多个独立词时<div class="button primary large">
严格匹配其中一个 class token//*[contains(concat(' ', normalize-space(@class), ' '), ' button ')]

多个条件只有在确实需要消除歧义时再组合:

//input[@type='text' and @name='username']

条件不是越多越稳定。多写一个会变化的条件,反而多一个失效点。

三、文本定位:text(). 到底差在哪?

文本定位最常见的坑,是页面看起来写着“提交”,但文本实际包在子节点中。

04 · TEXT MATCHING看得见的文字,不一定是直接文本

text() 只检查当前元素的直接文本节点;. 的字符串值会包含所有后代文本。

页面结构
<div class="dialog-action">
<span>提交</span>
</div>
直接文本//div[text()='提交']0 个结果

div 自身没有值为“提交”的直接文本节点。

完整字符串值//div[normalize-space(.)='提交']1 个结果

. 读取了子节点 span 中的文字,并清理首尾空白。

精确文本//button[normalize-space(.)='登录']
包含文本//*[contains(normalize-space(.),'登录')]

模糊文本的范围更宽,复杂页面中可能同时命中按钮、弹窗和外层容器。能精确匹配时,不要先用 contains()

四、三个常用函数,分别处理什么变化?

函数的作用可以归纳成三类:匹配片段、匹配前缀、清理空白。

05 · FUNCTION TRACK三个函数,解决三种变化

函数不是为了让 XPath 变复杂,而是为了把不可控变化压缩成稳定片段。

01contains()值里包含稳定片段contains(@class,'active')

适合动态属性中仍保留固定业务片段的情况。

02starts-with()值的开头固定starts-with(@name,'user')

后缀会变化,但前缀仍能表达同一类字段。

03normalize-space()清理首尾与连续空白//button[normalize-space(.)='提交']

处理换行、缩进和多余空格,比直接比较文本更稳定。

五、关系定位:先找到锚点,再沿关系轴移动

复杂后台页面里,目标元素不一定有好用属性,但它附近通常有稳定标签、标题或按钮。此时不要从页面根部硬写长路径,而是先找已知元素,再沿 DOM 关系移动。

06 · RELATION AXES把已知元素当作锚点

关系轴表达“向上找父级”或“在同一层向后找兄弟”,比依赖固定位置更容易读懂。

DOM 结构
<div class="product-card">商品卡片
<span>价格</span>已知文本
<input name="price">目标输入框
<button>购买</button>已知按钮
向上ancestor:: 找最近父级//button[normalize-space(.)='购买']/ancestor::div[1]

从“购买”按钮回到它所在的第一层 div 卡片。

向后following-sibling:: 找后续兄弟//span[normalize-space(.)='价格']/following-sibling::input

从“价格”标签移动到同一父级下后面的输入框。

关系定位依赖页面结构,但它依赖的是“业务关系”,不是“第几个 div”。仍然要在页面改版后复查锚点与目标是否保持同一层级关系。

六、表格定位:先锁定行,再读取列

表格定位最清晰的方式,是把动作拆成两步:先通过业务文本找到目标行,再从该行读取指定列。

07 · TABLE ROUTE从业务值走到目标单元格

先把搜索范围缩小到一行,再取列;不要直接依赖整张表里的绝对位置。

商品价格状态
键盘100在售
鼠标50在售
1先锁定“键盘”所在行//tr[td[normalize-space(.)='键盘']]
2再读取这一行的第 2 列//tr[td[normalize-space(.)='键盘']]/td[2]
最终结果100

如果列顺序也可能变化,应该先根据表头确定列号,再组合 XPath,而不是永久写死 td[2]

七、动态页面:匹配稳定片段,不复制随机值

现代前端经常为 class 或 id 生成随机前后缀。复制一次完整值,往往只能保证“现在能用”。

08 · DYNAMIC CLASS变化的是随机段,保留的是业务段

先对比刷新前后,识别稳定片段;再结合业务范围和唯一性验证。

刷新前<div class="a8f31_button">
刷新后<div class="b91kx_button">
稳定片段button
不要复制完整随机值//*[@class='a8f31_button']
在明确范围内匹配稳定片段//section[@data-module='actions']//*[contains(@class,'_button')]

contains() 只是放宽匹配条件,不会自动让选择器变稳定。稳定片段是否唯一、所在范围是否正确,仍然需要调试验证。

八、XPath 调试:先看结果是 0、1 还是多个

调试 XPath 时,第一步不是点击,而是查看匹配数量和实际节点。

XPATH VALIDATION CONSOLE
Chrome DevTools
查询$x("//button[normalize-space(.)='登录']")
0未找到

检查语法、加载状态、iframe 和弹窗上下文。

1理想结果

继续确认文本、属性和业务语义是否正确。

2+范围过宽

补充业务容器或必要条件,不直接点击第一个。

在影刀或其它 RPA 工具中,也应该先获取候选集合并检查数量。把“0 个”和“多个”变成明确错误,比静默点击错误元素更安全。

九、实际项目中,按什么顺序选择定位方式?

定位优先级取决于稳定性和业务语义,不取决于语法看起来是否高级。

09 · STABILITY LADDER越接近业务语义,越值得优先使用

先用最稳定的信息完成定位;只有前一层不足以唯一命中时,才进入下一层。

  1. 1
    稳定业务 / 测试属性data-*namearia-label
    优先
  2. 2
    稳定且有意义的 id确认不是随机生成,并且页面中唯一
    稳定
  3. 3
    固定文本使用 normalize-space(.) 处理空白与子节点
    可读
  4. 4
    业务范围 + 关系轴父级、同级、表格行列等结构关系
    需复查结构
  5. 5
    class 片段匹配限定范围,并验证 0 / 1 / 多个
    较宽松
  6. 6
    坐标点击受缩放、窗口大小和元素位置影响
    最后手段

如果一个定位必须依赖位置,优先回头确认页面是否存在更稳定的父容器、业务字段或可读文本。

十、常用 XPath TOP 10

最后把高频写法按用途收拢。复制前先替换示例值,并在实际页面验证匹配数量。

01稳定 id//*[@id='xxx']
02class 片段//*[contains(@class,'xxx')]
03精确文本//*[normalize-space(.)='xxx']
04包含文本//*[contains(normalize-space(.),'xxx')]
05输入框属性//input[@placeholder='xxx']
06先限范围//div[@data-module='xxx']//span
07表格目标行//tr[td[normalize-space(.)='xxx']]
08最近父级//xxx/ancestor::div[1]
09后续兄弟//xxx/following-sibling::xxx
10最后一个(//xxx)[last()]
最终判断XPath 的核心不是记住所有函数,而是理解网页结构,并把定位过程写成一条稳定、可读、可验证的路径。

在影刀、浏览器自动化和爬虫项目中,能写出稳定定位,比写出复杂 XPath 更重要。