XPath 与选择器进阶
前置知识
本文基于模型知识整理,建议核对官方文档(见 参考资料)。
一句话定义
XPath 是以路径表达式在 XML/HTML 树上定位节点的语言,具备条件过滤、函数计算与轴导航能力,是 CSS 选择器覆盖不了复杂场景时的进阶武器。
为什么重要
「取『价格』文本里含 ¥ 的那个元素」「按 class 前缀匹配」「向上找父容器再取兄弟字段」——这些 CSS 选择器做不到或很别扭的操作,XPath 一句搞定。Scrapy 的解析器 parsel 同时支持两者,XPath 是框架时代的通用语。
前置知识
kp-003、kp-007。
核心概念
- 路径轴:
//(任意后代)、/(直接子级)、..(父级)、following-sibling/preceding-sibling。 - 谓词:
[n](第 n 个,从 1 开始)、[@attr='v']、[last()]。 - 常用函数:
contains()、starts-with()、text()、normalize-space()。 - parsel:Scrapy 团队的独立选择器库,
.css()与.xpath()可混用接力。
原理与机制
表达式速查与 CSS 对照:
//article[@class="product_pod"] ≙ article.product_pod 单元定位
//article//h3/a/@title ≙ article h3 a 取属性
//p[contains(@class,"price")] ≙ p[class*="price"] 模糊匹配
//div[.//span[@class="stock"]] ≙(CSS 难以表达) 按子孙条件筛容器
//h2/../ul/li[1] ≙(CSS 无法向上) 向上取兄弟
parsel 接力(提取函数返回 SelectorList,可继续 .xpath()/.css()):
from parsel import Selector
sel = Selector(html=html)
for pod in sel.xpath('//article[@class="product_pod"]'):
title = pod.xpath('.//h3/a/@title').get() # 相对路径必须以 . 开头
price = pod.xpath('.//p[contains(@class,"price")]/text()').get()
print(title, price)
高频坑:相对定位忘写 .。 pod.xpath('//h3/a') 里的 // 是从全文根开始找,会重复命中全页所有节点;单元内必须写 .//h3/a。
实例或案例
某列表页价格元素 class 是动态哈希(price_3f2a):CSS 的 [class*="price"] 可用但易误伤(reprice_x 也命中);XPath 的 //span[starts-with(@class,"price_")] 更精准。经验法则:前缀匹配用 starts-with,包含匹配再考虑 contains。
常见误区
- 误区一:把
[1]当成从 0 开始。 XPath 下标从 1 开始;//li[1]是每个父节点下的第一个 li,不是全文档第一个(全文档第一是(//li)[1])。 - 误区二:
text()总返回单值。 它返回节点列表,常因标签内有换行出现多个值;规范做法是normalize-space()或取.get()。 - 误区三:能用 CSS 就别学 XPath。 反之:复杂条件与向上导航只有 XPath 能表达,值得投入。
自测题
.//a与//a在循环体内的区别?
答:.//a 在当前节点内相对查找;//a 从文档根全局查找,循环里会重复命中。
- 如何定位「包含 class 含 stock 的子孙 span 的 div」?
答://div[.//span[contains(@class,"stock")]]。
- XPath 谓词下标从几开始?
//li[1]与(//li)[1]差异?
答:从 1 开始;前者是每个父容器各自的第一个 li,后者是全文档第一个 li 节点。
与其他知识点的关系
kp-018 的 Scrapy Spider 内部用的就是 parsel;kp-012 渲染后的 DOM 同样可用 XPath 定位。
延伸阅读
MDN「XPath:Introduction」;parsel 官方文档(含 CSS/XPath 混用示例)。