正则表达式(Regular Expression,简称 regex 或 RE)是 Python 中处理文本数据的瑞士军刀。它是一种强大的工具,用于描述、匹配和操作字符串模式。无论你是数据分析师清洗数据、开发人员验证用户输入,还是自动化脚本编写者提取日志信息,正则表达式都能显著提高效率。
本指南将从基础概念入手,逐步深入到高级技巧,并通过丰富的实战案例(包括代码示例)帮助你掌握 Python 的
re
模块。我们将重点解决数据匹配、提取和验证三大难题。文章基于 Python 3.x 标准库的
re
模块,确保内容准确且实用。
1. 正则表达式基础:理解核心概念
正则表达式本质上是一种描述字符串模式的语法。它不是 Python 独有的,而是广泛应用于各种编程语言。在 Python 中,我们通过内置的
re
模块来使用它。
1.1 为什么使用正则表达式?
匹配(Matching)
:检查字符串是否符合特定模式(如判断是否为邮箱)。
提取(Extracting)
:从字符串中提取子字符串(如从日志中提取日期)。
验证(Validating)
:确保数据格式正确(如验证电话号码)。
替换(Replacing)
:修改字符串中的模式(如批量替换敏感词)。
相比简单的字符串方法(如
str.find()
或
str.split()
),正则表达式更灵活,能处理复杂模式。
1.2 Python re 模块入门
首先,导入
re
模块:
import re
核心函数包括:
re.match()
:从字符串开头匹配。
re.search()
:扫描整个字符串,返回第一个匹配。
re.findall()
:返回所有匹配的列表。
re.sub()
:替换匹配的模式。
re.compile()
:预编译正则表达式以提高性能。
示例:简单匹配
假设我们要检查字符串是否以 “Hello” 开头:
import re
pattern = r”Hello”
text = “Hello, World!”
# 使用 match 从开头匹配
match_obj = re.match(pattern, text)
if match_obj:
print(“匹配成功:”, match_obj.group()) # 输出: Hello
else:
print(“匹配失败”)
解释
:
r”Hello”
:
r
前缀表示原始字符串(raw string),避免反斜杠转义问题。
re.match()
只检查开头,如果文本是 “World, Hello”,则匹配失败。
match_obj.group()
返回匹配的字符串。
1.3 元字符(Metacharacters):正则表达式的构建块
正则表达式的核心是元字符,它们有特殊含义。以下是常见元字符:
元字符
含义
示例
.
匹配任意单个字符(除换行符)
a.c
匹配 “abc”, “a c”
^
匹配字符串开头
^Hello
匹配以 “Hello” 开头的字符串
$
匹配字符串结尾
World$
匹配以 “World” 结尾的字符串
*
匹配前一个字符 0 次或多次
ab*
匹配 “a”, “ab”, “abb”
+
匹配前一个字符 1 次或多次
ab+
匹配 “ab”, “abb”,但不匹配 “a”
?
匹配前一个字符 0 次或 1 次
ab?
匹配 “a”, “ab”
{n}
精确匹配前一个字符 n 次
a{3}
匹配 “aaa”
{n,}
匹配前一个字符至少 n 次
a{2,}
匹配 “aa”, “aaa”
{n,m}
匹配前一个字符 n 到 m 次
a{2,3}
匹配 “aa”, “aaa”
[]
字符集,匹配方括号内任意字符
[abc]
匹配 “a”, “b”, “c”
[^]
否定字符集,匹配不在方括号内的字符
[^abc]
匹配除 a,b,c 外的字符
|
或运算符
cat|dog
匹配 “cat” 或 “dog”
()
分组,捕获子模式
(ab)+
匹配 “ab”, “abab”
示例:使用元字符匹配日期格式(YYYY-MM-DD)
import re
pattern = r”^\d{4}-\d{2}-\d{2}$” # ^开头,$结尾,\d匹配数字,{n}指定次数
text1 = “2023-10-05”
text2 = “2023/10/05″
print(re.match(pattern, text1)) # 匹配成功,返回 match 对象
print(re.match(pattern, text2)) # 匹配失败,因为分隔符是 / 而非 –
解释
:
\d
是预定义字符类,等价于
[0-9]
。
^
和
$
确保整个字符串严格匹配,避免部分匹配(如 “2023-10-05 extra”)。
1.4 转义字符
如果需要匹配元字符本身(如点号
.
),使用反斜杠
\
转义:
\.
匹配实际的点。
示例
:匹配 IP 地址(简单版,假设 IPv4)。
pattern = r”^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$”
text = “192.168.1.1”
print(re.match(pattern, text)) # 匹配成功
2. 高级技巧:提升匹配精度和效率
掌握基础后,我们进入高级部分。这些技巧能处理更复杂的场景,如贪婪 vs 非贪婪匹配、分组和捕获。
2.1 预定义字符类
\d
:数字,等价于
[0-9]
。
\D
:非数字。
\w
:字母、数字、下划线,等价于
[a-zA-Z0-9_]
。
\W
:非
\w
。
\s
:空白字符(空格、制表符、换行等)。
\S
:非空白。
示例:提取邮箱地址
import re
text = “Contact: user@example.com or support@domain.org”
pattern = r”\b[\w.-]+@[\w.-]+\.\w+\b” # \b单词边界,避免匹配中间部分
emails = re.findall(pattern, text)
print(emails) # [‘user@example.com’, ‘support@domain.org’]
解释
:
[\w.-]+
:匹配用户名部分(字母数字、点、连字符,一次或多次)。
\b
:单词边界,确保不匹配 “user@example.com” 中的 “user” 部分。
re.findall()
返回所有匹配列表。
2.2 贪婪 vs 非贪婪匹配
默认情况下,量词(
*
,
+
,
?
,
{}
)是贪婪的,会匹配尽可能多的字符。非贪婪模式在量词后加
?
,匹配尽可能少。
示例
:提取 HTML 标签内容。
import re
html = “
World”
# 贪婪匹配:匹配整个
greedy = re.findall(r”<.*>“, html)
print(greedy) # [‘
World‘]
# 非贪婪匹配:只匹配单个标签
non_greedy = re.findall(r”<.*?>“, html)
print(non_greedy) # [‘
‘, ‘‘, ‘‘]
解释
:
贪婪模式的
.*
会从第一个
<
匹配到最后一个
>
。
非贪婪
.*?
会在遇到第一个
>
时停止。
2.3 分组与捕获
使用
()
分组,可以捕获子模式。
re.search()
或
re.match()
返回的匹配对象有
group(n)
方法获取第 n 个捕获组(n=0 是整个匹配)。
示例:从日志中提取日期和时间
import re
log = “2023-10-05 14:30:00 INFO: User logged in”
pattern = r”(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2})”
match = re.search(pattern, log)
if match:
date = match.group(1) # 第一个捕获组
time = match.group(2) # 第二个捕获组
print(f”Date: {date}, Time: {time}”) # Date: 2023-10-05, Time: 14:30:00
高级分组:命名捕获组(Python 3.6+)
使用
(?P
命名组,便于引用。
pattern = r”(?P