Python爬虫教程:XPath解析技术详解

爬取网页数据时,HTML结构千变万化?正则太脆弱一碰标签缩进/顺序就崩?XPath绝对是你要找的「网页导航精准手术刀」——这篇就带你用Python+lxml快速掌握核心用法!


1. 快速认识 XPath

XPath(XML Path Language)是一门定位文档节点的路径语言,最初为XML设计,但对解析HTML同样完美适配。

为什么选 XPath?

✅ 有路径式的直观语法,类似文件系统
✅ 提供丰富的筛选、处理函数
✅ 支持从节点向上/向下/同级全方位导航
✅ W3C官方标准,各语言解析库支持度极高


2. 环境准备:Python + lxml

我们用 Python 生态中最流行的 lxml 库来实现 XPath,它解析速度快、容错能力强(能修复很多HTML的小bug)。

安装依赖

pip install lxml

验证安装成功

from lxml import etree
print(f"lxml XPath解析库版本:{etree.__version__}")

3. 第一步:把HTML变成可查询的「树」

XPath是基于文档树模型工作的,所以我们需要先把HTML字符串/文件转换成 lxml 的 ElementTree 对象。

解析HTML字符串

from lxml import etree

# 模拟一段爬取到的HTML
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()
# 转换成 ElementTree
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())

4. 核心节点选择:路径、属性、文本

有了 tree 对象,我们可以调用它的 .xpath() 方法传入表达式查询,返回结果是符合条件的节点列表(或属性/文本列表)。

基本路径选择

语法符号作用示例结果
/从根节点或上一层的直接子节点tree.xpath('/html/body/div/ul')选中示例中唯一的ul
//从整个文档或上一层的所有子孙节点tree.xpath('//li')选中所有5个li
*匹配任意节点tree.xpath('//li/*')选中所有li下的a
.当前节点(多用于循环内的相对路径)-见下文电商案例
..父节点tree.xpath('//a[@href="link4.html"]/../@class')返回 ['item-1']

属性筛选与获取

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', ..., 'link5.html']

文本获取

在路径后加 /text() 即可获取直接子节点的纯文本,加 //text() 会获取所有子孙节点的文本(包括换行、空格)

# 获取所有 a 标签的文本
a_texts = tree.xpath('//li/a/text()')
print(a_texts)  # 输出:['first item', ..., 'fifth item']

5. 进阶技巧:按序、轴选择

按序选择节点

XPath 提供了几个内置的位置函数:

# 第一个 li
first_li = tree.xpath('//li[1]/a/text()')  # 注意:位置从1开始!
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)定义了当前节点与目标节点的空间关系,适合复杂结构的导航:

轴名作用
ancestor::当前节点的所有祖先(父、祖父…直到根)
attribute::当前节点的所有属性(可简写为 @*
child::当前节点的直接子节点(可简写为 /
descendant::当前节点的所有子孙(可简写为 //
following-sibling::当前节点的同级后续所有节点
preceding-sibling::当前节点的同级前置所有节点

6. 实战小案例:快速提取数据

案例1:电商商品列表提取(模拟数据)

当我们需要循环处理多个同类节点时,一定要用相对路径(以 .// 开头),否则每次都会从整个文档重新搜索,效率低还容易出错:

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)

7. 避坑指南与小工具

常见问题

  1. 编码乱码:爬取网页时用 response.encoding = response.apparent_encoding 自动检测
  2. 动态内容:XPath只能解析初始HTML,JS动态加载的内容需要用Selenium、Playwright或抓包分析API
  3. XPath调试:浏览器控制台直接输入 $x('//你的表达式') 就能实时测试!

性能优化小建议

  • 尽量用具体标签+属性筛选,少用 //*
  • 从有唯一标识的节点(比如 id="header")开始往下找,减少全局搜索
  • 循环内用相对路径

8. 扩展学习资源


这篇教程覆盖了XPath在Python爬虫中的核心场景和高频用法,赶紧找个小网站试试手吧!有问题欢迎在评论区留言~