正则表达式(Regular Expression,简称regex)是文本处理的利器,在Python中通过内置的re模块实现。本文从基础语法到高级技巧,结合实战案例,帮助开发者快速掌握正则表达式的核心用法,并避开常见陷阱。
一、基础语法与元字符
正则表达式由普通字符和元字符组成。常用元字符包括:.匹配除换行符外的任意字符;^匹配字符串开头;$匹配字符串结尾;*匹配前一个字符0次或多次;+匹配1次或多次;?匹配0次或1次;{n}精确匹配n次;{n,m}匹配n到m次;[abc]字符集合,匹配其中任意字符;[^abc]排除集合中的字符;|表示或;()用于分组;\转义符。
预定义字符集简化了常见匹配:\d匹配数字,等价于[0-9];\D匹配非数字;\w匹配字母、数字、下划线(即单词字符);\W匹配非单词字符;\s匹配空白字符(空格、制表符、换行等);\S匹配非空白字符;\b匹配单词边界。
二、贪婪与非贪婪
默认情况下,量词采用贪婪模式,尽可能多地匹配字符。例如正则<.*>匹配字符串'<div>hello</div>'会得到整个字符串。在量词后加?即变成非贪婪模式,如<.*?>会匹配到'<div>'。
三、re模块常用函数
1. re.match():从头开始匹配,如果开头不匹配则返回None。- import re
- result = re.match(r'\d+', '123abc456')
- print(result.group()) # 输出: 123
- result = re.match(r'\d+', 'abc123')
- print(result) # 输出: None
复制代码
2. re.search():在整个字符串中搜索第一个匹配项。- result = re.search(r'\d+', 'abc123def456')
- print(result.group()) # 输出: 123
复制代码
3. re.findall():返回所有不重叠匹配项的列表,最常用。- text = '我有3个苹果,5个香蕉,8个橙子'
- numbers = re.findall(r'\d+', text)
- print(numbers) # 输出: ['3', '5', '8']
复制代码
4. re.finditer():返回迭代器,每个元素是Match对象,适合处理大量匹配结果,节省内存。- text = '价格: 100元, 200元, 300元'
- for match in re.finditer(r'\d+', text):
- print(f'找到数字: {match.group()},位置: {match.span()}')
复制代码
5. re.sub():替换匹配的文本。- result = re.sub(r'\d+', '#', '房间123,楼层4')
- print(result) # 输出: 房间#,楼层#
- # 移除所有空白字符
- cleaned = re.sub(r'\s+', '', 'abc 12\ de 23 \n f45 6')
- print(cleaned) # 输出: abc12de23f456
复制代码
6. re.split():按模式分割字符串。- text = 'abc123def456ghi'
- parts = re.split(r'\d+', text)
- print(parts) # 输出: ['abc', 'def', 'ghi']
- # 多种分隔符
- text2 = 'apple, banana; orange|grape'
- result = re.split(r'[,;|]', text2)
- print(result) # 输出: ['apple', ' banana', ' orange', 'grape']
复制代码
7. re.compile():编译正则表达式为Pattern对象,提高重复使用的效率。- pattern = re.compile(r'\d+')
- print(pattern.findall('我有10个苹果')) # ['10']
- print(pattern.search('他有20个橙子').group()) # 20
复制代码
四、分组与捕获
1. 基础分组:用圆括号()提取关键内容,通过.group(1)、.group(2)获取分组。- text = '我的电话是010-12345678'
- pattern = r'(\d{3})-(\d{8})'
- match = re.search(pattern, text)
- if match:
- print(f'区号: {match.group(1)}') # 010
- print(f'号码: {match.group(2)}') # 12345678
复制代码
2. 非捕获分组(?:...):用于应用量词但不捕获内容,不占用分组编号。- pattern = r'(?:https?://)?(\w+\.\w+)'
- text = '访问 https://example.com 和 http://python.org'
- matches = re.findall(pattern, text)
- print(matches) # ['example.com', 'python.org']
复制代码
3. 命名分组(?P<name>...):提高代码可读性,用.group('name')访问。- pattern = r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})'
- match = re.search(pattern, '今天是2025-03-15')
- print(match.group('year')) # 2025
- print(match.groupdict()) # {'year': '2025', 'month': '03', 'day': '15'}
复制代码
4. 反向引用:在正则中引用前面捕获的内容,用\1、\2等。- pattern = r'\b(\w+)\s+\1\b'
- print(re.search(pattern, 'hello hello')) # 成功匹配
- print(re.search(pattern, 'hello world')) # None
复制代码
五、零宽断言
零宽断言不消耗字符,只判断位置。四种断言类型:
- 正向先行断言(?=...):匹配后面是指定内容的位置。
- 负向先行断言(?!...):匹配后面不是指定内容的位置。
- 正向后行断言(?<=...):匹配前面是指定内容的位置。
- 负向后行断言(?<!...):匹配前面不是指定内容的位置。
实战示例:- # 提取美元符号后面的数字
- import re
- text = '售价¥299,促销价$199'
- pattern = r'(?<=\$)\d+'
- print(re.findall(pattern, text)) # ['199']
- # 排除jpg/png文件
- files = 'image.jpg backup.zip config.yaml'
- pattern = r'\b\w+\.(?!jpg|png)\w{3}\b'
- print(re.findall(pattern, files)) # ['backup.zip', 'config.yaml']
- # 提取中括号内的内容
- log = 'ERROR [2026-01-01] 系统崩溃'
- pattern = r'(?<=\[).+?(?=\])'
- print(re.search(pattern, log).group()) # 2026-01-01
复制代码
六、性能优化建议
1. 重复使用的正则用re.compile()预编译。
2. 避免灾难性回溯:不要使用(a+)+这类嵌套量词。
3. 用字符类替代点号:[^"]*比.*?更高效。
4. 使用锚点^和$快速定位。
5. 先做简单过滤(如in操作符),再用正则精确匹配。
七、实战案例
1. 提取中文字符:使用Unicode范围\u4e00-\u9fff。- chinese = re.findall(r'[\u4e00-\u9fff]+', 'Python正则表达式入门教程123')
- print(chinese) # ['正则表达式入门教程']
复制代码
2. 验证手机号码:11位,以1开头,第二位3-9。- def is_valid_phone(phone):
- pattern = r'^1[3-9]\d{9}$'
- return bool(re.match(pattern, phone))
- print(is_valid_phone('13812345678')) # True
- print(is_valid_phone('12345678901')) # False
复制代码
3. 提取邮箱地址。- text = '请联系我: test.user@example.com 或 admin@python.org'
- pattern = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'
- print(re.findall(pattern, text)) # ['test.user@example.com', 'admin@python.org']
复制代码
4. 去除HTML标签:用re.sub替换掉<[^>]+>。- html = '<div class="content"><p>欢迎来到<span>Python</span>世界!</p></div>'
- clean_text = re.sub(r'<[^>]+>', '', html)
- print(clean_text) # 欢迎来到Python世界!
复制代码
5. 日志解析实战:提取错误日志的时间和信息。- log = '''
- 2026-04-06 19:23:45 INFO 用户登录成功
- 2026-04-06 19:25:12 ERROR 数据库连接失败
- 2026-04-06 19:30:08 WARN 响应时间过长
- '''
- pattern = r'(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) ERROR (.+)'
- errors = re.findall(pattern, log)
- for time, msg in errors:
- print(f'错误发生时间: {time}, 错误信息: {msg}')
复制代码
八、常见错误避坑
1. 忘记使用原生字符串:在模式字符串前加r,避免转义问题。正确:r'\d+',错误:'\d+'。
2. 混淆match和search:match要求从头开始匹配,search任意位置查找。
3. 分组编号理解错误:分组编号按左括号出现顺序计算,如((A)(B))C中,组1是((A)(B)),组2是(A),组3是(B)。
4. 正则过于复杂:若模式极其复杂(如完整URL校验),应使用专门库,避免单个正则难以维护。
掌握正则表达式能大幅提升文本处理效率。建议从简单模式开始,逐步深入,并在实际项目中多练习。 |