数据解析三板斧:正则、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"] 这种写法,一行就搞定了。所以说,工具选对了,事半功倍。
最后说一句,这三种技术不是互斥的。我在实际项目中经常混着用——先用XPath定位到某个区域,再用正则提取具体数据,最后用BeautifulSoup把结果整理成结构化数据。工具是死的,人是活的,怎么顺手怎么来。
想要更多资料,联系可可