在编程世界里,有一项技能一旦掌握,就会让你在处理文本时拥有“降维打击”的能力——这就是正则表达式。而Python标准库中的re模块,正是正则表达式在Python生态中的官方实现。无论你是需要从一大段日志中提取IP地址、验证用户输入的手机号格式、批量替换文档中的敏感词,还是从杂乱无章的网页源码里抓取邮箱,re都能用简洁而强大的模式匹配语法解决这些看似复杂的文本问题。

在实际生活中,re的应用场景几乎无处不在:后端开发用它做表单输入的合法性校验(邮箱、手机号、身份证);运维工程师用它从海量日志中grep出异常堆栈;数据清洗员用它批量规范化CSV中的日期格式;爬虫工程师用它从HTML中精准提取价格、评论数等结构化数据;自然语言处理用它做分词前的文本预处理;甚至你每天使用的IDE或编辑器的“查找替换”功能中的“使用正则表达式”选项,背后也是类似re的引擎。它的核心价值在于用一套模式语言描述无穷的文本规律——原本需要几十行字符串切片和条件判断才能完成的任务,用正则往往一两行就能优雅解决。

下面将从最基础的匹配开始,带你系统掌握Python中正则表达式的正确打开方式。

一、安装库

re是Python的标准库模块,无需任何安装,直接导入即可:

python

import re
print("re模块已就绪,版本:", re.__version__)  # 注意re没有独立版本,随Python发行

二、基本用法——4步从零构建第一个正则匹配

以下4个步骤,让你快速掌握re模块的核心API:matchsearchfindallsub

1. 使用re.match()从字符串开头匹配

match()从字符串的起始位置开始匹配,如果开头不满足模式,则返回None

python

import re

pattern = r'\d+'  # 匹配一个或多个数字
text = '123abc456'

result = re.match(pattern, text)
if result:
    print(f"匹配成功: {result.group()}")  # 输出: 123
else:
    print("匹配失败")

注意:原始字符串r''中的反斜杠不会被转义,推荐在正则表达式中始终使用。

2. 使用re.search()扫描整个字符串

search()扫描整个字符串,返回第一个匹配的位置。

python

text = '订单号: ABC-12345-XYZ'
pattern = r'\d{5}'  # 匹配连续5个数字

match = re.search(pattern, text)
if match:
    print(f"找到: {match.group()}")  # 输出: 12345
    print(f"起始索引: {match.start()}, 结束索引: {match.end()}")
else:
    print("未找到")
3. 使用re.findall()获取所有匹配结果

findall()返回所有非重叠匹配的列表,是最常用的提取函数。

python

text = '我的邮箱是: alice@example.com, bob@gmail.com, chris@163.com'
pattern = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'

emails = re.findall(pattern, text)
print(emails)
# 输出: ['alice@example.com', 'bob@gmail.com', 'chris@163.com']
4. 使用re.sub()进行替换

sub()将匹配到的内容替换为指定字符串,是文本清洗的利器。

python

text = '联系电话: 138-1234-5678, 办公室: 010-87654321'
# 将所有电话号码的连字符替换为空,只保留数字
cleaned = re.sub(r'-', '', text)
print(cleaned)  # 输出: 联系电话: 13812345678, 办公室: 01087654321

# 结合分组实现更灵活的替换
text2 = '2024-05-12, 2025-12-25'
# 将日期格式从 YYYY-MM-DD 转为 DD/MM/YYYY
converted = re.sub(r'(\d{4})-(\d{2})-(\d{2})', r'\3/\2/\1', text2)
print(converted)  # 输出: 12/05/2024, 25/12/2025

三、高级用法——让正则如虎添翼

1. 编译正则表达式:re.compile()

当同一个正则表达式需要重复使用时,提前编译可以提升性能,并使代码更清晰。

python

# 编译一个邮箱正则
email_regex = re.compile(r'\b[\w.%+-]+@[\w.-]+\.[A-Za-z]{2,}\b')

text = '联系: support@company.com, 投诉: feedback@company.com'
matches = email_regex.findall(text)
print(matches)  # ['support@company.com', 'feedback@company.com']

# 编译时可以指定标志,如忽略大小写
case_insensitive = re.compile(r'python', re.IGNORECASE)
print(case_insensitive.findall('I love Python and PYTHON'))  # ['Python', 'PYTHON']
2. 分组捕获与命名分组

括号()用于分组,可以提取子模式;(?P<name>)可以给分组命名,让代码更可读。

python

text = '姓名: 张三, 年龄: 28, 邮箱: zhangsan@163.com'

# 普通分组
pattern = r'姓名: (.+), 年龄: (\d+), 邮箱: (.+)'
match = re.search(pattern, text)
if match:
    print(f"姓名: {match.group(1)}")   # 张三
    print(f"年龄: {match.group(2)}")   # 28
    print(f"邮箱: {match.group(3)}")   # zhangsan@163.com

# 命名分组
pattern_named = r'姓名: (?P<name>.+), 年龄: (?P<age>\d+), 邮箱: (?P<email>.+)'
match = re.search(pattern_named, text)
if match:
    print(match.group('name'), match.group('age'), match.group('email'))
    # 获取所有分组字典
    print(match.groupdict())  # {'name': '张三', 'age': '28', 'email': 'zhangsan@163.com'}
3. 正则标志:多行、点号匹配换行、详细模式

python

text = '''第一行
第二行
第三行'''

