XPath 语法速查表 - XML/HTML 选择器语法大全
从 XML/HTML 里定位节点,正则几乎总是最差的选择:文档一换行缩进,模式就崩;而 XPath 是为结构化树设计的,选择器同时表达位置、属性和关系,换了排版仍成立。基础路径、谓词过滤、轴导航、常用函数、通配与运算这几组语法,最终都落到爬虫定位与配置文件解析两类场景上。判断选择器写得对不对有一条实用标准:能不能不依赖文本内容就命中——用谓词数下标(第二个匹配项)、用属性判断(带 class 的元素)、用轴表达关系(父节点、兄弟节点),比匹配具体文案稳定得多,因为文案会变而结构不会。性能上的判断同样明确:后代轴在文档较大时开销明显,定位已知层次时优先用子节点轴配合精确路径。另外要记住版本边界:XPath 1.0 没有正则匹配,需要模糊匹配时用 contains 组合,或换到支持 XPath 2.0 的处理器。
基础路径 7
///.../bookstore/book//bookbook/title谓词过滤 6
//book[1]//book[last()]//book[position()<3]//book[@lang="zh"]//book[price>35]//book[title]轴 Axes 7
ancestor::divparent::node()child::titledescendant::pfollowing-sibling::divpreceding-sibling::h2self::a常用函数 8
contains(@class, "active")starts-with(text(), "Hello")normalize-space()string-length(text())count(//book)concat("a", "-", "b")substring("hello", 1, 3)translate("abc", "abc", "ABC")通配与运算 6
*@*node()//a | //b//div[@id="x" and @class="y"]//div[@class="a" or @class="b"]命令示例
谓词组合:按条件过滤后再取第 N 个
//book[@lang="zh"]
//book[price>35]
//book[title]
//book[last()]第三、四行是两种常用过滤:含有 title 子元素、以及最后一个 book。下标类谓词都从 1 起数,且作用在过滤后的集合上。属性比较要求值可转成数字,price 写成带货币符号的字符串时表达式无效。
轴导航:从当前节点往上往外找
ancestor::div
following-sibling::div
preceding-sibling::h2ancestor 向上取所有匹配的祖先,following-sibling 与 preceding-sibling 取当前位置之后与之前的兄弟节点。它们在「相邻区块」场景里比 // 更快也更准,因为不必遍历整棵子树。
常用函数:清洗文本与取聚合值
normalize-space()
string-length(text())
count(//book)
concat("a", "-", "b")normalize-space 去掉首尾空白并压缩中间空白,抓取的 HTML 常有换行与缩进,不清洗就会导致文本匹配失败。count 返回数字,可用于先判断节点是否存在再继续取深层字段。
通配与逻辑运算组合定位
//div[@id="x" and @class="y"]
//div[@class="a" or @class="b"]
//a | //band 要求同时满足,or 是择一;竖线是并集,会把两边匹配到的节点合并去重后按文档顺序返回。需要「恰好是 a 而非包含 a」时用等值判断,需要宽松命中时换成 contains。
常见坑与注意事项
- 谓词下标从 1 开始且作用于过滤后的结果集,顺序写反(先下标后谓词)语义会完全不同,改表达式前先确认这一层。
- // 出现在深层嵌套里会显著放大遍历开销,文档较大时优先用精确路径加属性谓词定位。
- XPath 1.0 不支持正则,需要模糊匹配时用 contains 组合,或确认目标运行时是否支持 2.0 再用匹配函数。
- class 属性几乎总是复合的,等值判断匹配不到;用 contains 虽能命中,但会连带匹配 inactive 这类包含关系,需组合其它条件收窄。
- 文本节点里的换行与缩进会让 starts-with 与直接文本匹配失效,先用 normalize-space 清洗再比较。
提示
- // 比 / 慢很多,大范围文档优先用精确路径。
- contains(@class, "x") 会匹配 "x y" 这种复合 class,比精确匹配更实用。
- XPath 1.0 不支持正则,需要正则匹配得上 XPath 2.0 或用 contains 组合。
- 定位"第 N 个匹配项"用谓词下标并从 1 起数:(//li[2])是第二个;索引按过滤后的结果集计算,先写谓词再写下标,顺序反了结果完全不同。
- 抓取多个字段时把根节点先固定成一个变量,再在其下取相对路径,可避免每个表达式都从 // 重新扫描整棵树的重复开销。
- 解析配置文件时优先用元素名做路径而不是属性值,元素名是 schema 的公开契约,属性更可能是实现细节,改动时先变属性。
常见问题
// 和 / 到底差在哪?为什么说 // 慢?
路径语义不同。/ 是从文档根出发的绝对路径,只沿一条确定的分支向下;而 // 是「从任意位置递归寻找后代」的缩写,每次求值都要遍历子树的剩余部分。也就是说 /bookstore/book 只看根下一个 book 再看它的子元素,而 //book 要检查文档里所有节点。文档小时差别可以忽略,节点上到万级后,// 出现在多层嵌套里会成倍放大遍历次数。所以已知层级就写精确路径,不确定层级再用 //,并尽量把它放在表达式开头一次定位,后续用相对路径展开。
谓词下标从 0 还是从 1 开始?//li[2] 指的是哪个?
从 1 开始,//li[2] 是第二个匹配的 li,不是第三个,这点和数组下标直觉相反。还有两个容易出错的细节:第一,下标是按过滤后的结果集计算的,所以先写谓词再写下标,//li[@class="x"][2] 是「带 x 类的 li 中的第二个」,顺序反过来语义就变了;第二,[] 里可以放多个条件表示同时满足,@price>35 这类数值比较要求属性本身可转成数字,否则表达式不成立而不是返回空集。
想按 class 精确匹配,为什么 contains 更实用?
因为真实 HTML 里的 class 通常是复合的:class="btn btn-primary active" 里三个类并存。contains(@class, "active") 判断的是属性值是否包含某个子串,能命中这种复合写法;而精确等值判断在整个属性值完全等于 active 时才成立,几乎永远匹配不到。因此 contains 是实际抓取中的默认选择。代价是它也会匹配到 active-tab、inactive 这类包含关系,所以它比精确匹配宽松、但不精确;需要更严时组合其它条件一起用。
XPath 1.0 里想做正则匹配怎么办?
1.0 没有正则函数,这是版本边界而不是写法问题。可行的替代有三种:用 contains 配合精确长度判断做包含匹配,能覆盖大多数实际需求;用 substring 做位置化裁剪,比较适合编号、日期这类固定格式;换到支持 XPath 2.0 的处理器(很多语言的新版库已支持),那里才有 matches 之类的正则能力。选实现前先确认目标运行时版本,否则同一表达式在两个环境下行为不一致。
抓多个字段时,怎么写才不至于每个表达式都扫全文档?
先把根节点固定成一个变量,再在它下面取相对路径。比如先拿到商品列表节点,之后的字段表达式全部相对该节点求值,遍历范围被限制在那一小段里,而不是每个字段都从 // 重新扫描整棵树。解析配置文件时还有一条经验:优先用元素名做路径而不是属性值,因为元素名通常是 schema 的公开契约、属性更可能是实现细节,改动时先变的是属性。
由 巧匠 维护
公开更新于 2026年7月21日,内容持续校对官方文档。