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的无边界URLhttps?://[^\s]+ → 基本能抓全常见URL,但会带上末尾的标点

2. Python re 模块核心方法

Python没有内置正则引擎,但提供了标准库re,封装了常用的操作接口,先看一张速查表:

方法作用关键返回值
re.match()从字符串开头强制匹配成功返回Match对象,失败返回None
re.search()扫描整个字符串,返回第一个匹配re.match()
re.findall()扫描整个字符串,返回所有匹配结果成功返回列表(分组匹配返回元组列表),失败返回空列表
re.sub()替换所有匹配到的文本成功返回替换后的新字符串
re.compile()预编译正则表达式(性能优化用)返回Pattern对象,可复用执行匹配/替换操作

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个:

修饰符核心作用爬虫场景
re.I忽略大小写匹配图片时不管是.jpg还是.JPG
re.S.匹配换行符解析HTML标签时经常跨多行
re.M多行模式(^/$匹配每行的开头/结尾)解析日志文件的多行记录

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. 常用匹配模式速查表

模式描述常用场景
.匹配任意字符(除了换行符,除非加re.S)临时占位跨字符
\w匹配字母、数字、下划线匹配用户名、文件名前缀
\s匹配空白字符(空格、制表符、换行符)匹配文本里的间隔
\d匹配数字匹配价格、手机号、ID
^匹配字符串开头开头格式验证
$匹配字符串结尾结尾格式验证
*匹配前面的字符0次或多次匹配可选的重复内容
+匹配前面的字符1次或多次匹配必须存在的重复内容
?匹配前面的字符0次或1次匹配可选的单个内容
{n}匹配前面的字符恰好n次匹配固定长度的数字/字母
{n,}匹配前面的字符至少n次匹配长文本片段
[abc]匹配a、b、c中的任意一个匹配有限的可选字符
[^abc]匹配除了a、b、c之外的任意字符匹配有限的排除字符

7. 性能优化小提示

虽然正则很强大,但滥用可能导致性能瓶颈甚至卡死程序(比如复杂的贪婪匹配导致的「回溯爆炸」),记住这几点就能避免大部分问题:

  1. 优先用非贪婪匹配.*?,减少回溯次数
  2. 大量重复匹配时用re.compile()预编译
  3. 能用字符串内置方法(比如find()replace())解决的,就别用正则
  4. 合理设置边界字符,比如用[^"]代替.*?匹配双引号里的内容

正则表达式是爬虫开发的入门必备技能,也是文本处理的「瑞士军刀」——掌握基础语法后,建议多拿真实的HTML、日志文本练手,慢慢积累常用的模式库~