Python 正则表达式全面指南

爬取电商详情页的实时价格、解析服务器日志提取高危 IP、批量清洗文本里的乱码标签——这些高频又细碎的文本处理任务,正则表达式(Regular Expression,简称正则) 都是 Python 开发者的效率神器。本文将聚焦 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、日志文本练手,慢慢积累常用的模式库~