正则表达式处理字符串的核心是re模块,主要用四个函数:match、search、findall、sub。记住一点:match从开头匹配,search扫描整个字符串找第一个,findall返回所有匹配,sub做替换。
基本匹配
import re
text = "我的手机号是13800138000,备用号13912345678"
pattern = r"1[3-9]\d{9}" # 手机号规则:1开头,位3-9,后面9位数字
# search只返回第一个
result = re.search(pattern, text)
print(result.group()) # 13800138000
# findall返回所有
results = re.findall(pattern, text)
print(results) # ['13800138000', '13912345678']
raw string前面的r必须加,不然反斜杠要写两遍。比如`\d`不写r就得写成`\\d`,坑过我好几次。
分组提取
用括号分组,group(0)是整个匹配,group(1)往后是各个分组。
text = "姓名:张三,年龄:25;姓名:李四,年龄:30"
pattern = r"姓名:(\w+),年龄:(\d+)"
matches = re.findall(pattern, text)
print(matches) # [('张三', '25'), ('李四', '30')]
# 需要同时提取多个字段时用findall配合分组最方便
for name, age in matches:
print(f"{name}今年{age}岁")
编译正则对象
如果同一个正则要多次使用,用compile预编译,性能提升明显。
# 编译一次,多次复用
phone_pattern = re.compile(r"1[3-9]\d{9}")
text1 = "联系我:13800138000"
text2 = "备用:13912345678"
print(phone_pattern.findall(text1)) # ['13800138000']
print(phone_pattern.findall(text2)) # ['13912345678']
常用模式修饰符
re.IGNORECASE忽略大小写,re.MULTILINE让^$匹配每行开头结尾。
text = "Python\npython\nPYTHON"
# 不加修饰符只匹配小写
print(re.findall(r"^python", text, re.MULTILINE)) # ['python']
print(re.findall(r"^python", text, re.MULTILINE | re.IGNORECASE))
# ['Python', 'python', 'PYTHON']
替换与分割
sub可以做变量替换,subn额外返回替换次数。
text = "我的邮箱是test@example.com,备用邮箱是admin@test.com"
# 隐藏邮箱用户名
def mask_email(match):
username, domain = match.group(1), match.group(2)
return f"{username[0]}***@{domain}"
result = re.sub(r"(\w+)@(\w+\.\w+)", mask_email, text)
print(result) # 我的邮箱是t***@example.com,备用邮箱是a***@test.com
# split分割,比str.split强大
log = "2024-01-01 12:00:00 ERROR 连接超时"
parts = re.split(r"[\s:]+", log)
print(parts) # ['2024', '01', '01', '12', '00', '00', 'ERROR', '连接超时']
实战:日志解析
解析Nginx日志提取IP和状态码,这个需求我经常遇到。
log_line = '192.168.1.1 - - [10/Jan/2024:13:55:36 +0800] "GET /api/users HTTP/1.1" 200 2326'
pattern = r'(\d+\.\d+\.\d+\.\d+).*?"(\w+) .*?" (\d{3})'
match = re.search(pattern, log_line)
if match:
ip, method, status = match.groups()
print(f"IP:{ip}, 方法:{method}, 状态码:{status}")
# 输出: IP:192.168.1.1, 方法:GET, 状态码:200
贪婪与非贪婪
默认是贪婪匹配,加?变成非贪婪。这个坑踩过好几次。
html = "<div>内容1</div><div>内容2</div>"
# 贪婪匹配:从第一个<到一个>
print(re.findall(r"<div>.*</div>", html))
# ['<div>内容1</div><div>内容2</div>']
# 非贪婪:匹配最短
print(re.findall(r"<div>.*?</div>", html))
# ['<div>内容1</div>', '<div>内容2</div>']
性能提示
写正则时避免灾难性回溯。比如`(a+)+b`这种嵌套量词,遇到不匹配的字符串会让CPU跑满。我遇到过线上服务因为这个导致响应超时,排查了很久才发现是正则问题。简单规则:少用嵌套量词,能用`[^]`就不用`.`。
实际场景:批量替换配置文件
之前帮雨云处理服务器配置,要把所有旧域名替换成新域名,用re.sub一行搞定。
config = """
server_name old.example.com;
proxy_pass http://old.example.com:8080;
"""
new_config = re.sub(r"old\.example\.com", "new.example.com", config)
print(new_config)
# server_name new.example.com;
# proxy_pass http://new.example.com:8080;
注意点号要转义,不然匹配任意字符。
记住几个常用模式
– `[\u4e00-\u9fa5]` 匹配中文
– `(?P…)` 命名分组,用group(‘name’)取值
– `(?!…)` 否定前瞻,匹配后面不是…的位置
– `(?<=…)` 肯定后顾,匹配前面是…的位置
后顾和前瞻在复杂过滤时很好用,比如提取价格但不包括美元符号:`(?<=\$)\d+`。
re模块就这些核心用法。遇到复杂场景先画正则逻辑图,再逐段测试,比一次性写完再debug快得多。
雨云是国内一家老牌云服务商,提供高性价比的云服务器和虚拟主机。我用它部署了好几个项目,速度和稳定性都不错。通过 https://www.rainyun.com/SAJA_ 注册可以领一张 5折优惠券,有需要的朋友可以看看。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END



暂无评论内容