Python3正则表达式
什么是正则表达式?
想象一下,你是一位文本世界的探险家,需要在茫茫字符海洋中寻找特定的"宝藏"(模式)。正则表达式就是你的"寻宝地图"!
正则表达式(Regular Expression,简称regex)是一种强大的字符串搜索、匹配和操作的模式语言。它就像一个超级搜索工具,能帮你:
- 🔍 查找特定模式的文本
- ✂️ 替换文本中的特定内容
- ✅ 验证文本是否符合特定格式(如邮箱、电话号码)
- 🪓 分割文本为多个部分
在Python中使用正则表达式
Python通过re
模块提供正则表达式支持:
import re
一、正则表达式基础语法:你的魔法咒语
基本匹配符
符号 | 作用 | 就像在说… |
---|---|---|
. |
匹配任意单个字符(除了换行符) | “我不挑食,给我任何一个字符就行!” |
^ |
匹配字符串开头 | “我只关心句子的开头!” |
$ |
匹配字符串结尾 | “我只看结局!” |
\ |
转义特殊字符 | “别用魔法攻击我,这只是个普通字符!” |
字符类:性格各异的字符们
符号 | 作用 | 就像在说… |
---|---|---|
[abc] |
匹配方括号内的任意一个字符 | “给我一个a或b或c就满足了” |
[^abc] |
匹配除了方括号内的任意字符 | “除了abc,我都喜欢!” |
[a-z] |
匹配a到z范围内的任意小写字母 | “给我一个小写字母就行” |
[A-Z] |
匹配A到Z范围内的任意大写字母 | “我只收大写字母!” |
[0-9] |
匹配任意数字 | “数字就行,不挑” |
预定义字符类:常见角色的快捷方式
符号 | 作用 | 就像在说… |
---|---|---|
\d |
匹配任意数字,等同于[0-9] |
“来个数字吧!” |
\D |
匹配任意非数字,等同于[^0-9] |
“数字以外的都行!” |
\w |
匹配字母、数字或下划线,等同于[a-zA-Z0-9_] |
“给我一个’单词’字符!” |
\W |
匹配非字母、数字、下划线的字符 | “不要’单词’字符!” |
\s |
匹配任意空白字符(空格、制表符、换行符等) | “我需要一点空间!” |
\S |
匹配任意非空白字符 | “不要空白!” |
重复限定符:贪婪的收集者
符号 | 作用 | 就像在说… |
---|---|---|
* |
匹配前面的模式零次或多次 | “有多少要多少,没有也行!” |
+ |
匹配前面的模式一次或多次 | “至少给我一个,多了也行!” |
? |
匹配前面的模式零次或一次 | “有一个我就满足了,没有也无所谓” |
{n} |
精确匹配前面的模式n次 | “我要正好n个,不多不少!” |
{n,} |
匹配前面的模式至少n次 | “给我至少n个!” |
{n,m} |
匹配前面的模式n到m次 | “给我n到m个之间,我很好说话的” |
贪婪vs非贪婪:胃口大小问题
默认情况下,重复限定符是贪婪的,会尽可能多地匹配:
# 贪婪模式:尽可能多地匹配
re.search(r'a.*b', 'aabab').group() # 'aabab'
加上?
后,变成非贪婪模式,会尽可能少地匹配:
# 非贪婪模式:尽可能少地匹配
re.search(r'a.*?b', 'aabab').group() # 'aab'
分组与捕获:给模式戴上名牌
符号 | 作用 | 就像在说… |
---|---|---|
(...) |
分组并捕获匹配的内容 | “这几个字符是一伙的,我要记住他们!” |
(?:...) |
分组但不捕获匹配的内容 | “这几个字符是一伙的,但不用记住他们” |
(?P<name>...) |
命名捕获组 | “这几个字符是一伙的,叫他们’name’” |
二、Python中的re模块:施展魔法的工具箱
主要函数
函数 | 作用 | 记忆小窍门 |
---|---|---|
re.search(pattern, string) |
在字符串中搜索第一个匹配项 | “找一找就好” |
re.match(pattern, string) |
从字符串开头匹配模式 | “必须从头开始找” |
re.findall(pattern, string) |
返回所有匹配项的列表 | “全都要找出来!” |
re.finditer(pattern, string) |
返回所有匹配项的迭代器 | “一个一个慢慢找” |
re.sub(pattern, repl, string) |
替换所有匹配项 | “找到就换掉” |
re.split(pattern, string) |
按匹配项分割字符串 | “看到就切一刀” |
re.compile(pattern) |
编译正则表达式模式 | “先准备好魔法卷轴” |
常用标志
标志 | 作用 | 记忆小窍门 |
---|---|---|
re.IGNORECASE 或 re.I |
忽略大小写 | “A和a都一样” |
re.MULTILINE 或 re.M |
多行模式,使^ 和$ 匹配每行的开始和结束 |
“每行都很重要” |
re.DOTALL 或 re.S |
使. 也能匹配换行符 |
“点点无所不包” |
三、实际例子:魔法入门
1. 验证邮箱地址
import re
def is_valid_email(email):
pattern = r'^[\w\.-]+@[\w\.-]+\.\w+$'
return bool(re.match(pattern, email))
# 测试一下
print(is_valid_email('user@example.com')) # True
print(is_valid_email('invalid-email')) # False
2. 提取文本中的日期
import re
text = "今天是2023-05-15,明天是2023-05-16,后天是2023/05/17"
# 找出所有日期
dates = re.findall(r'\d{4}[-/]\d{2}[-/]\d{2}', text)
print(dates) # ['2023-05-15', '2023-05-16', '2023/05/17']
3. 提取并处理HTML标签
import re
html = "<div>Hello <b>Python</b> and <i>Regex</i></div>"
# 找出所有标签
tags = re.findall(r'<[^>]+>', html)
print(tags) # ['<div>', '<b>', '</b>', '<i>', '</i>', '</div>']
# 去除所有标签
clean_text = re.sub(r'<[^>]+>', '', html)
print(clean_text) # 'Hello Python and Regex'
4. 分割CSV,但忽略引号内的逗号
# 这个例子展示了复杂模式的威力
import re
csv_line = 'John,"Doe,Jr",New York,USA'
# 错误的简单分割
print(csv_line.split(',')) # ['John', '"Doe', 'Jr"', 'New York', 'USA']
# 使用正则表达式正确分割
pattern = r',(?=(?:[^"]*"[^"]*")*[^"]*$)'
print(re.split(pattern, csv_line)) # ['John', '"Doe,Jr"', 'New York', 'USA']
四、正则表达式调试技巧:魔法训练
- 小步前进:先测试简单的模式,然后逐渐添加复杂度
- 在线工具:使用regex101.com等在线工具进行可视化测试
- 分组使用:使用分组来隔离和测试正则表达式的各个部分
- 命名捕获组:给重要的捕获组命名,增强可读性
五、正则表达式性能注意事项:魔力消耗控制
- 避免过度回溯:复杂的嵌套重复限定符可能导致灾难性回溯
- 预编译模式:频繁使用的模式应该预编译
phone_pattern = re.compile(r'\d{3}-\d{3}-\d{4}') # 重复使用phone_pattern.search()而不是re.search()
- 非捕获组:当不需要捕获结果时,使用非捕获组
(?:...)
提高性能 - 适当使用原子组:减少回溯的可能性
总结:正则表达式修炼指南
- 正则表达式是强大的文本处理工具,但需要时间掌握
- 从简单模式开始,逐步构建复杂模式
- 多练习,多实验,才能掌握这门"魔法"
- 记住:有时候,简单的字符串方法可能更适合简单的任务
“一开始,所有的正则表达式都像天书;熟练后,它们变成了你的得力助手。” – 正则表达式大师的传说
练习题:小试身手
- 编写一个正则表达式来验证中国手机号码(11位数字,以1开头)
- 从文本中提取所有的URL链接
- 验证一个字符串是否为有效的IPv4地址
- 从文本中提取所有的"#标签"(Twitter风格)
1.验证中国手机号码
中国手机号码规则:11位数字,以1开头。
import re
def validate_chinese_phone(phone_number):
"""
验证中国手机号码是否有效
参数:
phone_number: 要验证的手机号码字符串
返回:
布尔值: 是否为有效的中国手机号码
"""
# 正则表达式解释:
# ^1 - 以1开头
# [0-9]{10} - 后跟10位数字
# $ - 结束匹配
pattern = r'^1[0-9]{10}$'
return bool(re.match(pattern, phone_number))
# 测试示例
test_phones = [
"13812345678", # 有效
"19912345678", # 有效
"12345678901", # 有效 (只要求以1开头的11位数字)
"138123456789", # 无效 (12位)
"2381234567", # 无效 (10位且不以1开头)
"138abcd1234", # 无效 (包含非数字字符)
"01381234567", # 无效 (12位且不以1开头)
"138-1234-567" # 无效 (包含连字符)
]
for phone in test_phones:
result = "有效" if validate_chinese_phone(phone) else "无效"
print(f"手机号 {phone} 是{result}的中国手机号")
2. 从文本中提取所有URL链接
提取各种格式的URL链接,包括http、https、ftp等协议。
import re
def extract_urls(text):
"""
从文本中提取所有URL链接
参数:
text: 要搜索的文本
返回:
列表: 包含所有找到的URL
"""
# 正则表达式解释:
# (https?|ftp):// - 匹配协议(http://, https://, ftp://)
# [-a-zA-Z0-9@:%._\+~#=]{1,256} - 匹配域名和路径中的有效字符
# \.[a-zA-Z0-9()]{1,6} - 匹配顶级域名(.com, .org等)
# \b - 单词边界
# ([-a-zA-Z0-9()@:%_\+.~#?&//=]*)- 匹配URL中的参数和路径
pattern = r'(https?|ftp)://[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)'
# 简化版本,也可以使用这个更简单但覆盖面广的模式
# pattern = r'https?://[^\s]+'
return re.findall(pattern, text)
# 测试示例
test_text = """
这是一个包含多个URL的文本示例:
访问 https://www.example.com 获取更多信息。
下载链接: http://files.example.org/document.pdf
FTP服务器: ftp://ftp.example.net/downloads/
无效链接: www.not-extracted.com (因为没有协议前缀)
嵌入在文本中的链接https://api.example.com/v1/data?id=123&format=json也会被提取。
"""
urls = extract_urls(test_text)
print("提取的URL:")
for i, url in enumerate(urls, 1):
print(f"{i}. {url[0]}{url[1]}") # 合并捕获组
3. 验证IPv4地址
验证一个字符串是否为有效的IPv4地址(四个0-255之间的数字,用点分隔)。
import re
def validate_ipv4(ip_address):
"""
验证一个字符串是否为有效的IPv4地址
参数:
ip_address: 要验证的IP地址字符串
返回:
布尔值: 是否为有效的IPv4地址
"""
# 正则表达式解释:
# ^ - 开始匹配
# (25[0-5]|2[0-4][0-9]| - 匹配250-255或200-249
# [01]?[0-9][0-9]?| - 匹配0-199
# [0-9]) - 匹配单个数字0-9
# \. - 匹配点号(.)
# 重复上述模式三次,最后一次不带点号
# $ - 结束匹配
pattern = r'^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$'
return bool(re.match(pattern, ip_address))
# 测试示例
test_ips = [
"192.168.1.1", # 有效
"10.0.0.1", # 有效
"172.16.254.1", # 有效
"255.255.255.255",# 有效
"0.0.0.0", # 有效
"256.0.0.1", # 无效 (256超出范围)
"192.168.1", # 无效 (只有3段)
"192.168.1.1.1", # 无效 (有5段)
"192.168.1.a", # 无效 (包含非数字字符)
"192.168.01.1" # 有效 (前导零被允许)
]
for ip in test_ips:
result = "有效" if validate_ipv4(ip) else "无效"
print(f"IP地址 {ip} 是{result}的IPv4地址")
4. 提取Twitter风格的标签
从文本中提取所有的"#标签"(Twitter风格)。
import re
def extract_hashtags(text):
"""
从文本中提取所有的Twitter风格标签(#标签)
参数:
text: 要搜索的文本
返回:
列表: 包含所有找到的标签(不含#符号)
"""
# 正则表达式解释:
# # - 匹配#符号
# ([a-zA-Z0-9_\u4e00-\u9fa5]+) - 匹配标签内容:
# [a-zA-Z0-9_] - 英文字母、数字和下划线
# \u4e00-\u9fa5 - 中文字符范围
pattern = r'#([a-zA-Z0-9_\u4e00-\u9fa5]+)'
return re.findall(pattern, text)
# 测试示例
test_text = """
今天的天气真不错 #天气 #晴天
我正在学习Python #Python #编程 #学习
这是一个#复合标签 和一个 #带有标点符号的标签!
#中英文混合tag #123数字 #with_underscore
"""
hashtags = extract_hashtags(test_text)
print("提取的标签:")
for i, tag in enumerate(hashtags, 1):
print(f"{i}. {tag}")
综合示例应用
下面是一个综合应用,将上面的所有函数整合到一个文本分析工具中:
import re
class TextAnalyzer:
"""文本分析工具,提供多种正则表达式功能"""
@staticmethod
def validate_chinese_phone(phone_number):
pattern = r'^1[0-9]{10}$'
return bool(re.match(pattern, phone_number))
@staticmethod
def extract_urls(text):
pattern = r'(https?|ftp)://[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)'
matches = re.findall(pattern, text)
return [f"{protocol}{path}" for protocol, path in matches]
@staticmethod
def validate_ipv4(ip_address):
pattern = r'^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$'
return bool(re.match(pattern, ip_address))
@staticmethod
def extract_hashtags(text):
pattern = r'#([a-zA-Z0-9_\u4e00-\u9fa5]+)'
return re.findall(pattern, text)
@staticmethod
def analyze_text(text):
"""分析文本,提取所有可能的信息"""
result = {
"phones": [],
"urls": [],
"ips": [],
"hashtags": []
}
# 提取手机号
phone_pattern = r'1[0-9]{10}'
phones = re.findall(phone_pattern, text)
result["phones"] = [p for p in phones if TextAnalyzer.validate_chinese_phone(p)]
# 提取URL
result["urls"] = TextAnalyzer.extract_urls(text)
# 提取IP地址
ip_pattern = r'\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b'
potential_ips = re.findall(ip_pattern, text)
result["ips"] = [ip for ip in potential_ips if TextAnalyzer.validate_ipv4(ip)]
# 提取标签
result["hashtags"] = TextAnalyzer.extract_hashtags(text)
return result
# 测试综合分析
sample_text = """
联系我: 13812345678 或 19987654321
网站: https://www.example.com/path?query=123
服务器IP: 192.168.1.1 和 8.8.8.8 (但 999.999.999.999 不是有效IP)
话题: #Python #正则表达式 #数据分析
"""
analyzer = TextAnalyzer()
analysis_result = analyzer.analyze_text(sample_text)
print("文本分析结果:")
print(f"手机号: {analysis_result['phones']}")
print(f"URL链接: {analysis_result['urls']}")
print(f"IP地址: {analysis_result['ips']}")
print(f"话题标签: {analysis_result['hashtags']}")
这些正则表达式示例展示了如何使用Python处理各种文本模式匹配任务。您可以根据具体需求进一步调整这些模式,使它们更加精确或更适合您的用例。
记住:正则表达式就像厨艺,多练习才能精通。每解决一个文本处理问题,你就离正则表达式大师更近一步!