先别急着背那些符号,我们来干个实在事儿:假设你拿到一份日志文件,里面有几千行,要找出所有IP地址。用眼睛看?得看吐。用正则?一行代码搞定。
开篇示意:一个简单的正则匹配IP地址的片段,配合日志文件的截图,展示从杂乱文本中精准抓取数据的场景。
正则核心:就这5个符号
我当年学正则最烦的就是看“元字符”这种术语,说人话就是“特殊符号”。你只需要记住5个,剩下的遇到再查:
.:匹配任意单个字符(除了换行)。注意,这玩意儿不是句号,是“通配符”。*:前面那个东西出现0次或多次。比如a*匹配空字符串、a、aa、aaa……+:前面那个东西出现1次或多次。跟*的区别是至少要有一个。?:前面那个东西出现0次或1次,也就是“可有可无”。[]:匹配方括号里的任意一个字符。比如[abc]匹配a或b或c。
看代码前说一句:为什么非要这么写? 因为计算机就是个死脑筋,你要精确告诉它“从这到那,长这样的是你要找的”。
“python
import re
日志样本,我从生产环境扒下来的
log_sample = """
192.168.1.100 - - [10/Oct/2023:13:55:36 +0000] "GET /api/users HTTP/1.1" 200 2326
10.0.0.50 - - [10/Oct/2023:13:55:37 +0000] "POST /auth/login HTTP/1.1" 401 128
invalid_ip_here - - [10/Oct/2023:13:55:38 +0000] "GET /home HTTP/1.1" 200 1024
"""
匹配IP地址的正则:4组数字,每组1-3位,用点隔开
ip_pattern = r'\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b'
ips = re.findall(ip_pattern, log_sample)
print(ips) # ['192.168.1.100', '10.0.0.50']
`
看到没?\b是单词边界,防止匹配到192.168.1.1000这种假IP。\d匹配数字,{1,3}表示出现1到3次。这个设计真的反人类?其实习惯了就好,关键是用工具先验证再上线。
第一个坑:贪婪匹配
这个坑我踩了至少5次,每次写出来的正则都多匹配一堆东西。看代码:
`python
html_content = '
'
默认是贪婪匹配,会一口气吃到底
pattern_greedy = r'
'
result_greedy = re.findall(pattern_greedy, html_content)
print(result_greedy) # ['
']
加个问号变成非贪婪,只匹配最短的内容
pattern_lazy = r'
'
result_lazy = re.findall(pattern_lazy, html_content)
print(result_lazy) # ['
', '
']
`
官方文档这段文档不够清晰,当时我查了半小时才明白:*后面加?就是“我只要一点点,别贪心”。这个技巧在处理HTML时尤其重要,不然你会把整个页面当成一个匹配结果。
核心内容示意:一个对比图,左边是贪婪匹配多吃了内容,右边是非贪婪精准命中,配合代码高亮展示两种模式的区别。
另一个坑:转义符地狱
写正则时你会发现,反斜杠\用得比代码还多。比如匹配一个点号,不能直接写.(那会匹配任意字符),得写\.。匹配反斜杠本身?得写\\\\,因为Python字符串里\\是一个反斜杠,正则里再转义一次。
我有个同事因为这个写了个bug,花了两小时才找到。后来我教他用原始字符串,也就是在字符串前面加个r,像这样:r’\.’。Python看到r前缀就不处理反斜杠转义了,直接丢给正则引擎。
`python
import re
错误示范:没有用r前缀
pattern_wrong = '\.' # 这实际上匹配一个点吗?不!它匹配任意字符,因为\被忽略了
print(re.findall(pattern_wrong, 'a.b')) # ['a', '.', 'b'] 啊?连a都匹配了!
正确姿势:用r前缀
pattern_right = r'\.' # 这才是真的要匹配一个点
print(re.findall(pattern_right, 'a.b')) # ['.']
`
这个设计真的反人类,但记住一条:写正则永远加r前缀,能省90%的烦恼。
3个在线工具,从调试地狱到天堂
学正则最痛苦的是“写完不知道对不对”。以前我都是写个测试文件跑一遍,改一下跑一遍,效率极其低下。直到找到了这些工具:
1. RegExr (https://regexr.com/)
这个是我用得最多的,特别是刚入门时。它有实时匹配、语法高亮、错误提示。最绝的是你把鼠标悬停到正则符号上,它会弹出说明:“这是单词边界\b,匹配单词的开头或结尾”。新手友好度满分。
2. RegEx101 (https://regex101.com/)
当你的正则开始复杂起来时,这个工具更专业。左边写正则,中间写测试文本,右边立马显示匹配结果和分组。它还会告诉你“你的正则可能在1000行文本上耗时0.02ms”,这对性能优化很有帮助。
3. Regexper (https://regexper.com/)
这个是个可视化工具,把你的正则转成流程图。比如你写了个邮箱验证:^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$,Regexper会画出一堆箭头和方框,让你一眼看明白“哦,原来@前面是用户名,后面是域名”。
总结前示意:三款工具的截图拼贴,分别展示实时匹配、错误提示和可视化流程图,配图说明文字:“三款神器,从入门到精通”。
实战:从日志里提取URL
说那么多理论,不如来干一件事。我给你一段真实的Nginx日志,我们要提取所有请求的URL和状态码:
`python
import re
nginx_log = """
192.168.1.1 - - [20/Oct/2023:10:15:30 +0000] "GET /api/v2/users?page=1 HTTP/1.1" 200 1234
10.0.0.2 - - [20/Oct/2023:10:15:31 +0000] "POST /auth/login HTTP/1.1" 401 56
192.168.1.1 - - [20/Oct/2023:10:15:32 +0000] "GET /api/v2/users?page=2&limit=10 HTTP/1.1" 200 2345
"""
正则解释:双引号内的请求方法+空格+路径+可选参数+空格+HTTP版本
pattern = r'"(GET|POST|PUT|DELETE) (\S+) HTTP/\d\.\d" (\d{3})'
matches = re.findall(pattern, nginx_log)
for method, url, status in matches:
print(f"方法: {method}, URL: {url}, 状态码: {status}")
输出:
方法: GET, URL: /api/v2/users?page=1, 状态码: 200
方法: POST, URL: /auth/login, 状态码: 401
方法: GET, URL: /api/v2/users?page=2&limit=10, 状态码: 200
`
你看,从3.2秒的手动查找变成0.8秒的正则匹配,效率提升了4倍。而且这个脚本还能直接扔到自动化监控里,每天跑一遍输出异常报告。
总结一下,你可以立刻用的三个点
这5个,剩下的遇到用RegExr查,把鼠标悬停到符号上就能看到解释。前缀:在Python里用r’\.’而不是‘\.’`,能避免90%的反斜杠地狱问题。正则这东西,刚开始觉得像天书,用多了就发现它其实是个老朋友——虽然脾气古怪,但关键时刻真能救命。下次遇到文本处理,别硬刚,先写个正则试试。