XPath(XML Path Language)是一门在 XML/HTML 文档中查找和定位节点的语言。它最初用于 XSLT 和 XPointer,现在最广泛的应用是网页爬虫(如 Scrapy、lxml)和自动化测试(如 Selenium、Playwright)。
常见错点:
索引从 1 开始,不是 0。
//div[1] 和 (//div)[1] 不同:
-
//div[1]:每个父节点下的第一个 div。
-
(//div)[1]:整个文档中第一个 div。
text() 只匹配直接文本子节点,不包含后代文本。要包含所有后代文本用 string(.) 或 .。
HTML 不区分大小写,XML 区分。浏览器解析 HTML 时标签名通常小写。
一、XPath 的核心概念:节点树
XPath 把 XML/HTML 文档看作一棵节点树,节点类型主要有:
| 根节点 | 整棵树的根 | / |
| 元素节点 | HTML 标签 | <div>、<a> |
| 属性节点 | 标签的属性 | class="item" |
| 文本节点 | 标签内的文字 | 苹果 |
| 注释节点 | 注释 | <!– xxx –> |
| 命名空间节点 | 带命名空间的节点 | ns:tag |
节点之间的关系:
1. 父节点(Parent)
定义:当前节点的直接上层节点。每个元素节点和属性节点都有且仅有一个父节点。根节点(/)没有父节点。
示例:
<book>
<title>Python编程</title>
</book>
-
<title> 的父节点是 <book>。
-
XPath 写法://title/parent::* 或 //title/.. 结果都是 <book>。
2. 子节点(Child)
定义:当前节点的直接下层节点。子节点可以是元素节点、文本节点、注释节点等,但不包含孙子节点。
示例:
<book>
<title>Python编程</title>
<author>张三</author>
</book>
-
<book> 的子节点是 <title> 和 <author>。
-
XPath 写法:/book/child::title 等价于 /book/title。
3. 兄弟节点(Sibling)
定义:拥有同一个父节点的其他节点。分为两种:
-
前置兄弟(preceding-sibling):当前节点之前的同级节点。
-
后置兄弟(following-sibling):当前节点之后的同级节点。
示例:
<book>
<title>Python编程</title>
<author>张三</author>
<price>59.9</price>
</book>
-
<author> 的前置兄弟是 <title>,后置兄弟是 <price>。
-
XPath 写法:
-
找前置兄弟://author/preceding-sibling::* → 结果 <title>
-
找后置兄弟://author/following-sibling::* → 结果 <price>
-
4. 祖先节点(Ancestor)
定义:当前节点的所有上层节点,包括父节点、祖父节点,一直追溯到根节点。
示例:
<library>
<shelf>
<book>
<title>Python编程</title>
</book>
</shelf>
</library>
-
<title> 的祖先节点是 <book>、<shelf>、<library>(以及根节点)。
-
XPath 写法://title/ancestor::* → 结果包含 <book>、<shelf>、<library>。
-
注意与 ancestor-or-self 的区别:后者会额外包含当前节点自身。
5. 后代节点(Descendant)
定义:当前节点的所有下层节点,包括子节点、孙节点,一直向下延伸到底。
示例:
<library>
<shelf>
<book>
<title>Python编程</title>
</book>
</shelf>
</library>
-
<library> 的后代节点是 <shelf>、<book>、<title>。
-
XPath 写法://library/descendant::title → 结果 <title>(等价于 //library//title)。
-
注意与 descendant-or-self 的区别:后者会额外包含当前节点自身。
补充:容易混淆的几组概念
| 父节点 | 否 | 仅上一层 | parent |
| 子节点 | 否 | 仅下一层 | child |
| 兄弟节点 | 否 | 同级 | preceding-sibling / following-sibling |
| 祖先节点 | 否 | 所有上层 | ancestor |
| 后代节点 | 否 | 所有下层 | descendant |
二、路径表达式基础
1. 绝对路径 vs 相对路径
| /html/body/div | 从根节点开始的绝对路径 |
| //div | 从任意位置匹配的相对路径(全文档搜索) |
| ./div | 当前节点下的 div |
| ../div | 父节点下的 div |
2. 常用符号
| / | 子节点(或根) |
| // | 后代节点(任意层级) |
| . | 当前节点 |
| .. | 父节点 |
| @ | 属性 |
| * | 通配符,匹配任意元素 |
| @* | 任意属性 |
| text() | 文本节点 |
| node() | 任意节点 |
示例
/html/body/div # body 下所有直接子 div
//div # 文档中所有 div
//div/p # 所有 div 的直接子 p
//div//p # 所有 div 的后代 p
//div/* # div 下所有直接子元素
//div[@class] # 有 class 属性的 div
//div[@class="item"] # class 正好等于 item 的 div
//a/@href # 所有 a 的 href 属性值
//p/text() # 所有 p 的文本
三、谓语(Predicates)
谓语写在 [] 中,用来过滤节点。
1. 按位置
一)、基础位置谓语
| [1] | 第 1 个(索引从 1 开始) |
| [2] | 第 2 个 |
| [last()] | 最后一个 |
| [last()-1] | 倒数第 2 个 |
| [last()-2] | 倒数第 3 个 |
| [position()=1] | 等价于 [1] |
| [position()=last()] | 等价于 [last()] |
| [position()<3] | 前 2 个(位置 1、2) |
| [position()<=3] | 前 3 个 |
| [position()>2] | 第 3 个及之后的所有 |
| [position()>=2] | 第 2 个及之后的所有 |
| [position()!=2] | 除了第 2 个以外的所有 |
二)、按位置奇偶筛选
| [position() mod 2 = 1] | 奇数位置(第 1、3、5…个) |
| [position() mod 2 = 0] | 偶数位置(第 2、4、6…个) |
例子://ul/li[position() mod 2 = 0] → 选取所有偶数位置的 li
三)、范围筛选(XPath 1.0 无 .. 范围写法)
| [position()>=2 and position()<=5] | 第 2 到第 5 个 |
| [position()>2 and position()<5] | 第 3、4 个 |
| [position()<3 or position()>5] | 前 2 个或第 6 个之后 |
四)、按文本内容/属性间接定位位置
位置谓语可以和其他条件组合,间接实现“按内容找位置”:
| //li[text()="首页"] | 文本为“首页”的 li(不管它是第几个) |
| //li[contains(text(),"下一页")] | 文本包含“下一页”的 li |
| //tr[td[text()="张三"]] | 包含“张三”的那一行 tr |
| //div[@class="item"][3] | class 为 item 的 div 中的第 3 个 |
| //div[@class="item" and position()<=3] | class 为 item 且位置在前 3 的 div |
五)、嵌套位置(父子层级同时限制)
| //ul[2]/li[1] | 第 2 个 ul 下的第 1 个 li |
| //ul/li[1] | 每个 ul 下的第 1 个 li |
| (//ul/li)[1] | 整个文档中所有 ul/li 里的第 1 个 |
| //table/tr[2]/td[3] | 第 2 行第 3 列 |
| //table/tr[last()]/td[last()] | 最后一行最后一列 |
2. 按属性
一)、基础属性匹配
| //input[@type] | 拥有 type 属性的 input |
| //input[@type="text"] | type 属性值完全等于 text |
| //input[@type!="text"] | type 属性值不等于 text |
| //input[not(@type)] | 没有 type 属性的 input |
| //input[@type and @name] | 同时有 type 和 name |
| //input[@type or @name] | 有 type 或 name |
注意:[@type] 只判断“存在”,[@type="text"] 才判断具体值。
二)、部分匹配(模糊匹配)
| //div[contains(@class,"item")] | class 属性包含 item |
| //a[starts-with(@href,"http")] | href 属性以 http 开头 |
| //a[ends-with(@href,".pdf")] | href 属性以 .pdf 结尾 |
| //a[substring-after(@href,"://")="example.com"] | :// 之后的部分等于 example.com |
| //a[substring-before(@href,"?")="http://a.com"] | ? 之前的部分等于该值 |
三)、属性值的多条件组合
| //input[@type="text" and @name="user"] | 两个属性同时满足 |
| //input[@type="text" or @type="email"] | 满足其一 |
| //input[@type="text" and not(@disabled)] | 有 type 且无 disabled |
| //input[@type="text"][@name="user"][@id="uid"] | 多个谓语连写,等价于 and |
| //div[contains(@class,"a") and contains(@class,"b")] | class 同时含 a 和 b |
四)、按属性名(通配符)
| //div[@*] | 拥有任意属性的 div |
| //div[not(@*)] | 没有任何属性的 div |
| //div[@*="x"] | 任意属性的值等于 x |
| //*[@id] | 文档中所有带 id 属性的元素 |
五)、按属性值去空白比较
| //div[normalize-space(@class)="item"] | 去首尾空白后比较 |
| //div[normalize-space(@title)="首页"] | 同上 |
| //div[@class="item active"] | 必须完全匹配整个字符串 |
| //div[contains(@class,"item")] | 只要有 item 即可 |
六)、大小写不敏感匹配属性值
| //input[translate(@type,"TEXT","text")="text"] | 大小写不敏感 |
| //a[translate(@href,"HTTP","http")="http://a.com"] | 同上 |
七)、属性值长度筛选
| //input[string-length(@value)>5] | value 属性长度大于 5 |
| //input[string-length(@value)=0] | value 为空 |
| //input[string-length(normalize-space(@class))=4] | 去空白后 class 长度恰好 4 |
八)、属性值数字比较(属性值要转数字)
| //div[@data-index="1"] | 字符串比较 |
| //div[number(@data-index)>5] | 转数字后比较 |
| //div[@data-index>5] | XPath 会隐式转数字(部分环境可用) |
| //div[number(@data-price)>=100] | 价格大于等于 100 |
示例:
1. //input[@type="text"]
含义:选取文档中所有 type 属性等于 "text" 的 input 元素。
用途:在表单里定位所有文本框输入框。
HTML 示例:
<input type="text" name="username">
<input type="password" name="pwd">
<input type="text" name="email">
结果:匹配第 1 个和第 3 个 input。
2. //input[@type="text" and @name="user"]
含义:选取同时满足两个条件的 input —— type="text" 并且 name="user"。
用途:精确定位某个具体输入框,避免匹配到其他文本框。
HTML 示例:
<input type="text" name="user">
<input type="text" name="email">
结果:只匹配第 1 个。
补充:and 是逻辑运算符,要求两边的条件都为真。
3. //input[@type="text" or @type="email"]
含义:选取 type="text" 或者 type="email" 的 input。
用途:一次性匹配多种类型的输入框。
HTML 示例:
<input type="text" name="a">
<input type="email" name="b">
<input type="password" name="c">
结果:匹配前两个。
补充:or 只要一边为真即可。
4. //a[@href]
含义:选取所有带有 href 属性的 a 元素。
用途:过滤掉没有链接地址的 <a>(例如仅作锚点或占位的 <a>)。
HTML 示例:
<a href="https://example.com">链接</a>
<a name="top">锚点</a>
<a>无属性</a>
结果:只匹配第 1 个。
补充:@href 只判断属性是否存在,不关心它的值是什么。
5. //div[not(@class)]
含义:选取没有 class 属性的 div 元素。
用途:排除掉带样式的 div,常用于定位结构性的容器。
HTML 示例:
<div class="container">有 class</div>
<div id="main">无 class</div>
<div>无 class 无 id</div>
结果:匹配第 2 个和第 3 个。
补充:not() 是布尔函数,对条件取反。
常见的属性筛选写法
| [@attr] | 拥有该属性 |
| [@attr="值"] | 属性等于某值 |
| [@attr!="值"] | 属性不等于某值 |
| [not(@attr)] | 没有该属性 |
| [@a="x" and @b="y"] | 多条件同时满足 |
| [@a="x" or @b="y"] | 满足任一条件 |
| [contains(@attr,"子串")] | 属性值包含子串 |
| [starts-with(@attr,"前缀")] | 属性值以某串开头 |
3. 按文本
一)、直接匹配文本节点
1. 精确匹配
| //button[text()="登录"] | 文本完全等于“登录” |
| //button[.="登录"] | . 表示当前节点的字符串值,效果同上 |
| //button[string()="登录"] | string() 转字符串后比较,等价 |
2. 包含匹配
| //button[contains(text(),"登")] | 文本包含“登” |
| //button[contains(.,"登")] | . 写法,更常用 |
| //button[contains(string(),"登")] | 显式转字符串 |
3. 开头/结尾匹配
| //p[starts-with(text(),"Hello")] | 文本以 Hello 开头 |
| //p[starts-with(.,"Hello")] | 同上 |
| //p[ends-with(.,"world")] | 仅 XPath 2.0+,1.0 不支持 |
| //p[substring(., string-length(.)-4) = "world"] | 取最后 5 个字符比较 |
二)、去除空白后再匹配
HTML 文本经常带空格、换行,直接 text()="登录" 可能匹配不到。
| //button[normalize-space(text())="登录"] | 去首尾空白并合并中间空白后比较 |
| //button[normalize-space(.)="登录"] | 同上 |
| //button[normalize-space()="登录"] | 省略参数默认对当前节点 |
例子:
<button> 登录 </button>
-
//button[text()="登录"] → 匹配不到
-
//button[normalize-space(text())="登录"] → 匹配到
三)、按文本长度筛选
| //p[string-length(text())>10] | 文本长度大于 10 |
| //p[string-length(normalize-space(.))=4] | 去空白后长度恰好为 4 |
| //p[string-length(text())<3] | 文本长度小于 3 |
例子:
//a[string-length(normalize-space(.))=2] → 选取文本恰好 2 个字的 a。
四)、按文本包含关键词的多种组合
| //div[contains(.,"Python") and contains(.,"爬虫")] | 文本同时包含 Python 和 爬虫 |
| //div[contains(.,"Python") or contains(.,"Java")] | 包含 Python 或 Java |
| //div[not(contains(.,"广告"))] | 不包含“广告” |
| //div[contains(text(),"错误") and not(contains(text(),"警告"))] | 组合条件 |
五)、对“后代文本”进行匹配
text() 只匹配直接文本子节点,不包含后代元素的文本。要包含所有后代文本,用 . 或 string(.)。
| //div[text()="Hello"] | 只匹配 div 的直接文本节点恰好是 Hello |
| //div[.="Hello"] | 匹配 div 及其所有后代的合并文本值等于 Hello |
| //div[contains(.,"Hello")] | 匹配 div 及其后代文本中包含 Hello |
例子:
<div>Hello <span>World</span></div>
-
//div[text()="Hello"] → 匹配(直接文本是 "Hello")
-
//div[text()="Hello World"] → 不匹配(text() 只看直接文本)
-
//div[.="Hello World"] → 匹配(. 合并了后代文本)
-
//div[contains(.,"World")] → 匹配
六)、用 translate() 实现大小写不敏感匹配
XPath 1.0 没有 lower-case(),可用 translate() 替代。
| //p[translate(text(),"ABC","abc")="hello"] | 把大写字母转小写后比较 |
| //p[translate(.,"ABCDEFGHIJKLMNOPQRSTUVWXYZ","abcdefghijklmnopqrstuvwxyz")="hello"] | 完整大小写不敏感匹配 |
例子:
<p>Hello</p>
-
//p[text()="hello"] → 大小写不匹配
-
//p[translate(text(),"HELLO","hello")="hello"] → 匹配
七)、按文本选出的节点再做位置筛选
| //li[text()="首页"] | 文本为“首页”的 li |
| (//li[contains(text(),"页")])[1] | 包含“页”的 li 中的第 1 个 |
| //li[contains(.,"页")][last()] | 包含“页”的 li 中的最后一个 |
| //tr[td[text()="张三"]]/td[2] | 找到含“张三”的行,取第 2 列 |
八)、按子元素的文本筛选父节点
| //div[span[text()="价格"]] | 包含文本为“价格”的 span 的 div |
| //tr[td[contains(.,"总价")]] | 包含“总价”单元格的行 |
| //ul[li[text()="首页"]] | 包含文本为“首页”的 li 的 ul |
| //div[p[contains(text(),"错误")]] | 包含含“错误”文本的 p 的 div |
九)、按文本筛选属性值(不是元素文本)
如果文本在属性里,要用 @属性名 而不是 text()。
| //a[@title="首页"] | title 属性等于“首页” |
| //a[contains(@title,"首页")] | title 属性包含“首页” |
| //a[starts-with(@title,"首")] | title 属性以“首”开头 |
| //input[@value="提交"] | value 属性等于“提交” |
| //a[normalize-space(@title)="首页"] | 属性去空白后比较 |
十)、按文本模糊匹配正则(XPath 2.0+ / 3.0)
| //p[matches(.,"^Hello.*world$")] | 正则匹配 |
| //p[matches(.,"\\d{4}-\\d{2}-\\d{2}")] | 匹配日期格式 |
| //p[replace(.,"\\s+","")="Hello"] | 正则替换后再比较 |
4. 按子元素
一)、基础:判断是否包含某子元素
| //div[p] | 包含 p 子元素的 div |
| //div[not(p)] | 不包含 p 子元素的 div |
注意:[p] 只看直接子元素,不看后代。要包含后代用 [.//p]。
二)、同时包含多个子元素
| //div[p and span] | 同时包含 p 和 span 子元素 |
| //div[p or span] | 包含 p 或 span |
| //div[p][span] | 多谓语连写,等价于 and |
| //div[p and not(span)] | 包含 p 但不包含 span |
| //div[p and span and a] | 同时包含三种子元素 |
三)、按子元素数量筛选
| //div[count(p)=1] | 恰好有 1 个 p 子元素 |
| //div[count(p)>2] | 多于 2 个 p 子元素 |
| //div[count(p)>=2] | 至少 2 个 |
| //div[count(p)<3] | 少于 3 个 |
| //div[count(*)=0] | 没有任何子元素 |
| //div[count(*)=1] | 恰好 1 个子元素 |
| //ul[count(li)>5] | li 数量大于 5 的 ul |
四)、按子元素文本筛选
| //div[p[text()="价格"]] | 包含文本为“价格”的 p 的 div |
| //div[p[contains(.,"错误")]] | 包含文本含“错误”的 p 的 div |
| //tr[td[text()="张三"]] | 包含“张三”单元格的行 |
| //ul[li[contains(text(),"首页")]] | 包含“首页”li 的 ul |
| //div[span[normalize-space(.)="确定"]] | 去空白后文本为“确定”的 span 的父 div |
五)、按子元素属性筛选
| //div[a[@href]] | 包含有 href 的 a 的 div |
| //div[input[@type="text"]] | 包含 type=text 输入框的 div |
| //div[a[contains(@href,"/list")]] | 包含 href 含 /list 的 a 的 div |
| //form[input[@name="user"] and input[@name="pwd"]] | 同时包含用户名和密码输入框的表单 |
| //div[span[@class="price"]] | 包含 class=price 的 span 的 div |
六)、按子元素的子元素(多层级)
| //div[ul/li] | 包含 ul,且 ul 下含 li 的 div |
| //div[ul/li[text()="首页"]] | 包含“首页”li 的 div |
| //table[tr/td[text()="总价"]] | 包含“总价”单元格的表格 |
| //div[section/article/h2] | 多层嵌套路径作为条件 |
用路径当谓语时,ul/li 是相对于 div 的直接子级路径。
七)、后代 vs 直接子元素
| //div[p] | 只有直接子元素 p 才匹配 |
| //div[.//p] | 任意后代中有 p 就匹配 |
| //div[descendant::p] | 等价于 .//p |
| //div[child::p] | 等价于 [p] |
例子:
<div><section><p>Hello</p></section></div>
-
//div[p] → 不匹配(p 不是直接子元素)
-
//div[.//p] → 匹配(p 是后代)
-
//div[descendant::p] → 匹配
八)、按子元素位置筛选
| //div[p[1]] | 第一个子元素是 p 的 div(等价 //div[p and position()…],注意写法) |
| //div[*[1][self::p]] | 第一个子元素恰为 p |
| //div[last()[self::p]] | 最后一个子元素是 p |
| //div[count(p)=2 and count(span)=1] | 组合数量条件 |
//div[p[1]] 的准确含义是:div 含 p 子元素,且该 p 是它父节点下的第 1 个 p。要表达“第一个子元素是 p”,用 //div[*[1][self::p]]。
九)、子元素为空 / 非空
| //div[not(*)] | 没有任何子元素(空元素) |
| //div[not(node())] | 没有任何子节点(包括文本) |
| //div[not(text())] | 没有直接文本 |
| //div[text()] | 有直接文本 |
| //td[not(*)] | 空单元格 |
四、轴(Axes)——按节点关系定位
轴用于指定相对于当前节点的方向。语法:轴::节点测试[谓语]
| child | 子节点(默认轴) |
| parent | 父节点 |
| descendant | 所有后代 |
| ancestor | 所有祖先 |
| descendant-or-self | 后代及自身 |
| ancestor-or-self | 祖先及自身 |
| following-sibling | 之后的所有兄弟 |
| preceding-sibling | 之前的所有兄弟 |
| following | 文档中之后的所有节点 |
| preceding | 文档中之前的所有节点 |
| self | 自身 |
| attribute | 属性 |
| namespace | 命名空间 |
1. child
精确含义:当前节点的直接子元素(不包括文本节点、注释节点、属性)。
例子:
//div/child::p # div 的直接子 p
//div/p # 等价写法(child 是默认轴,可省略)
child:: 只包含元素,不包含文本。想选文本用 child::text()。
2. parent(父节点)
精确含义:当前节点的直接父节点,每个节点最多只有一个。
简写:..
例子:
//p/parent::div # p 的直接父 div
//p/.. # 等价写法
根节点的 parent 是空节点集,不会报错,但匹配不到东西。
3. descendant(所有后代)
精确含义:当前节点的所有层级的子节点、孙节点、曾孙节点……(不包含自己)。
简写://(如 //book//title)
例子:
//div/descendant::p # div 里所有层级的 p(不管嵌套多深)
//div//p # 等价写法
descendant:: 不包含自己;包含自己要用 descendant-or-self::。
4. ancestor(所有祖先)
含义:当前节点的所有上级节点,一直到根节点(不包含自己)。
例子:
//span/ancestor::div # span 的所有 div 祖先(可能多个)
//span/ancestor::* # span 的所有祖先元素
5. descendant-or-self(后代及自身)
精确含义:descendant + self 的并集,包含自己和所有后代。
例子:
//div/descendant-or-self::* # div 自己 + 所有后代元素
//div/descendant-or-self::p # div 自己 + 后代 p
6. ancestor-or-self(祖先及自身)
精确含义:ancestor + self 的并集,包含自己和所有祖先。
例子:
//span/ancestor-or-self::* # span 自己 + 所有祖先
//div/ancestor-or-self::div # div 自己 + 所有 div 祖先
7. following-sibling(之后的所有兄弟)
精确含义:当前节点之后的同级兄弟节点(同一个父节点下的后续兄弟元素),不包含自己,不包含各自的后代。
例子:
//label/following-sibling::input # label 后面的所有 input 兄弟
//li[1]/following-sibling::li # 第 1 个 li 后面的所有 li
只算同级,不算子级
8. preceding-sibling(之前的所有兄弟)
精确含义:当前节点之前的同级兄弟节点,不包含自己,不包含各自的后代。
例子:
//input/preceding-sibling::label # input 前面的所有 label
//li[last()]/preceding-sibling::li # 最后一个 li 前面的所有 li
9. following(文档中之后的所有节点)
精确含义:当前节点结束标记之后的所有节点(不限于同级,包含其他分支),不包含自己节点的后代、不包含自己。
例子:
//h2/following::p[1] # h2 之后文档中的第 1 个 p(不限同级)
10. preceding(文档中之前的所有节点)
精确含义:当前节点开始标记之前的所有节点(不限于同级),不包含祖先、不包含自己及其自己的后代。
例子:
//h2/preceding::p[1] # h2 之前文档中的第 1 个 p
11. self(自身)
精确含义:当前节点本身。
简写:.(点)
例子:
//div/self::div # 匹配 div 自己
//div/self::* # 等价于 //div
//p[self::p] # 常用于谓语中判断自己是 p
单独用没意义,主要用于 ancestor-or-self、descendant-or-self 或在谓语中判断。
12. attribute(属性)
精确含义:当前节点的所有属性节点(不是元素)。
简写:@
例子:
//div/attribute::class # 等价于 //div/@class
//div/attribute::* # div 的所有属性
13. namespace(命名空间)
精确含义:当前节点的所有命名空间节点(极少用)。
总示例:
<?xml version="1.0" encoding="UTF-8"?>
<catalog>
<book id="b1" lang="en">
<title>XPath Guide</title>
<author>Alice</author>
<price>30</price>
</book>
<book id="b2" lang="zh">
<title>Python爬虫</title>
<author>Bob</author>
<price>50</price>
</book>
<magazine id="m1">
<title>Tech Weekly</title>
</magazine>
</catalog>
接下来,以 <book id="b1"> 为当前节点,依次讲解 13 个轴。
//book[@id="b1"]/child::title # 匹配 <title>XPath Guide</title>,在显示节点时,会默认显示这个元素节点内部的文本内容,这是展示效果,不是选中了文本节点。
//book[@id="b1"]/parent::* # 匹配 <catalog>
//book[@id="b1"]/descendant::* # book(b1) 内所有后代元素<title> <author><price>
//title/ancestor::* # title 的所有祖先(book、catalog)
//book[@id="b1"]/descendant-or-self::* # book(b1) 自己 + 该节点下的所有后代
//book[@id="b1"]/ancestor-or-self::* # book(b1) 自己 + 所有祖先
//book[@id="b1"]/following-sibling::* # b1 后面的所有兄弟:<book(b2)> <magazine>
//book[@id="b2"]/preceding-sibling::* # b2 前面的兄弟 <book(b1)>
//book[@id="b1"]/following::* # b1 结束标记之后的所有节点: <book(b2)>及其子节点、<magazine>及其子节点
//magazine/preceding::* # magazine 之前的所有节点
//book[@id="b1"]/self::* # 匹配 book b1 自己
五、函数大全
XPath 函数分几大类,以下是最常用的。
1. 字符串函数
| contains(s, sub) | s 是否包含 sub | contains(text(),"abc") |
| starts-with(s, pre) | s 是否以 pre 开头 | starts-with(@href,"http") |
| ends-with(s, suf) | 是否以 suf 结尾(XPath 2.0+) | ends-with(@href,".pdf") |
| string-length(s) | 长度 | string-length(text())>5 |
| normalize-space(s) | 去首尾空白并合并中间空白 | normalize-space(text())="Hi" |
| substring(s, start, len) | 截取 | substring(text(),1,3) |
| substring-before(s, sub) | sub 之前的部分 | substring-before(@href,"?") |
| substring-after(s, sub) | sub 之后的部分 | substring-after(@href,"?") |
| translate(s, from, to) | 字符替换 | translate(text(),"ABC","abc") |
| concat(s1, s2, …) | 拼接 | concat(@first," ",@last) |
| string(s) | 转字符串 | string(//div) |
| upper-case(s) | 转大写(2.0+) | |
| lower-case(s) | 转小写(2.0+) |
2. 布尔函数
| not(expr) | 取反 |
| true() / false() | 布尔值 |
| boolean(expr) | 转布尔 |
3. 数值函数
| count(nodes) | 节点数量 |
| sum(nodes) | 求和 |
| floor(n) / ceiling(n) | 向下/向上取整 |
| round(n) | 四舍五入 |
| number(s) | 转数字 |
4. 节点函数
| last() | 最后一个位置 |
| position() | 当前位置 |
| name() | 节点名 |
| local-name() | 不带前缀的节点名 |
| text() | 文本节点 |
5. 逻辑运算符
| and | 逻辑与(同时满足) | //input[@type="text" and @name="user"] | 选取 type 为 text 且 name 为 user 的 input |
| or | 逻辑或(满足其一) | //div[@class="a" or @class="b"] | 选取 class 为 a 或 b 的 div |
| not() | 逻辑非(取反) | //input[not(@disabled)] | 选取没有 disabled 属性的 input |
注意事项
-
and 的优先级高于 or,混用时建议用括号明确逻辑,例如 //div[(a or b) and c]。
-
not() 是函数,不是运算符,需写 not(expr)。
-
多个谓语连写 [@a][@b] 等价于 and,但可读性不同。
用法示例
# 多条件组合:同时满足多个属性
//input[@type="text" and @name="user" and not(@disabled)]
# 范围组合:价格大于 20 且小于 50
//book[price>20 and price<50]
# 文本与属性组合:文本包含"登录"且 class 含 btn
//button[contains(text(),"登录") and contains(@class,"btn")]
# 排除特定内容:包含"错误"但不含"警告"
//div[contains(.,"错误") and not(contains(.,"警告"))]
# 多选一:type 是 text 或 email
//input[@type="text" or @type="email"]
6. 比较运算符
| = | 等于 | //div[@id="main"] | 选取 id 为 main 的 div |
| != | 不等于 | //div[@id!="main"] | 选取 id 不为 main 的 div |
| < | 小于 | //price[. < 100] | 选取价格小于 100 的 price |
| <= | 小于等于 | //price[. <= 100] | 价格小于等于 100 |
| > | 大于 | //price[. > 100] | 价格大于 100 |
| >= | 大于等于 | //price[. >= 100] | 价格大于等于 100 |
注意事项
-
比较运算符中的 < 和 > 在 XML 文档中需要转义为 < 和 >,但在 XPath 表达式中通常直接写即可(取决于宿主语言,如 Python 的 lxml 可直接写)。
-
字符串比较是区分大小写的,"Main" 和 "main" 不相等。
-
属性值默认是字符串,做数值比较时建议用 number(@attr) 显式转换,例如 //div[number(@data-index)>5]。
-
!= 在节点集比较中有特殊语义:只要有一个节点值不等于目标值就为真,而非“所有节点都不等于”。需谨慎使用。
用法示例
# 数值比较(注意:属性值需显式转数字)
//div[number(@data-price) > 100]
# 字符串比较
//a[@href="https://example.com"]
# 与逻辑运算符组合
//book[price>=20 and price<=50]
# 不等式的实际场景:排除隐藏元素
//div[@style!="display:none"]
# 节点集比较(存在性判断)
//div[p > 10] # 存在一个文本值大于 10 的 p 子元素
六、 Xpath 运算符
1、并集运算符 |
| | | 计算两个节点集的并集 | //title | //author |
补充说明:
-
并集只能用于节点集,不能用于字符串或数字。
-
它会去除重复节点(同一个节点在两个集合中只出现一次)。
-
示例://book/title \\| //book/author → 返回所有 title 和 author。
2、算术运算符
| + | 加法 | //book[price=10+5] |
| – | 减法 | //book[price=20-5] |
| * | 乘法 | //book[price=5*4] |
| div | 除法 | //book[price=50 div 2] |
| mod | 取余 | //book[price=10 mod 3] |
补充说明:
-
XPath 中除法用 div 而非 /(/ 是路径分隔符)。
-
取余用 mod,例如 5 mod 2 = 1。
-
算术结果可用于谓语中的比较,例如 //book[price=10+5] 等价于 //book[price=15]。
-
更实用的场景://div[position() mod 2 = 1] 选取奇数位置。
-
字符串转数字:如果属性值是字符串,做算术时最好用 number() 显式转换,例如 //book[number(@price) > 20]。
示例:
//book[price=10+5] # 价格等于 15
//book[price=50 div 2] # 价格等于 25
//book[price=10 mod 3] # 价格等于 1
//li[position() mod 2 = 0] # 偶数位置的 li
//div[number(@data-index) * 2 = 10] # data-index 为 5
3、比较运算符
| = | 等于 | //book[@id="b1"] |
| != | 不等于 | //book[@id!="b1"] |
| < | 小于 | //book[price<30] |
| <= | 小于等于 | //book[price<=30] |
| > | 大于 | //book[price>30] |
| >= | 大于等于 | //book[price>=30] |
补充说明:
-
字符串比较区分大小写,"Main" 和 "main" 不同。
-
属性值默认是字符串,做数值比较时建议用 number(@attr),例如 //div[number(@data-index)>5]。
-
!= 在节点集比较中语义特殊:只要存在一个节点值不等于目标值,整个条件就为真。
-
不等于的坑://book[@id!="b1"] 会排除 id 为 b1 的节点,但如果节点没有 id 属性,也会被排除(因为没有 id 属性无法判定“不等于”)。
示例:
//price[. > 100] # 价格大于 100
//price[. <= 100] # 价格小于等于 100
//div[number(@data-price) >= 50] # 数值比较,显式转数字
//a[@href!="/home"] # 链接不是 /home 的 a
//book[price>=20 and price<=50] # 范围比较
4、逻辑运算符
| and | 逻辑与(同时满足) | //book[price>20 and price<50] |
| or | 逻辑或(满足其一) | //book[price>20 or price<10] |
| not() | 逻辑非(取反,是函数) | //input[not(@disabled)] |
补充说明:
-
not() 是函数,不是运算符,写法为 not(expr)。
-
and 的优先级高于 or,混用时建议加括号,例如 //div[(a or b) and c]。
-
多谓语连写 [@a][@b] 等价于 and。
示例:
//input[@type="text" and @name="user"]
//div[@class="a" or @class="b"]
//input[not(@disabled)]
//book[price>20 and price<50]
//div[contains(.,"错误") and not(contains(.,"警告"))]
5. 路径运算符
| / | 子节点 | /library/book |
| // | 后代节点 | //book |
| . | 当前节点 | ./title |
| .. | 父节点 | ../book |
| @ | 属性 | @id |
| * | 通配符(任意元素) | //* |
| @* | 任意属性 | //div[@*] |
| :: | 轴分隔符 | child::book |
注意:* 在算术里是乘法,在路径里是通配符,取决于上下文。
字符串与节点比较
| //book[title="Python"] | 子元素 title 的文本等于 Python |
| //book[@id="b1"] | 属性 id 等于 b1 |
| //book[.="Hello"] | 当前节点字符串值等于 Hello(含后代文本) |
| //book[text()="Hello"] | 直接文本节点等于 Hello |
节点集与数值比较的特殊规则
-
当节点集与数值比较时,只要任一节点满足条件即为真。例如 //book[price>20] 中,price 是节点集,只要有一个 price 大于 20 就匹配。
XPath 运算符优先级(从高到低)
| 1 | ()(括号) |
| 2 | [](谓语) |
| 3 | / //(路径) |
| 4 | |(并集) |
| 5 | + -(加减) |
| 6 | * div mod(乘除余) |
| 7 | = != < <= > >=(比较) |
| 8 | and |
| 9 | or |





