Python爬虫必备:XPath从入门到精通(附lxml实战案例)

Python爬虫进阶:用XPath与lxml构建健壮的数据提取管道

如果你曾经尝试过用Python写爬虫,大概率会遇到一个绕不开的问题:如何从那些结构复杂、嵌套混乱的HTML页面里,精准地提取出你想要的数据?正则表达式虽然强大,但面对现代网页的动态生成和层层嵌套,常常显得力不从心,写出来的表达式既难维护,又容易因为页面结构的微小变动而失效。

这时候,XPath就像一把精准的手术刀。它不是什么新潮的技术,但却是每个处理结构化文档(无论是HTML还是XML)的开发者工具箱里,最值得信赖的工具之一。尤其是在Python生态中,配合lxml库,XPath能让你从“勉强能用”的爬虫,进化到“稳定可靠”的数据采集系统。这篇文章不会重复那些基础的语法手册,而是聚焦于如何在实际的、复杂的爬虫项目中,构建一套基于XPath和lxml的、具备容错性和可维护性的数据提取方案。我们会从核心概念讲起,然后深入到实战中的高级技巧、性能优化和异常处理,最后用一个完整的、模拟真实复杂场景的案例来串联所有知识点。

1. 超越基础:理解XPath在DOM树中的导航逻辑

很多教程一上来就罗列//div[@class="content"]这样的表达式,这当然没错,但如果你不理解XPath背后的“上下文”概念,就很难写出灵活且健壮的表达式。XPath的本质是在一棵由节点构成的树(DOM树)中进行导航和查询。

1.1 上下文节点:你的查询起点

每次执行XPath查询,都有一个隐含的“当前节点”,即上下文节点。在lxml中,当你对一个etree元素调用.xpath()方法时,这个元素就是初始的上下文节点。

from lxml import etree

html = """
<html>
  <body>
    <div id="main">
      <p>Paragraph 1</p>
      <p>Paragraph 2</p>
    </div>
    <div id="sidebar">
      <p>Side note</p>
    </div>
  </body>
</html>
"""

tree = etree.HTML(html)
main_div = tree.xpath('//div[@id="main"]')[0]  # 获取id为main的div元素

# 此时,main_div是上下文节点
# 查询其下的所有p标签
paragraphs_in_main = main_div.xpath('.//p')
print([p.text for p in paragraphs_in_main])  # 输出:['Paragraph 1', 'Paragraph 2']

# 对比:如果从根节点(tree)开始,使用相同的相对路径'.//p',结果会不同
all_paragraphs_from_root = tree.xpath('.//p')
print([p.text for p in all_paragraphs_from_root])  # 输出:['Paragraph 1', 'Paragraph 2', 'Side note']

注意:表达式开头的.代表从当前上下文节点开始。main_div.xpath('.//p')main_div.xpath('//p')有天壤之别。前者只在main_div的子孙中查找,后者会从文档根节点重新开始全局查找,通常会返回非预期的更多结果。

1.2 轴(Axes):定义搜索方向

轴定义了相对于上下文节点的搜索方向。这是XPath强大但常被忽略的部分。

轴名称 缩写 描述 示例(上下文节点为某个<li>
child (默认) 上下文节点的直接子节点 li/span 选择<li>下的直接<span>子元素
descendant // 上下文节点的所有后代节点 li//span 选择<li>下任意层级的<span>元素
parent .. 上下文节点的父节点 ../@class 选择父节点的class属性
ancestor 上下文节点的所有祖先节点 ancestor::div 选择所有祖先<div>节点
following-sibling 上下文节点之后的所有同级节点 following-sibling::li 选择后面所有的<li>兄弟节点
preceding-sibling 上下文节点之前的所有同级节点 preceding-sibling::a 选择前面所有的<a>兄弟节点
attribute @ 上下文节点的属性 @href 选择href属性值

在实战中,following-sibling轴尤其有用。比如,在一个没有规律类名的表格中,你想获取某个特定表头后的所有数据行:

# 假设表格结构不规则,但知道“价格”列在“产品名”列之后
tree.xpath('//th[text()="产品名"]/following-sibling::td')

1.3 谓词(Predicates):进行精细过滤

谓词是放在方括号[]内的表达式,用于对节点集进行过滤。它可以是位置索引、属性判断、函数调用等。

  • 位置索引//div[@class="item"][1] 选择第一个具有class="item"div注意:XPath索引从1开始
  • 属性存在性检查//a[@href] 选择所有拥有
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值