XPath中索引从1开始,//div/p[1]选第一个p子元素;[last()]选最后一个,[last()-1]选倒数第二个;*匹配任意元素,node()匹配所有节点(含文本),需注意浏览器与lxml解析差异。

用 [n] 定位第 n 个子节点(注意索引从 1 开始)
XPath 的索引不是从 0 开始,而是从 1 开始。所以 //div/p[1] 选的是第一个 p 子节点,//div/p[2] 是第二个,//div/p[5] 是第五个。
常见错误是套用编程习惯写成 [0] 或 [:3] —— XPath 不支持这些写法,[0] 直接不匹配任何节点。
-
//ul/li[1]:选ul下第一个li(不管它在 DOM 中是否是第一个子元素,只看同级同名的li) -
//table/tr[position()=3]:等价于//table/tr[3],显式调用position()函数更利于加条件,比如//table/tr[position() - 如果想选“所有
div中的第二个子节点(无论类型)”,得用//div/*[2],其中*表示任意元素节点
first() 和 last() 是函数,但不在标准 XPath 1.0 中
别被某些文档误导:first()、last() 不是 XPath 1.0 原生函数(主流浏览器、lxml、libxml2 默认都只支持 1.0)。它们只存在于 XPath 2.0+ 或某些方言(如 Selenium 的旧版 Java 绑定曾模拟过,但已弃用)。
真正跨平台安全的写法是:
- 第一个 →
[1] - 最后一个 →
[last()](注意括号不能省) - 倒数第二个 →
[last()-1] - 前三个 →
[position() 或[position()
例如://ol/li[last()-1] 稳定选倒数第二个 li,哪怕列表长度动态变化。
小心「子节点」和「子元素节点」的区别
XPath 中的 [n] 默认匹配的是「同名的子元素节点」,不是所有子节点。文本节点、注释、空格换行都会占用位置,但 //div/p[2] 只会在 p 元素里数,不会把中间的文本节点算进去。
如果你要严格按 DOM 树顺序取第 n 个子节点(含文本、注释等),必须用 node():
//div/node()[2]
但多数场景不需要这么细——你通常想选的是元素,不是空白文本。所以更常用的是:
-
//div/*[2]:第二个子元素(忽略文本/注释) -
//div/p[2]:第二个p元素(只在p中计数) -
//div/node()[2][self::p]:第二个子节点,且它必须是p元素(较啰嗦,少用)
在 Python lxml 或浏览器 DevTools 中验证时的典型陷阱
用 lxml 或 Chrome 控制台测试时,容易因 HTML 解析差异踩坑:
- 浏览器会自动补全缺失标签(比如把
补成带1 tbody的结构),导致你以为//table/tr[1]应该命中,实际要写//table/tbody/tr[1] -
lxml默认不补全,行为更“原始”,同一段 HTML 在两者中可能返回不同结果 - 用
text()提取内容后,开头结尾的空白可能来自换行缩进,建议链式调用.strip()(Python)或用normalize-space()函数://div/p[1]/normalize-space(text())
最稳妥的方式:先用 //* 或 //*[@id="xxx"] 定位到父容器,再逐级加 [n],避免跨层级硬写绝对路径。










