正则表达式是文本处理的强大工具,本文将系统全面地介绍正则表达式的所有知识点,结合Python的re模块,帮助读者从零开始掌握正则表达式的使用。
1. 正则表达式基础概念
1.1 什么是正则表达式?
正则表达式(Regular Expression,简称regex或RE)是一种用于描述字符串匹配规则的表达式,它并不是Python特有的,而是计算机科学中的一个通用概念。
核心功能:
-
验证:检查字符串是否符合特定格式(如邮箱、电话号码)
-
提取:从文本中提取符合规则的内容
-
替换:替换文本中的特定内容
1.2 为什么需要正则表达式?
假设我们需要:
-
验证用户输入的邮箱是否合法
-
从网页源码中提取所有URL
-
将文本中的所有日期格式统一
使用普通字符串方法实现这些功能会非常繁琐,而正则表达式可以简洁高效地完成这些任务。
2. 正则表达式基本语法
2.1 字符匹配
2.1.1 普通字符
大多数字符(字母、数字)会直接匹配它们自身:
import re
re.findall("abc", "abcde") # 匹配['abc']
2.1.2 元字符
具有特殊含义的字符:
元字符 | 说明 | 示例 |
---|---|---|
. | 匹配除换行符外的任意字符 | a.c → abc, a1c |
\d | 匹配数字,等价于[0-9] | \d\d → 12 |
\D | 匹配非数字 | \D\D → ab |
\w | 匹配字母、数字、下划线 | \w\w → a1, _a |
\W | 匹配非字母数字下划线 | \W\W → @# |
\s | 匹配空白字符(空格、制表符等) | a\sb → a b |
\S | 匹配非空白字符 | \S\S → ab |
2.2 字符组
用[]
表示,匹配其中任意一个字符:
[aeiou] # 匹配任意一个元音字母
[0-9] # 匹配任意数字,等价于\d
[a-zA-Z] # 匹配任意字母
[^0-9] # 匹配非数字,等价于\D
2.3 量词
控制前面元素的匹配次数:
量词 | 说明 | 示例 |
---|---|---|
* | 0次或多次 | a* → "", a, aa |
+ | 1次或多次 | a+ → a, aa |
? | 0次或1次 | a? → "", a |
{n} | 恰好n次 | a{2} → aa |
{n,} | 至少n次 | a{2,} → aa, aaa |
{n,m} | n到m次 | a{2,3} → aa, aaa |
2.4 边界匹配
边界符 | 说明 | 示例 |
---|---|---|
^ | 匹配字符串开始 | ^abc → abc开头 |
$ | 匹配字符串结束 | abc$ → abc结尾 |
\b | 匹配单词边界 | \bfoo\b → 匹配"foo"单词 |
\B | 匹配非单词边界 | \Bfoo\B → 匹配"xfooy"中的foo |
2.5 分组与或操作
分组 ()
将多个元素组合为一个整体:
(abc)+ # 匹配abc, abcabc等
或操作 |
匹配左边或右边的表达式:
a|b # 匹配a或b
(ab)|(cd) # 匹配ab或cd
3. 正则表达式进阶技巧
3.1 贪婪与非贪婪匹配
-
贪婪匹配(默认):尽可能匹配更长的字符串
-
非贪婪匹配 :在量词后加
?
,尽可能匹配更短的字符串贪婪匹配
re.findall("a.*b", "axbxb") # 匹配['axbxb']
非贪婪匹配
re.findall("a.*?b", "axbxb") # 匹配['axb', 'xb']
3.2 后向引用
使用\数字
引用前面的分组:
# 匹配重复单词
re.findall(r"\b(\w+)\b\s+\1\b", "hello hello world") # 匹配['hello']
3.3 零宽断言
断言 | 说明 | 示例 |
---|---|---|
(?=exp) | 匹配后面是exp的位置 | \d+(?=元) → 匹配"100元"中的100 |
(?!exp) | 匹配后面不是exp的位置 | \d+(?!元) → 匹配"100刀"中的100 |
(?<=exp) | 匹配前面是exp的位置 | (?<=)\\d+ → 匹配"100"中的100 |
(?<!exp) | 匹配前面不是exp的位置 | (?<!$)\d+ → 匹配"¥100"中的100 |
4. Python re模块详解
4.1 常用方法
re.match()
从字符串开头匹配,返回匹配对象或None:
result = re.match(r"\d+", "123abc")
if result:
print(result.group()) # 输出: 123
re.search()
扫描整个字符串,返回第一个匹配对象:
result = re.search(r"\d+", "abc123def456")
print(result.group()) # 输出: 123
re.findall()
返回所有匹配的字符串列表:
re.findall(r"\d+", "123abc456def") # 输出: ['123', '456']
re.finditer()
返回所有匹配的迭代器(适合大文本):
for match in re.finditer(r"\d+", "123abc456def"):
print(match.group())
# 输出:
# 123
# 456
re.sub()
替换匹配的字符串:
re.sub(r"\d+", "NUM", "123abc456") # 输出: 'NUMabcNUM'
re.split()
按匹配模式分割字符串:
re.split(r"\d+", "abc123def456ghi") # 输出: ['abc', 'def', 'ghi']
4.2 匹配对象方法
匹配成功后返回的匹配对象常用方法:
方法 | 说明 | 示例 |
---|---|---|
group() | 返回匹配的字符串 | match.group() → "123" |
start() | 返回匹配的开始位置 | match.start() → 0 |
end() | 返回匹配的结束位置 | match.end() → 3 |
span() | 返回(start, end)元组 | match.span() → (0, 3) |
groups() | 返回所有分组的元组 | match.groups() → ('123',) |
4.3 编译正则表达式
对于重复使用的正则表达式,可以先编译提高效率:
pattern = re.compile(r"\d+")
pattern.findall("123abc456") # 输出: ['123', '456']
5. 常用正则表达式示例
5.1 数字相关
# 整数
^[+-]?\d+$
# 正整数
^[1-9]\d*$
# 负整数
^-[1-9]\d*$
# 非负整数
^\d+$
# 浮点数
^[+-]?\d+\.\d+$
# 保留两位小数
^\d+(\.\d{2})?$
5.2 字符串相关
# 中文字符
^[\u4e00-\u9fa5]+$
# 英文数字
^[A-Za-z0-9]+$
# 用户名(字母开头,5-16字符)
^[a-zA-Z][a-zA-Z0-9_]{4,15}$
# 强密码(至少8位,含大小写字母和数字)
^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}$
5.3 常用信息验证
# 邮箱
^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$
# 手机号(中国)
^(13[0-9]|14[5-9]|15[0-3,5-9]|16[2,5-7]|17[0-8]|18[0-9]|19[0-3,5-9])\d{8}$
# 身份证号(15或18位)
^(\d{15}$|^\d{18}$|^\d{17}(\d|X|x))$
# IPv4地址
^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$
# URL
^https?://([\w-]+\.)+[\w-]+(/[\w-./?%&=]*)?$
6. 实际应用案例
6.1 提取网页中的所有链接
import re
import requests
html = requests.get("https://example.com").text
links = re.findall(r'href=["\'](https?://.*?)["\']', html)
print(links)
6.2 验证用户输入
def validate_email(email):
pattern = r'^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$'
return re.match(pattern, email) is not None
print(validate_email("test@example.com")) # True
print(validate_email("invalid.email")) # False
6.3 日志分析
log = """
2023-01-01 12:00:00 [INFO] User login
2023-01-01 12:05:23 [ERROR] Connection timeout
2023-01-01 12:10:45 [WARNING] Disk space low
"""
# 提取所有ERROR级别的日志
errors = re.findall(r'\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} \[ERROR\] .*', log)
print(errors)
6.4 数据清洗
text = "价格:¥100.50 特价:$99.99 优惠:50%"
cleaned = re.sub(r'[^\d.]', '', text) # 只保留数字和小数点
print(cleaned) # 输出: 100.5099.9950
7. 性能优化与最佳实践
7.1 编译正则表达式
对于频繁使用的正则表达式,预编译可提高性能:
pattern = re.compile(r'\d+') # 编译一次
for text in large_text_collection:
pattern.findall(text) # 多次使用
7.2 避免过度使用正则
简单字符串操作能解决的,不要用正则:
# 不好
re.sub(r'\.$', '', text)
# 更好
text.rstrip('.')
7.3 使用非贪婪模式减少回溯
# 贪婪模式(可能性能差)
re.match(r'<.*>', '<tag>content</tag>')
# 非贪婪模式(性能更好)
re.match(r'<.*?>', '<tag>content</tag>')
7.4 合理使用分组
只对需要的内容使用分组,避免不必要的捕获:
# 不好(使用捕获分组)
re.findall(r'(http://\S+)', text)
# 更好(使用非捕获分组)
re.findall(r'(?:http://)\S+', text)
8. 常见问题与解决方案
Q1: 正则表达式为什么匹配不到?
可能原因:
-
忘记转义特殊字符(如
.
、*
等) -
大小写不匹配(可添加
re.IGNORECASE
标志) -
多行模式下
^
和$
的行为不同(re.MULTILINE
)
Q2: 如何匹配多行文本?
使用re.DOTALL
标志使.
匹配换行符:
re.findall(r'start.*end', text, re.DOTALL)
Q3: 正则表达式性能很差怎么办?
-
避免嵌套量词(如
(a+)+
) -
使用更具体的字符类(如
\d
代替.
) -
考虑使用非贪婪模式
-
预编译正则表达式
Q4: 如何调试复杂的正则表达式?
-
使用在线工具(如regex101.com)
-
分步测试各个部分
-
添加注释(使用
re.VERBOSE
标志)
9. 总结
正则表达式是文本处理的强大工具,掌握它可以:
-
高效验证数据格式
-
快速提取所需信息
-
灵活替换文本内容
-
简化复杂的字符串操作
关键点回顾:
-
元字符和量词是基础
-
分组和或操作实现复杂匹配
-
贪婪与非贪婪模式影响匹配结果
-
Python的re模块提供丰富方法
-
常用正则表达式可以复用
-
性能优化对大规模文本很重要
建议多练习实际案例,逐步掌握正则表达式的强大功能。遇到复杂问题时,可以拆分为多个简单正则表达式逐步解决。