先用 XPath 定位到包含目标数据的节点或节点集,再用正则表达式从这些节点的内容中精确地提取你想要的信息。

为什么需要两者结合?
- XPath 的强项:在 XML/HTML 结构中“导航”和“定位”,它擅长根据标签名、层级、属性(如
id,class)来快速找到你感兴趣的区域。 - 正则表达式的强项:在文本内容中进行“模式匹配”和“提取”,它擅长处理那些没有固定结构、格式多变或包含特定模式(如邮箱、电话、日期)的数据。
举个例子: 你想从一个商品列表页面提取所有商品的“价格”,HTML 结构可能是这样:
<div class="product"> <h3>智能手表</h3> <p class="price">价格: ¥1,299.00</p> </div> <div class="product"> <h3>无线耳机</h3> <p class="price">价格: ¥899.00 (促销)</p> </div>
- XPath 可以轻松帮你找到所有包含价格的
<p class="price">节点。 - 正则表达式 可以帮你从
"价格: ¥1,299.00"和"价格: ¥899.00 (促销)"这两个字符串中,精确地提取出00和00这样的数字。
实现方法:使用 lxml 库
在 Python 中,最推荐使用 lxml 库来处理 XPath 和正则表达式,因为它性能高且功能全面。
安装 lxml
pip install lxml
基本工作流程
- 解析 HTML/XML:使用
lxml.html解析你的文档,得到一个可以操作的元素树。 - 使用 XPath 定位:在元素树上使用
.xpath()方法定位到目标节点。 - 遍历节点并应用正则:遍历定位到的节点,获取它们的文本内容(或其他属性),然后用
re模块进行正则匹配和提取。
详细代码示例
假设我们有以下 HTML 内容:
<!DOCTYPE html>
<html>
<head>测试页面</title>
</head>
<body>
<h1>用户信息</h1>
<div id="user-list">
<div class="user">
<p>姓名: 张三</p>
<p>邮箱: zhangsan@example.com</p>
<p>电话: 138-1234-5678</p>
</div>
<div class="user">
<p>姓名: 李四</p>
<p>邮箱: lisi_2025@my-domain.org</p>
<p>电话: 139-8765-4321</p>
</div>
</div>
<div id="logs">
<p>系统日志 [2025-10-27 10:00:00]: 用户登录成功</p>
<p>系统日志 [2025-10-27 10:05:23]: 数据备份完成</p>
</div>
</body>
</html>
示例 1:提取所有用户的邮箱
这个例子比较简单,直接用 XPath 就可以完成,但我们可以用它来演示基本流程。

