数据解析三板斧:正则、BeautifulSoup与XPath

说到数据解析,我刚开始做爬虫那会儿,最头疼的就是从一堆HTML里把想要的数据抠出来。你想想看,网页源码动辄几千行,密密麻麻的标签,眼睛都快看花了。后来慢慢摸索,发现其实就三样工具:正则表达式、BeautifulSoup、还有XPath。今天咱们就把这三样东西掰开揉碎了讲清楚。

核心观点:没有最好的解析方式,只有最合适的场景。我个人的习惯是——简单文本用正则,复杂HTML用BeautifulSoup,大规模XML用XPath+lxml。

正则表达式:文本处理的瑞士军刀

正则表达式,说白了就是一套描述文本模式的规则。我记得刚学的时候觉得它像天书,全是些奇怪的符号。但用多了你会发现,它真的就是一把瑞士军刀——小巧、锋利、什么都能干。

咱们先看几个最常用的元字符:

元字符含义示例
.匹配任意单个字符(除换行)a.b 匹配 aab、a3b
*匹配前一个字符0次或多次ab*c 匹配 ac、abc、abbc
+匹配前一个字符1次或多次ab+c 匹配 abc、abbc
?匹配前一个字符0次或1次ab?c 匹配 ac、abc
\d匹配数字\d{3} 匹配三位数字
\w匹配字母、数字、下划线\w+ 匹配一个单词

来看个实际例子。假设我们要从一段文本里提取所有手机号:

import re

text = "联系人:张三 13800138000,李四 15912345678,王五 010-88886666"
pattern = r'1[3-9]\d{9}'  # 1开头,第二位3-9,后面9位数字
phones = re.findall(pattern, text)
print(phones)  # ['13800138000', '15912345678']

小技巧:写正则的时候,我习惯先用 re.compile() 把模式编译好,尤其是同一个模式要匹配多次的时候。这样性能会好不少。

我曾经踩过一个坑——用正则去解析HTML。当时想偷懒,直接用正则匹配某个标签里的内容。结果网页结构稍微一变,代码就崩了。后来我学乖了:正则适合处理纯文本,别用它解析嵌套的HTML。

BeautifulSoup:HTML解析的温柔乡

BeautifulSoup 这个名字起得好,它确实能让你的解析工作变得「美丽」起来。它把HTML文档转换成一棵标签树,你可以像逛公园一样在里面溜达。

安装很简单:pip install beautifulsoup4。然后咱们看看怎么用:

from bs4 import BeautifulSoup

html = """
<html>
    <body>
        <div class="content">
            <h1>标题</h1>
            <p class="desc">这是一段描述</p>
            <a href="http://example.com">链接</a>
        </div>
    </body>
</html>
"""

soup = BeautifulSoup(html, 'html.parser')

# 找标签
print(soup.h1.text)  # 标题
print(soup.find('a')['href'])  # http://example.com

# 按class找
desc = soup.find('p', class_='desc')
print(desc.text)  # 这是一段描述

# 找所有链接
for link in soup.find_all('a'):
    print(link.get('href'))

BeautifulSoup 有三种解析器:html.parser(Python内置)、lxml(速度快)、html5lib(容错强)。我个人推荐用 lxml,速度快,解析也够稳定。

注意:BeautifulSoup 默认会把HTML标签补全。比如你只给了 <p>内容,它会自动加上 </p>。这有时候是好事,有时候会掩盖问题。我在调试时经常先打印 soup.prettify() 看看解析结果对不对。

XPath与lxml:精准定位的利器

XPath 是一种在XML文档中查找信息的语言。它比CSS选择器更强大,比正则更结构化。配合 lxml 库,解析速度比 BeautifulSoup 快不少。

先看几个XPath的基本语法:

表达式含义
/从根节点选取
//从任意位置选取
.当前节点
..父节点
@选取属性
[]条件过滤

来看个实际例子:

from lxml import etree

html = """
<html>
    <body>
        <ul id="list">
            <li class="item">苹果</li>
            <li class="item active">香蕉</li>
            <li class="item">橘子</li>
        </ul>
    </body>
</html>
"""

tree = etree.HTML(html)

# 获取所有li
items = tree.xpath('//li')
for item in items:
    print(item.text)  # 苹果 香蕉 橘子

# 获取class包含active的li
active = tree.xpath('//li[contains(@class, "active")]')
print(active[0].text)  # 香蕉

# 获取id为list的ul下的所有li
items_in_list = tree.xpath('//ul[@id="list"]/li')
print([item.text for item in items_in_list])

经验之谈:写XPath的时候,我习惯先用浏览器的开发者工具验证一下。在Elements面板选中元素,右键 -> Copy -> Copy XPath,能快速拿到一个参考路径。不过浏览器生成的XPath往往又长又啰嗦,我会手动精简一下。

XPath 有个特别实用的功能——轴(Axes)。比如你想找某个元素后面的兄弟节点,或者找祖先节点,用轴就特别方便:

# 找后面的兄弟节点
following = tree.xpath('//li[2]/following-sibling::li')
print([item.text for item in following])  # ['橘子']

# 找祖先节点
ancestor = tree.xpath('//li[1]/ancestor::ul')
print(ancestor[0].get('id'))  # list

三种方式怎么选?

我做了这么多年数据采集,总结了一套选择原则:

  • 正则表达式:适合从纯文本里提取特定格式的数据,比如手机号、邮箱、日期。也适合做字符串替换和清洗。
  • BeautifulSoup:适合解析结构不太规范的HTML,或者你需要频繁修改文档树。它的API很友好,新手容易上手。
  • XPath + lxml:适合大规模数据解析,或者需要精确定位某个元素。性能最好,语法也最灵活。

我的建议:如果你刚开始学,先用BeautifulSoup把感觉找到。等遇到性能瓶颈或者需要更精细的控制时,再切换到XPath。别一上来就追求「最牛」的工具,够用就好。

嗯,说到这儿,我想起一个真实的项目。当时要采集一个电商网站的商品信息,页面结构特别复杂,嵌套了七八层div。用BeautifulSoup写选择器,又长又容易出错。后来换成XPath,用 //div[@class="product"]//span[@class="price"] 这种写法,一行就搞定了。所以说,工具选对了,事半功倍。

数据解析技术选型 原始数据(HTML/文本) 正则表达式 BeautifulSoup XPath + lxml 适用场景 • 纯文本提取(手机号、邮箱) • 字符串替换与清洗 • 简单格式校验 适用场景 • 结构不规范的HTML • 需要修改文档树 • 新手友好,上手快 适用场景 • 大规模数据解析 • 精确定位元素 • 性能要求高

最后说一句,这三种技术不是互斥的。我在实际项目中经常混着用——先用XPath定位到某个区域,再用正则提取具体数据,最后用BeautifulSoup把结果整理成结构化数据。工具是死的,人是活的,怎么顺手怎么来。

想要更多资料,联系可可