今天我们来学习 Python 里超实用的字符串匹配和正则表达式。这是处理文本数据的神器,无论是爬虫、数据清洗还是文本分析,都离不开它,我们从基础语法讲起,再到实战场景,深入体会正则的妙用。
1. re 库
正则表达式(Regular Expression,简称regex或regexp)是一种用来匹配字符串的强大工具。它由一串字符和特殊符号组成,用于描述或匹配一系列符合某种模式的字符串。正则表达式广泛应用于文本搜索、文本替换等操作中。Python中的re
模块提供了对正则表达式的全面支持。
1.1 正则表达式基础语法
1.1.1 正则表达式基本元素
符号 | 描述 | 示例 |
. | 匹配除换行符外的任意字符 | a.c 可匹配 “abc”, “a1c” 等 |
^ | 匹配字符串的开始 | ^hello 匹配以 “hello” 开始的字符串 |
$ | 匹配字符串的结束 | world$ 匹配以 “world” 结束的字符串 |
* | 匹配前面子表达式 0 次或多次 | ab*c 匹配 “ac”, “abc”, “abbc” 等 |
+ | 匹配前面子表达式 1 次或多次 | ab+c 匹配 “abc”, “abbc” 等,但不匹配 “ac” |
? | 匹配前面子表达式 0 次或 1 次 | colou?r 匹配 “color” 或 “colour” |
.
用来匹配除换行符外的任意字符
python">import re
# 示例:匹配号码格式的字符串
text = "我的号码是 A123-456 和 B789+012"
pattern = r"A1..-4.."
match = re.search(pattern, text)
print(match.group()) # 输出:A123-456
# 这里的 "A1..-4.." 中的每 “.” 都匹配一个字符,就像“万能胶”
^
匹配字符串的开始
python">import re
# 示例:检查用户名是否以特定前缀开头
text = "user12345"
pattern = r"^user" # 匹配以 "user" 开头的字符串
match = re.search(pattern, text)
print("用户名以 'user' 开头:", bool(match))
$
匹配字符串的结束
python">import re
# 示例:检查一段话是否以问句结束
text = "这是为什么呢?"
pattern = r"呢?$" # 匹配以 "呢?" 结尾的字符串
match = re.search(pattern, text)
print("句子以 '呢?' 结尾:", bool(match))
*
匹配前面子表达式 0 次或多次
python">import re
# 示例:匹配某人吃了多少块披萨
text = "他吃了披萨pizza, pppppizza, 和 pizza!"
pattern = r"p*izza" # 匹配 "pizza" 或 "pppppizza" 等
matches = re.findall(pattern, text)
print(matches) # 输出:['pizza', 'pppppizza', 'pizza']
+
匹配前面子表达式 1 次或多次
python">import re
# 示例:匹配某人的电子邮件地址是否包含至少一个 @
text = "我的邮箱是 test@domain.com 和 another@test.com"
pattern = r"\w+@\w+\.\w+" # 匹配包含至少一个 "@" 的邮箱地址
matches = re.findall(pattern, text)
print(matches) # 输出:['test@domain.com', 'another@test.com']
?
匹配前面子表达式 0 次或 1 次
1.1.2. 特殊字符集
符号 | 描述 | 示例 |
\d | 匹配一个数字字符 | \d\d\d-\d\d\d-\d\d\d\d 匹配电话号码格式 |
\D | 匹配非数字字符 | \D{3} 匹配三个非数字字符 |
\w | 匹配字母、数字或下划线 | \w+ 匹配单词 |
\W | 匹配非字母、数字或下划线 | \W+ 匹配非单词字符 |
\s | 匹配任何空白字符 | \s+ 匹配空格、制表符等 |
\d
用来匹配一个数字字符
python">import re
text = "我有6颗苹果!"
pattern = r"我有 \d+ 颗苹果" # 匹配以数字表示的苹果数量
print(re.findall(pattern, text)) # 输出:['我有6颗苹果']
\D
匹配非数字字符
python">import re
text = "这是一段没有数字的文字!"
pattern = r"\D+" # 匹配所有非数字字符
print(re.findall(pattern, text)) # 输出:['这是一段没有数字的文字!\n']
# 这里非数字字符的范围很广,包括了字母和标点
\w
匹配字母、数字或下划线
python">import re
text = "我的用户名是_user123"
pattern = r"\w+" # 匹配用户名中的字母、数字和下划线
print(re.findall(pattern, text)) # 输出:['我的用户名是', '_user123']
\W
匹配非字母、数字或下划线,即匹配特殊字符
python">import re
text = "这是一段$带有特殊字符的文本!@#"
pattern = r"\W+" # 匹配所有非字母、数字或下划线的字符
print(re.findall(pattern, text)) # 输出:['$', '!', '@#']
\s
匹配任何空白字符
python">import re
text = "这是 一段有 多余空格 的 文本。"
pattern = r"\s+" # 匹配文本中的所有空白字符
print(re.findall(pattern, text)) # 输出:[' ', ' ', ' ', ' ']
1.2 re模块常用函数
函数 | 用法 | 示例 |
search() | 扫描整个字符串并返回第一个成功的匹配 | re.search(r'ain', 'The rain in Spain') |
match() | 尝试从字符串的起始位置匹配一个模式 | re.match(r'The', 'The rain in Spain') |
findall() | 返回所有与模式匹配的列表 | re.findall(r'ain', 'The rain in Spain') |
sub() | 替换所有匹配项为指定字符串 | re.sub(r'Spain', 'France', 'The rain in Spain') |
split() | 根据模式分割字符串 | re.split(r'\s+', 'The rain in Spain') |
re.search()
用于在字符串中寻找第一个匹配的模式。如果找到匹配,返回一个匹配对象,否则返回 None
。
python">import re
text = "Hello, World!"
pattern = r"World"
result = re.search(pattern, text)
if result:
print("匹配成功!")
print("匹配位置:", result.start(), result.end())
else:
print("匹配失败!")
# 匹配成功!匹配位置: 7 12
re.findall()
用于找到字符串中所有匹配的模式,返回一个列表。
python">import re
text = "I have 1 apple, 2 bananas, and 3 oranges"
pattern = r"\d+"
result = re.findall(pattern, text)
print(result) # 输出:['1', '2', '3']
re.sub()
用于替换字符串中匹配的模式。它会将所有匹配的内容替换为指定的字符串。
python">import re
text = "I have 1 apple, 2 bananas, and 3 oranges"
pattern = r"\d+"
replacement = "many"
result = re.sub(pattern, replacement, text)
print(result) # 输出:I have many apple, many bananas, and many oranges
re.match()
专门用来匹配字符串开头的部分,如果字符串的开头符合给定的模式,它会返回一个匹配对象;否则返回 None
。
python">import re
# 示例:检查字符串是否以数字开头
text = "123 是一个数字"
pattern = r"\d+" # 匹配一个或多个数字
# 使用 re.match()
result = re.match(pattern, text)
if result:
print("匹配成功!匹配的内容是:", result.group())
else:
print("匹配失败!")
#运行结果:匹配成功!匹配的内容是: 123
re.split()
可以根据指定的正则表达式模式将字符串分割成多个部分,并返回一个列表。
python">import re
# 示例:按逗号或空格分割字符串
text = "apple, banana orange,grape"
pattern = r"[,\s]+" # 匹配逗号或空格
# 使用 re.split()
result = re.split(pattern, text)
print("分割后的结果:", result)
# 分割后的结果: ['apple', 'banana', 'orange', 'grape']
1.3 复杂模式
正则表达式不仅能匹配单个字符,还能通过一些特殊符号和语法构建复杂的匹配模式。下面我们来介绍几个常用的复杂模式构建方法。
符号 | 用法 | 示例 |
{n} | 匹配前一字符恰好 n 次 | a{2} 匹配 “aa” |
{n,} | 至少匹配 n 次 | a{2,} 匹配 “aa”, “aaa” 等 |
{n,m} | 最少匹配 n 次且最多 m 次 | a{2,3} 匹配 “aa”, “aaa” |
[abc] | 字符集合,匹配括号内任一字符 | [abc] 匹配 “a”, “b”, 或 “c” |
{n}
:该模式表示前一个字符必须出现恰好 n
次。
python">import re
# 示例:匹配恰好 4 个连续的 'a'
text = "aaaa"
pattern = r"a{4}"
match = re.match(pattern, text)
print(match.group() if match else "No match") # 输出:aaaa
{n,}
:至少匹配 n
次,该模式表示前一个字符至少出现 n
次,可以无限次。
python">import re
# 示例:匹配至少 2 个连续的 'a'
text = "aaaaaaaaaaa"
pattern = r"a{2,}"
match = re.match(pattern, text)
print(match.group() if match else "No match") # 输出:aaaaaaaaaaa
{n,m}
:该模式表示前一个字符至少出现 n
次,但不超过 m
次。
python">import re
# 示例:匹配 1 到 3 个连续的 'a'
text = "aaabb"
pattern = r"a{1,3}"
match = re.match(pattern, text)
print(match.group() if match else "No match") # 输出:aaa
[abc]
:该模式表示匹配括号内的任一字符。
python">import re
# 示例:匹配 'a' 或 'b' 或 'c'
text = "apple"
pattern = r"[abc]"
match = re.search(pattern, text)
print(match.group() if match else "No match") # 输出:a
🎯Tips:
对于非常复杂的模式,建议分步骤构建并测试每个部分,想一口吃撑胖子很容易出错,分而治之是很好的思想。
1.4 贪婪与非贪婪模式
正则表达式中的贪婪模式和非贪婪模式是两种不同的匹配方式。在贪婪模式下,匹配尽可能多的字符,而非贪婪模式则匹配尽可能少的字符。
例如,对于字符串 “aaaab”,模式 “a+”(贪婪)将匹配整个 “aaaa”,而 “a+?”(非贪婪)将每次匹配一个 “a”。
1.4.1 贪婪模式
默认情况下,正则表达式的量词(如 *
, +
, ?
, {n,}
, {n,m}
)都是贪婪的,会尽可能多地匹配字符。
python">import re
text = "abbbc"
pattern = r"ab+" # 贪婪模式,匹配尽可能多的 b
match = re.search(pattern, text)
print(match.group()) # 输出:abbb
# 这里的正则表达式会匹配尽可能多的 'b',因为它是贪婪的。
1.4.2 非贪婪模式
在量词后加上问号?
,即可启用非贪婪模式,匹配尽可能少的字符。
python">import re
text = "abbbc"
pattern = r"ab+?" # 非贪婪模式,匹配尽可能少的 b
match = re.search(pattern, text)
print(match.group()) # 输出:ab
# 这里的正则表达式会匹配最少的 'b',即只匹配一个 'b'。
在实际使用中,我们通常:
- 使用
*
等贪婪匹配来处理简单的字符串操作。 - 当需要精确控制匹配范围时,使用非贪婪模式来避免过度匹配。
例如我们在匹配网页源码时,匹配标签中的内容:
python">text = "I have <div>Hello, <span>World!</span></div>"
pattern_greedy = r"<.*>"
pattern_non_greedy = r"<.*?>"
result_greedy = re.findall(pattern_greedy, text)
result_non_greedy = re.findall(pattern_non_greedy, text)
print("贪婪匹配结果:", result_greedy) # 输出:['<div>Hello, <span>World!</span></div>']
print("非贪婪匹配结果:", result_non_greedy) # 输出:['<div>', '<span>']
2. 实际应用
2.1 邮箱/手机号格式验证
在用户注册或数据输入时,验证邮箱和手机号的格式是非常重要的。我们可以用正则表达式来实现。
(1)邮箱验证📧
python">import re
def validate_email(email):
pattern = r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$"
if re.match(pattern, email):
return True
else:
return False
email = "test@exa@mple.com"
if validate_email(email):
print("邮箱格式正确!")
else:
print("邮箱格式错误!")
(2)手机号验证📱
python">import re
def validate_phone(phone):
pattern = r"^1[3-9]\d{9}$"
if re.match(pattern, phone):
return True
else:
return False
phone = "13812345678"
if validate_phone(phone):
print("手机号格式正确!")
else:
print("手机号格式错误!")
(3)手机号提取📱
python">import re
phone_pattern = r"(?<!\d)(1[3-9]\d{9})(?!\d)"
text = "紧急联系13812345678,备用19198765432"
phones = re.findall(phone_pattern, text)
print("提取到的手机号:", phones)
#提取到的手机号: ['13812345678', '19198765432']
2.2 HTML 文件解析
在爬虫或数据提取中,经常需要从 HTML 中提取内容。正则表达式可以帮助我们快速提取标签中的内容。
python">import re
html = """
<div class='news'>
<h1>今日热榜</h1>
<p>Python 3.12发布啦!</p>
</div>
"""
# 提取所有标签内容
contents = re.findall(r"<.*?>(.*?)</.*?>", html)
print("HTML内容提取:", contents) # ['今日热榜', 'Python 3.12发布啦!']
# 提取指定标签内容
h1_content = re.search(r"<h1>(.*?)</h1>", html).group(1)
print("头条标题:", h1_content) # 今日热榜
2.3 日志分析
在处理日志文件时,我们可能需要提取错误信息。正则表达式可以帮助我们快速过滤出错误信息。
python">log = """
[ERROR] 2023-08-10 14:22:35 数据库连接失败
[INFO] 2023-08-10 14:23:10 用户登录成功
[WARNING] 2023-08-10 14:24:02 内存使用率80%
"""
# 提取错误信息
errors = re.findall(r"\[ERROR\].+", log)
print("系统错误记录:", errors) # ['[ERROR] 2023-08-10 14:22:35 数据库连接失败']
2.4 综合案例:简历解析
我们有一个简历文本,现在要使用正则提取其中的主要信息,保存到 json 中。
完整代码:
python">import re
resume = """
姓名:张三
电话:138-1234-5678
邮箱:zhangsan@example.com
技能:Python, 正则表达式, 数据分析
项目经验:
1. <项目名称>智能客服系统</项目名称>
<描述>使用正则表达式实现对话指令解析</描述>
2. <项目名称>日志分析平台</项目名称>
<描述>开发基于正则的日志过滤模块</描述>
"""
# 信息提取
def parse_resume(text):
info = {
"name": re.search(r"姓名:(.*)", text).group(1),
"phone": re.search(r"电话:(.*)", text).group(1),
"email": re.search(r"邮箱:(.*)", text).group(1),
"skills": re.findall(r"技能:(.+)", text)[0].split(", "),
"projects": re.findall(r"<项目名称>(.*?)</项目名称>\s+<描述>(.*?)</描述>", text)
}
return info
print(parse_resume(resume))
运行结果:
{'name': '张三', 'phone': '138-1234-5678', 'email': 'zhangsan@example.com', 'skills': ['Python', '正则表达式', '数据分析'], 'projects': [('智能客服系统', '使用正则表达式实现对话指令解析'), ('日志分析平台', '开发基于正则的日志过滤模块')]}
3. 辅助开发工具
在学习和使用正则表达式时,可以使用在线测试工具,如 regex101.com。它支持多种编程语言的正则表达式测试,还能提供详细的匹配结果和解释,非常方便。
🔧 RegEx 调试神器地址: regex101.com
- 实时高亮匹配 → 高亮匹配结果
- 解释模式 → 自动翻译正则语法
- 测试用例库 → 自带常见场景用例
4. 小结
正则表达式是文本处理的强大工具,掌握了它,你就能轻松应对各种文本匹配和提取任务。在学习正则表达式的过程中,需要注意以下几点:
理解元字符的含义 :元字符是正则表达式的核心,需要熟练掌握各个元字符的含义和用法,如 .
、^
、$
、*
、+
、?
等。
注意特殊字符的转义 :在正则表达式中,一些特殊字符如 .
、*
、+
等具有特殊含义,如果需要匹配这些字符本身,需要进行转义,即在字符前加上反斜杠 \
。
掌握常用函数的用法 :re
模块提供了许多常用的函数,如 search()
、match()
、findall()
、sub()
、split()
等,需要熟练掌握这些函数的用法和区别。
理解贪婪与非贪婪模式 :贪婪模式会尽可能多地匹配字符,而非贪婪模式则尽可能少地匹配字符。在实际应用中,需要根据具体需求选择合适的模式。
注意调试和优化 :在编写复杂的正则表达式时,要注意调试和优化,避免出现错误和性能问题。可以使用在线测试工具如 regex101.com 进行调试和测试。
多多练习,熟练掌握正则表达式,人人都能成为文本处理的高手!