4、XPath与lxml:XPath语法入门、lxml库安装与使用、XPath轴与谓词、实战:抓取商品价格与评论数

各位同学,咱们今天聊点硬核的。数据采集这活儿,说白了就是跟HTML文档死磕。你拿到一个网页,怎么精准地把价格、评论数这些字段捞出来?XPath就是那把手术刀。

我个人习惯,不管用什么框架,底层解析一定用lxml。为什么?因为它快,而且XPath支持得最完整。今天这一章,我带你从零上手XPath,再配合lxml这个利器,最后来一个实战——抓商品价格和评论数。

核心知识点一览:

  • XPath语法基础:路径表达式、节点选择
  • lxml库的安装与基本使用
  • XPath轴与谓词:高级筛选技巧
  • 实战:商品价格与评论数抓取
XPath与lxml 知识体系 XPath + lxml XPath语法入门 lxml安装与使用 XPath轴与谓词 实战:商品价格+评论数

4.1 XPath语法入门

XPath,全称XML Path Language。别看名字带XML,它在HTML上一样好使。HTML其实就是XML的一种变体,只不过更松散。

XPath的核心思想,就是用路径表达式在文档树里找节点。就像你在文件系统里找文件一样——/root/child/grandchild,只不过这里的“文件”变成了标签节点。

最常用的几个表达式:

表达式含义示例
/从根节点选取/html/body/div
//从任意位置选取//div 选所有div
.当前节点./p 当前节点下的p
..父节点//span/.. 所有span的父节点
@选取属性//a/@href 所有链接
*通配符//div/* div下所有子节点

举个例子。你想抓一个商品页面的标题,HTML结构可能是这样的:

<div class="product">
  <h1 class="title">2024新款智能手表</h1>
  <span class="price">¥299.00</span>
  <span class="comments">1286条评论</span>
</div>

用XPath抓标题://h1[@class='title']/text()。意思就是:从任意位置找h1标签,它的class属性等于title,然后取里面的文本。

我的小习惯:写XPath时,我一般先用浏览器开发者工具验证一下。按F12,在Elements面板选中元素,右键→Copy→Copy XPath。虽然浏览器生成的不一定最优,但至少能跑通。

4.2 lxml库安装与使用

lxml是Python里解析HTML/XML最快的库之一。底层用的是C语言的libxml2,所以性能没得说。

安装:

pip install lxml

就这么简单。如果你在Windows上遇到编译错误,别慌,去 这个网站 下载对应的whl文件,然后 pip install xxx.whl 就行。

基本使用:

from lxml import etree

# 解析HTML字符串
html_str = '''
<html>
  <body>
    <div class="product">
      <h1 class="title">2024新款智能手表</h1>
      <span class="price">¥299.00</span>
      <span class="comments">1286条评论</span>
    </div>
  </body>
</html>
'''

# 创建解析树
tree = etree.HTML(html_str)

# 用XPath提取数据
title = tree.xpath('//h1[@class="title"]/text()')[0]
price = tree.xpath('//span[@class="price"]/text()')[0]
comments = tree.xpath('//span[@class="comments"]/text()')[0]

print(f"标题: {title}")
print(f"价格: {price}")
print(f"评论数: {comments}")

输出结果:

标题: 2024新款智能手表
价格: ¥299.00
评论数: 1286条评论

注意:lxml的HTML解析器会自动补全缺失的标签。比如你只给了<p>hello,它会自动加上</p>。这既是好事也是坏事——好事是容错性强,坏事是如果你依赖原始结构,可能会被它“修正”掉。

4.3 XPath轴与谓词

基础XPath能解决80%的问题。但遇到复杂页面,比如嵌套很深、或者需要根据兄弟节点定位时,就得用轴和谓词了。

XPath轴: 轴定义了节点之间的相对关系。

轴名称含义示例
child子节点(默认)//div/child::p
parent父节点//span/parent::div
ancestor所有祖先节点//span/ancestor::div
following-sibling后面的兄弟节点//h1/following-sibling::span
preceding-sibling前面的兄弟节点//span/preceding-sibling::h1
descendant所有后代节点//div/descendant::*

谓词: 用方括号 [] 加条件过滤节点。

举个例子。你有一个商品列表,想抓第二个商品的价格:

<div class="list">
  <div class="item">
    <span class="price">¥199</span>
  </div>
  <div class="item">
    <span class="price">¥299</span>
  </div>
  <div class="item">
    <span class="price">¥399</span>
  </div>
</div>

抓第二个价格://div[@class='item'][2]/span[@class='price']/text()

抓最后一个价格://div[@class='item'][last()]/span[@class='price']/text()

抓价格大于250的商品://div[@class='item'][span[@class='price'] > 250] —— 注意,这里比较的是文本,需要先转成数字。

避坑指南:我曾经在抓一个电商网站时,发现XPath在浏览器里能匹配到元素,但用lxml就是抓不到。后来发现是HTML里有多余的命名空间。解决办法:用etree.HTML()解析时,它会自动处理大部分命名空间问题。如果还不行,试试etree.fromstring()加上parser参数。

4.4 实战:抓取商品价格与评论数

理论说完了,咱们来真的。假设我们要抓一个商品详情页,HTML结构如下:

<div class="product-detail">
  <div class="price-area">
    <span class="current-price">¥599.00</span>
    <span class="original-price">¥1299.00</span>
  </div>
  <div class="info-area">
    <span class="sales">已售 2.3万</span>
    <span class="comments">评论 4567</span>
  </div>
</div>

目标:抓取当前价格、原价、销量、评论数。

完整代码:

from lxml import etree

html_content = '''
<div class="product-detail">
  <div class="price-area">
    <span class="current-price">¥599.00</span>
    <span class="original-price">¥1299.00</span>
  </div>
  <div class="info-area">
    <span class="sales">已售 2.3万</span>
    <span class="comments">评论 4567</span>
  </div>
</div>
'''

tree = etree.HTML(html_content)

# 提取当前价格
current_price = tree.xpath('//span[@class="current-price"]/text()')[0]
# 提取原价
original_price = tree.xpath('//span[@class="original-price"]/text()')[0]
# 提取销量
sales = tree.xpath('//span[@class="sales"]/text()')[0]
# 提取评论数
comments = tree.xpath('//span[@class="comments"]/text()')[0]

print(f"当前价格: {current_price}")
print(f"原价: {original_price}")
print(f"销量: {sales}")
print(f"评论数: {comments}")

输出:

当前价格: ¥599.00
原价: ¥1299.00
销量: 已售 2.3万
评论数: 评论 4567

如果页面结构更复杂,比如评论数藏在某个深层节点里,可以用轴来定位:

# 用following-sibling轴:从价格区域往后找兄弟节点
comments = tree.xpath('//div[@class="price-area"]/following-sibling::div[@class="info-area"]//span[@class="comments"]/text()')[0]

我的经验:写XPath时,尽量用@class而不是@id。因为很多网站的class是动态生成的,但id通常更稳定。不过也别完全依赖class,有些网站会用随机class名。这时候就得用contains()函数了://span[contains(@class, 'price')]

嗯,到这里,XPath和lxml的核心用法你应该已经掌握了。从基础语法到轴与谓词,再到实战抓取,每一步我都踩过坑,也总结出了最佳实践。记住一句话:XPath写得好,数据抓取没烦恼。


专注资料整理