Python爬虫教程:XPath解析技术详解
爬取网页数据时,HTML 结构千变万化让人头疼?正则表达式太脆弱,标签缩进或顺序稍微一变就崩盘?别急,XPath 绝对是你正在寻找的那把「网页导航精准手术刀」。本篇就带你用 Python + lxml 快速掌握核心用法,让数据提取变得轻松高效!
1. 快速认识 XPath
XPath (XML Path Language) 是一门在文档树中定位节点的路径语言。它最初为 XML 设计,但对 HTML 的解析同样堪称完美。
为什么选 XPath?
- ✅ 路径式语法,直观易懂,就像操作文件系统一样
- ✅ 内置丰富的筛选函数,过滤节点随心所欲
- ✅ 支持向上、向下、同级全方位导航,不放过任何角落
- ✅ W3C 官方标准,各大主流语言均有成熟的解析库支持
2. 环境准备:Python + lxml
Python 生态中,lxml 是实现 XPath 最流行的库。它底层基于 C 语言,解析速度飞快,且容错能力极强——哪怕遇到不规范的 HTML,也能自动修复成可查询的树结构。
安装依赖
验证安装成功
from lxml import etree
print(f"lxml XPath解析库版本:{etree.__version__}")
运行后若能正常输出版本号(如 lxml XPath解析库版本:5.3.0),说明环境已经准备就绪。
3. 第一步:把 HTML 变成可查询的「树」
XPath 基于文档树模型工作,所以我们得先把 HTML 字符串或本地文件转换成 lxml 的 ElementTree 对象。
解析 HTML 字符串
from lxml import etree
sample_html = """
<html>
<body>
<div class="container">
<ul>
<li class="item-0"><a href="link1.html">first item</a></li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-inactive"><a href="link3.html">third item</a></li>
<li class="item-1"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a></li>
</ul>
</div>
</body>
</html>
"""
# 初始化 HTML 解析器(自动修复不规范的 HTML)
html_parser = etree.HTMLParser()
tree = etree.fromstring(sample_html, html_parser)
# 打印修复并格式化后的 HTML,确认结构
print(etree.tostring(tree, pretty_print=True, method="html").decode("utf-8"))
从本地 HTML 文件加载
# 解析本地的 test.html 文件
tree = etree.parse("test.html", etree.HTMLParser())
两种方式得到的 tree 对象用法完全一致,接下来我们重点玩转它。
4. 核心节点选择:路径、属性、文本
拿到 tree 对象后,调用它的 .xpath() 方法并传入表达式,就能轻松查询。返回结果通常是匹配节点组成的列表,或者属性值、文本的列表。
基本路径符号速查
属性筛选与获取
XPath 使用 [@属性名="值"] 这种谓语(放在方括号里的筛选条件)来精确匹配:
# 筛选 class 属性等于 "item-0" 的所有 li
filtered_li = tree.xpath('//li[@class="item-0"]')
print(len(filtered_li)) # 输出:2
# 多条件组合:使用 and / or 连接
multi_attr_li = tree.xpath('//li[contains(@class, "item") and position()<3]')
# contains():模糊匹配属性值,对多值 class(如 "item active")尤其好用
# position():返回节点在兄弟中的位置
# 直接获取属性值:在路径后追加 /@属性名
all_hrefs = tree.xpath('//li/a/@href')
print(all_hrefs) # ['link1.html', 'link2.html', 'link3.html', 'link4.html', 'link5.html']
提取文本内容
/text():仅获取直接子节点的纯文本
//text():递归获取所有子孙节点的文本(可能包含换行和多余空白)
# 获取所有 a 标签的直接文本
a_texts = tree.xpath('//li/a/text()')
print(a_texts) # ['first item', 'second item', 'third item', 'fourth item', 'fifth item']
小贴士:使用 //text() 时要留意提取到的内容可能超出预期,建议先用 /text() 保证精确,必要时再用 Python 的 .strip() 清理。
5. 进阶技巧:按序选择与轴导航
按序选择节点
XPath 内置了几个非常实用的位置函数,需要注意的是位置编号从 1 开始,而非编程中常见的 0。
# 第一个 li
first_li = tree.xpath('//li[1]/a/text()')
print(first_li) # ['first item']
# 最后一个 li
last_li = tree.xpath('//li[last()]/a/text()')
print(last_li) # ['fifth item']
# 倒数第三个
last_3rd = tree.xpath('//li[last()-2]/a/text()')
print(last_3rd) # ['third item']
轴选择:全方位导航
轴 (Axis) 定义了当前节点与目标节点之间的空间关系,让你能灵活穿梭于复杂的文档结构。以下是常用轴一览:
举个例子:想获取当前 <li> 后面紧跟着的那个兄弟 <li>,就可以用 following-sibling::li[1]。这在处理表格或列表数据时非常方便。
6. 实战小案例:电商商品列表提取
当需要循环处理多个同类节点时,务必使用相对路径(以 .// 开头)。否则每次都会从整个文档根节点重新搜索,不仅效率低下,还容易因为上下文错乱而取到错误数据。
from lxml import etree
product_html = """
<div class="product-list">
<div class="product">
<h3><a href="/product/1">复古蓝牙音箱</a></h3>
<span class="price">¥199.00</span>
<span class="sales">已售2300件</span>
</div>
<div class="product">
<h3><a href="/product/2">机械键盘青轴</a></h3>
<span class="price">¥349.00</span>
<span class="sales">已售8700件</span>
</div>
</div>
"""
tree = etree.fromstring(product_html)
products = []
for p_node in tree.xpath('//div[@class="product"]'):
# 关键!使用相对路径 .// 从当前 p_node 开始查找
name = p_node.xpath('.//h3/a/text()')[0]
price = p_node.xpath('.//span[@class="price"]/text()')[0]
sales = p_node.xpath('.//span[@class="sales"]/text()')[0]
products.append({
"name": name,
"price": price,
"sales": sales
})
print(products)
输出结果:
[
{'name': '复古蓝牙音箱', 'price': '¥199.00', 'sales': '已售2300件'},
{'name': '机械键盘青轴', 'price': '¥349.00', 'sales': '已售8700件'}
]
7. 避坑指南与小工具
常见问题排查
- 编码乱码:爬取网页后,先用
response.encoding = response.apparent_encoding 自动检测正确编码,再传递给 lxml 解析。
- 动态内容抓不到:XPath 只能解析服务器返回的初始 HTML。如果数据是通过 JavaScript 动态生成的,需要配合 Selenium、Playwright 等工具,或者直接分析 Ajax 接口。
- 表达式在代码中不生效:把表达式粘贴到浏览器的控制台,用
$x('//你的表达式') 实时测试,快速定位问题。
性能优化小建议
- 尽量用具体标签 + 属性来定位,避免滥用
//* 全局通配。
- 从有唯一标识的节点(如
id="header")向下一层层查找,减少全局扫描。
- 循环体内务必使用以
.// 开头的相对路径。
8. 扩展学习资源
这篇教程覆盖了 XPath 在 Python 爬虫中的核心场景和高频用法,赶紧找个小网站动手试试吧!如果在实践中遇到什么问题,欢迎在评论区留言交流~