from lxml import html
import re
# 1. 解析 HTML
html_content = """
<!DOCTYPE html>
<html>
<head>测试页面</title>
</head>
<body>
<h1>用户信息</h1>
<div id="user-list">
<div class="user">
<p>姓名: 张三</p>
<p>邮箱: zhangsan@example.com</p>
<p>电话: 138-1234-5678</p>
</div>
<div class="user">
<p>姓名: 李四</p>
<p>邮箱: lisi_2025@my-domain.org</p>
<p>电话: 139-8765-4321</p>
</div>
</div>
<div id="logs">
<p>系统日志 [2025-10-27 10:00:00]: 用户登录成功</p>
<p>系统日志 [2025-10-27 10:05:23]: 数据备份完成</p>
</div>
</body>
</html>
"""
tree = html.fromstring(html_content)
# 2. 使用 XPath 定位所有邮箱节点
# XPath: //p[contains(text(), '邮箱')] 找到所有文本内容包含"邮箱"的 <p> 标签
email_nodes = tree.xpath('//p[contains(text(), "邮箱")]')
# 3. 遍历节点并提取文本
emails = []
for node in email_nodes:
text = node.text_content()
# 4. 使用正则表达式提取邮箱地址
# \S+ 匹配非空白字符(邮箱用户名)
# @ 匹配 @ 符号
# [^@\s]+ 匹配 @ 符号后的非空白字符(域名)
# \.[^@\s]+ 匹配 . 和顶级域名
email = re.search(r'(\S+@\S+\.\S+)', text)
if email:
emails.append(email.group(1))
print("提取到的邮箱列表:", emails)
# 输出: 提取到的邮箱列表: ['zhangsan@example.com', 'lisi_2025@my-domain.org']
示例 2:提取所有日志的时间戳
这个例子更能体现两者的结合优势,日志的格式虽然固定,但 lxml 本身没有直接提取这种格式化文本的函数,正则表达式就派上用场了。
from lxml import html
import re
# (使用上面相同的 html_content 和 tree)
# 1. 使用 XPath 定位所有日志节点
# XPath: //div[@id='logs']//p 定位到 id 为 'logs' 的 div 内的所有 p 标签
log_nodes = tree.xpath('//div[@id="logs"]//p')
# 2. 遍历节点并应用正则
timestamps = []
for node in log_nodes:
text = node.text_content()
# 3. 使用正则表达式提取时间戳
# \d{4} 匹配4位年份
# -\d{2} 匹配月和日
# \s\d{2}:\d{2}:\d{2} 匹配时分秒
match = re.search(r'(\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2})', text)
if match:
timestamps.append(match.group(1))
print("提取到的时间戳列表:", timestamps)
# 输出: 提取到的时间戳列表: ['2025-10-27 10:00:00', '2025-10-27 10:05:23']
高级技巧:在 XPath 中使用正则表达式
lxml 的 XPath 引擎支持在 XPath 表达式内部直接使用正则表达式,这有时会更高效,这通过 re:test() 函数实现。
语法: re:test(字符串, 正则表达式)
字符串:通常是text()或@属性名。正则表达式:标准的正则表达式。
示例 3:在 XPath 中使用 re:test 提取邮箱
我们可以在定位节点时就用正则过滤,而不是先拿到节点再过滤。
from lxml import html
import re
# (使用上面相同的 html_content 和 tree)
# 1. 在 XPath 中直接使用 re:test
# 定位所有 <p> 标签,其文本内容符合邮箱格式的正则表达式
email_nodes = tree.xpath('//p[re:test(text(), "\\S+@\\S+\\.\\S+")]')
# 2. 遍历节点并提取
emails = [node.text_content().split(': ')[1] for node in email_nodes]
print("使用 re:test 提取的邮箱列表:", emails)
# 输出: 使用 re:test 提取的邮箱列表: ['zhangsan@example.com', 'lisi_2025@my-domain.org']
对比两种方法:
| 方法 | 优点 | 缺点 |
|---|---|---|
| 先 XPath,后正则 | 逻辑清晰,易于理解,先缩小范围,再精细处理。 | 需要编写循环代码,略显繁琐。 |
在 XPath 中使用 re:test |
代码更简洁,定位非常精确,一步到位。 | XPath 表达式会变得复杂,可读性稍差。 |
选择建议:
- 如果筛选条件简单,优先使用纯 XPath。
- 如果文本内容模式复杂且多变,优先使用 先 XPath,后正则 的模式,这样更灵活、更易于维护。
- 如果一个复杂的正则表达式能帮你一步到位地定位到唯一的几个节点,可以考虑使用
re:test来简化代码。
总结与最佳实践
- 明确分工:让 XPath 负责定位,让正则表达式负责提取,不要试图用正则去解析整个 HTML 结构,那会非常痛苦且容易出错。
- 先定位,后提取:始终先用 XPath 找到最小的、最相关的节点集合,然后再对这些节点的内容应用正则表达式,这能极大地提高效率和准确性。
- 优先使用
lxml:它是 Python 中处理 XML/HTML 性能最好、功能最全的库之一。 - 处理编码:从网页抓取的内容通常是
bytes类型,记得先解码成str(如response.text.encode('utf-8').decode('utf-8')或直接使用response.content让lxml自动处理),再交给lxml解析。 - 善用浏览器开发者工具:在 Chrome 或 Firefox 的开发者工具(Elements -> 右键 -> Copy -> Copy XPath)中可以快速获取 XPath 表达式,这是你最好的朋友。
通过掌握 XPath 和正则表达式的结合使用,你几乎可以从任何网页中提取出你想要的任何结构化或半结构化的数据。