# re.MULTILINE: ^ 和 $ 匹配每行的开头/结尾
multi_pattern = re.compile(r'^\w+', re.MULTILINE)
print(multi_pattern.findall(text))  # ['第一行', '第二行', '第三行']

# re.DOTALL: 让 . 匹配包括换行在内的所有字符
dotall_pattern = re.compile(r'第一行(.*?)第三行', re.DOTALL)
print(dotall_pattern.search(text).group(1))  # 匹配换行符在内的中间内容

# re.VERBOSE: 允许正则中包含空白和注释,提升可读性
verbose_pattern = re.compile(r"""
    \d{3}   # 区号
    -       # 连字符
    \d{8}   # 号码
""", re.VERBOSE)
print(verbose_pattern.findall('电话: 010-12345678'))  # ['010-12345678']
4. 贪婪与非贪婪匹配

*+?{m,n}默认是贪婪的(尽可能多匹配),在后面加?变为非贪婪(尽可能少匹配)。

python

html = '<div>内容1</div><div>内容2</div>'

# 贪婪匹配:匹配尽可能长的字符串
greedy = re.findall(r'<div>.*</div>', html)
print(greedy)  # ['<div>内容1</div><div>内容2</div>']

# 非贪婪匹配:匹配最短的合规字符串
non_greedy = re.findall(r'<div>.*?</div>', html)
print(non_greedy)  # ['<div>内容1</div>', '<div>内容2</div>']

四、实际应用场景——三个有深度的案例,日常就能用

场景一:从杂乱文本中提取所有URL

无论是爬虫还是日志分析,提取文本中的URL是一个经典需求。

python

import re

def extract_urls(text):
    # 该正则匹配http/https/ftp开头的URL,支持常见字符
    url_pattern = r'https?://[^\s<>"\'{}|\\^`\[\]]+'
    urls = re.findall(url_pattern, text)
    return list(set(urls))  # 去重

sample = """
请访问我们的官网 https://www.example.com 获取更多信息。
你也可以使用 http://docs.python.org 查阅文档。
另外,内部地址 http://192.168.1.1/admin 仅内网访问。
重复的官网 https://www.example.com 会去重。
"""
print(extract_urls(sample))
# 输出: ['http://192.168.1.1/admin', 'https://www.example.com', 'http://docs.python.org']
场景二:验证并清洗用户输入(手机号/身份证)

在日常表单验证中,正则是最便捷的校验手段。

python

import re

def validate_phone(phone):
    # 中国大陆手机号:1开头,第二位3-9,后面9位数字
    pattern = r'^1[3-9]\d{9}$'
    if re.match(pattern, phone):
        return True, "手机号有效"
    else:
        return False, "手机号格式错误"

def mask_id_card(id_num):
    # 将身份证号中间8位替换为********
    pattern = r'(\d{4})\d{10}(\d{4})'  # 前4位+后4位保留,中间10位变成8个*
    masked = re.sub(pattern, r'\1********\2', id_num)
    return masked

print(validate_phone('13812345678'))  # (True, '手机号有效')
print(validate_phone('12345678901'))  # (False, '手机号格式错误')
print(mask_id_card('11010119900307663X'))  # 1101********7663X
场景三:解析日志文件中的错误行与时间戳

运维经常需要从大型日志中筛选出ERROR级别的日志,并提取发生时间。

python

import re

log_sample = """
2025-05-12 08:30:15 INFO 服务启动成功
2025-05-12 09:45:22 ERROR 数据库连接超时,重试中
2025-05-12 10:02:07 WARN 磁盘使用率85%
2025-05-12 10:15:39 ERROR 用户登录失败,密码错误
"""

# 匹配ERROR级别所在的行,并提取时间和消息
error_pattern = re.compile(r'(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\s+ERROR\s+(.+)')

errors = []
for line in log_sample.splitlines():
    match = error_pattern.search(line)
    if match:
        timestamp, message = match.groups()
        errors.append({'time': timestamp, 'message': message})

for err in errors:
    print(f"[{err['time']}] 错误: {err['message']}")
# 输出:
# [2025-05-12 09:45:22] 错误: 数据库连接超时,重试中
# [2025-05-12 10:15:39] 错误: 用户登录失败,密码错误

五、结尾:总结与互动

re模块是Python文本处理能力皇冠上的明珠。它以极少的代码量完成看似复杂的匹配、查找、替换任务,是日常自动化脚本、数据清洗、爬虫、表单验证等场景中不可或缺的工具。但正则表达式也是一把双刃剑——过于复杂的正则不仅难以调试,还可能造成灾难性的回溯性能问题(例如(a+)+b匹配超长字符串)。因此,建议在编写正则时遵循“简单够用”的原则:能用字符串方法解决的(如startswithin)就不用正则;对于复杂正则,拆分成多个小正则,并利用re.VERBOSE添加注释。

现在,轮到你把正则用起来了:有没有遇到过需要从一大段文本里提取结构化数据的需求?比如提取某个应用日志中的所有IP地址、解析商品页面里的价格数字、或者验证用户输入的密码强度(至少一个大写、一个小写、一个数字、长度8-16位)?欢迎在评论区分享你曾经用正则解决过的真实问题,或者留下你在使用re时遇到的疑难杂症(比如re.matchre.search什么时候用、.*?到底怎么工作)——我会挑选经典问题,在后续文章中详细解答。快去尝试用正则优雅地处理你身边的文本吧!

更多推荐