Python 正则表达式全面指南
爬取电商详情页抠取实时价格、解析服务器日志提取高危IP、批量清理文本里的乱码标签——这些高频且细碎的文本处理场景,正则表达式(Regular Expression,下文简称正则)都是Pythoner的效率神器。本文将聚焦Python内置re模块,从基础语法到高级技巧、再到爬虫实战,系统性梳理它的用法。
1. 正则表达式基础
1.1 什么是正则
正则是一套专门用来「描述字符串规则」的语法体系——你可以把它理解为文本的「筛子模版」:模版上画好特定的符号组合,符合规则的文本就会被精准「筛出来」,也可以替换掉筛到的部分。
1.2 入门试手工具
先别急着写Python代码,用轻量级在线工具验证思路最快!推荐两款中文常用工具:
拿一段测试文本做实验:
我的电话号码是:13812345678,备用邮箱是:user.name@mail.co.uk,测试网站:https://blog.example.com/posts/123#comment
这里有两个入门匹配演示(注意基础模式的局限性):
- 匹配简单手机号/纯数字前缀单域名邮箱:
\w+@\w+\.\w+ → 仅能抓到类似example@domain.com的邮箱
- 匹配带可选https的无边界URL:
https?://[^\s]+ → 基本能抓全常见URL,但会带上末尾的标点
2. Python re 模块核心方法
Python没有内置正则引擎,但提供了标准库re,封装了常用的操作接口,先看一张速查表:
3. 常用匹配方法详解
3.1 match():开头匹配法
这个方法有个「硬性要求」——必须从字符串第一个字符开始就完全符合规则,否则直接返回None,适合做格式严格的开头验证。
基础用法
import re
# 测试文本开头必须是Hello + 3位数字 + 4位数字 + 10位字母/数字/下划线
content = 'Hello 123 4567 World_This'
pattern = r'^Hello\s\d{3}\s\d{4}\s\w{10}' # 注意加r前缀避免转义冲突
result = re.match(pattern, content)
if result:
print(result.group()) # 打印完整匹配内容:Hello 123 4567 World_This
print(result.span()) # 打印匹配的起始/结束索引:(0, 25)
分组提取(核心功能!)
如果需要从匹配到的文本里单独抠出某一部分,就用()把要抠的内容「框起来」——这就是分组。group(0)是完整匹配,group(1)是第一个括号的内容,group(2)第二个,以此类推。
content = 'Hello 1234567 World_This'
pattern = r'^Hello\s(\d+)\sWorld'
result = re.match(pattern, content)
if result:
print(result.group(1)) # 单独抠出数字:1234567
3.2 search():全局扫描找第一个
和match()不同,search()会跳过开头不符合的部分,找到整个字符串里第一个符合规则的就返回,日常用得比match()多得多。
content = '开头的废话 Extra Hello 1234567 World_This'
pattern = r'Hello\s(\d+)\sWorld'
result = re.search(pattern, content)
if result:
print(result.group(1)) # 依然能抠到:1234567
3.3 findall():全局扫描找所有
如果需要一次性提取所有符合规则的内容(比如爬取整个商品列表的所有链接),就用findall()——它返回的是列表,不用再循环判断。
# 模拟一个简单的HTML商品列表片段
html = '''
<ul class="product-list">
<li><a href="https://shop.example.com/p1">苹果15Pro</a></li>
<li><a href="https://shop.example.com/p2">华为Mate60</a></li>
<li><a href="https://shop.example.com/p3">小米14Ultra</a></li>
</ul>
'''
# 分组提取链接和商品名,re.S让.匹配换行符
pattern = r'<li><a href="(.*?)">(.*?)</a></li>'
results = re.findall(pattern, html, re.S)
for url, name in results:
print(f'商品:{name},链接:{url}')
4. 高级匹配技巧
4.1 贪婪 vs 非贪婪匹配(爬虫必懂!)
这是新手最容易踩的坑——.*和*这类默认是贪婪匹配:会尽可能多地「吞掉」后面的字符;而.*?和+?这类是非贪婪匹配:会尽可能少地「吞掉」,找到下一个规则的边界就停。
content = 'He1234567WoDemo'
# 贪婪匹配:.*吞掉了123456,只留最后一个7给\d+
pattern_greedy = r'^He.*(\d+).*Demo$'
result_greedy = re.search(pattern_greedy, content)
print(result_greedy.group(1)) # 7
# 非贪婪匹配:.*?只吞到第一个数字前就停,把1234567全留给\d+
pattern_lazy = r'^He.*?(\d+).*Demo$'
result_lazy = re.search(pattern_lazy, content)
print(result_lazy.group(1)) # 1234567
4.2 修饰符(让正则更灵活)
修饰符可以改变正则的默认匹配规则,常用的有3个:
4.3 转义匹配(处理特殊字符)
正则里有很多「特殊符号」(比如.*()+[]?),如果要匹配这些符号本身的字面意思,必须在前面加一个反斜杠\进行转义。
content = '(CSDN)https://blog.csdn.net'
pattern = r'\(CSDN\)https://blog\.csdn\.net'
result = re.search(pattern, content)
if result:
print('匹配成功')
5. 实用辅助方法
5.1 sub():批量替换
这个方法可以一次性替换所有匹配到的文本,适合清理乱码、标签、敏感词等。
# 把文本里的所有数字替换成空,提取纯字母
content = '54aK54yr5oiR54ix5L2g'
clean_content = re.sub(r'\d+', '', content)
print(clean_content) # aKyroiRixLg
# 批量替换HTML的<p>标签为换行
html_p = '<p>第一行</p><p>第二行</p>'
clean_html = re.sub(r'</?p>', '\n', html_p).strip() # strip()去掉首尾换行
print(clean_html)
5.2 compile():预编译正则(性能优化)
如果需要对大量文本重复使用同一个正则(比如爬1000个商品页都用同一个价格匹配规则),先用re.compile()预编译成Pattern对象,可以避免每次匹配都重新解析正则语法,提升效率。
# 预编译一个时间格式的正则(HH:MM)
time_pattern = re.compile(r'\d{2}:\d{2}')
# 对多个文本重复使用
texts = ['现在是12:34', '明天23:45下班', '后天休息']
for text in texts:
result = time_pattern.search(text)
if result:
print(f'找到时间:{result.group()}')
6. 常用匹配模式速查表
7. 性能优化小提示
虽然正则很强大,但滥用可能导致性能瓶颈甚至卡死程序(比如复杂的贪婪匹配导致的「回溯爆炸」),记住这几点就能避免大部分问题:
- 优先用非贪婪匹配
.*?,减少回溯次数
- 大量重复匹配时用
re.compile()预编译
- 能用字符串内置方法(比如
find()、replace())解决的,就别用正则
- 合理设置边界字符,比如用
[^"]代替.*?匹配双引号里的内容
正则表达式是爬虫开发的入门必备技能,也是文本处理的「瑞士军刀」——掌握基础语法后,建议多拿真实的HTML、日志文本练手,慢慢积累常用的模式库~