正则表达式实战:10 个高频场景 + 调试方法,一次讲透
正则不是背出来的,是练出来、调出来的。
刚学编程时我以为正则是"魔法符号",后来发现它就是一套清晰规则。这篇先给一张速查表,再带你用 10 个真实场景把规则用熟,最后讲怎么调试、怎么避开性能大坑。
一、速查表
元字符
| . | 任意单字符(除换行) |
| \\d \\w \\s | 数字 / 词字符 / 空白 |
| \\D \\W \\S | 对应取反 |
| ^ $ | 行首 / 行尾 |
量词:*(0+)、+(1+)、?(0 或 1)、{n,m}(n 到 m 次)。
分组与断言:( ) 捕获组、(?: ) 非捕获、(?=x) 后面是 x、(?!x) 后面不是 x。
标志位:i 忽略大小写、g 全局、m 多行、s 让 . 匹配换行。
二、10 个高频场景
1. 邮箱格式校验(不要过度精确)
import re
pattern = r"^[\\w.+-]+@[\\w-]+\\.[\\w.-]+$"
print(bool(re.match(pattern, "a.b+c@sub.example.com"))) # True
解释:\\w.+- 覆盖用户名常见字符,@ 后匹配域名和点。别追求 100% 符合 RFC,能挡住明显错误就行。
2. 手机号(含 +86 与分隔符容错)
pattern = r"^[+]?86[-\\s]?1[3-9]\\d{9}$"
print(bool(re.match(pattern, "+86 13812345678"))) # True
[+]? 可选加号,[-\\s]? 容忍空格或横杠,核心 11 位以 1[3-9] 开头。
3. 强密码校验(用 lookahead)
pattern = r"^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[^A-Za-z0-9]).{8,}$"
print(bool(re.match(pattern, "Abcd123!"))) # True
四个 (?=.*x) 正向断言分别要求有小写、大写、数字、符号,末尾 .{8,} 限长度。
4. 提取 URL 的域名与路径参数
pattern = r"https?://([^/]+)(/[^?]*)?(\\?.*)?"
m = re.match(pattern, "https://shop.com/list?page=2")
print(m.group(1), m.group(2)) # shop.com /list
三组捕获分别拿域名、路径、查询串。
5. 中文匹配
# 匹配中文字符范围
pattern = r"[\\u4e00-\\u9fa5]+"
text = "张三 zhangsan 李四"
print(re.findall(pattern, text)) # ['张三', '李四']
# 去除所有中文
print(re.sub(r"[\\u4e00-\\u9fa5]", "", text)) # ' zhangsan '
6. 金额千分位(捕获组 + 反向引用思路)
# 每三位数字前加逗号(从右往左)
text = "1234567"
print(re.sub(r"(?<=\\d)(?=(\\d{3})+(?!\\d))", ",", text)) # 1,234,567
用先行断言找"后面是 3 的倍数个数字且不是数字结尾"的位置插入逗号。
7. HTML 标签匹配与去除
html = "<p>hello <b>world</b></p>"
print(re.sub(r"<[^>]+>", "", html)) # hello world
提醒:这只是应急。完整 HTML 嵌套复杂,正则无法正确处理,正式场景请用解析库(如 Python 的 BeautifulSoup、JS 的 DOMParser)。
8. 日志时间戳批量替换(re.sub 带函数)
import re
log = " [2023-01-01] ok\\n [2024-05-20] fail\\n"
# 把日期统一改成标记
print(re.sub(r"\\[(\\d{4}-\\d{2}-\\d{2})\\]", lambda m: "[OLD]", log))
sub 的第二参数传函数,可对每个匹配做任意转换。
9. 重复词检测
pattern = r"\\b(\\w+)\\s+\\1\\b"
text = "the the cat sat"
print(re.findall(pattern, text)) # ['the']
\\1 反向引用第一个捕获组,配 \\b 词边界避免误判。
10. 敏感词过滤(大小写不敏感 + 词边界)
words = ["苹果", "香蕉"]
text = "我爱吃苹果,也买过苹果派,山东的苹果不错"
# 用前后词边界保证整词匹配,避免子串误伤
pattern = r"(?<!\\w)" + "|".join(words) + r"(?!\\w)"
print(re.sub(pattern, "***", text, flags=re.IGNORECASE))
前后 (?<!\\w)(?!\\w) 保证整词匹配,避免把"苹果派"里的"苹果"单独替换或子串误伤。
三、调试方法
- regex101.com:在线高亮匹配、逐字符解释,我首选。
- grep -E:命令行快速验证grep -E "^[a-z]+@[a-z]+\\." emails.txt
- Python re.DEBUG:打印正则的解析树,看哪里写错:re.compile(r"\\d+", re.DEBUG)
贪婪 vs 非贪婪:.* 贪婪会一直吃到最后,.*? 非贪婪遇到第一个满足条件的就停。替换 HTML 标签一定要用 <.+?> 而非 <.+>。
灾难性回溯:像 (a+)+$ 配长串 aaaa….X 会让引擎指数级爆炸。避免嵌套量词,必要时用原子组或改写表达式。
什么时候别用正则:简单分割、固定前缀判断、大小写替换,直接用字符串方法(split、startswith、replace)更快更稳,别什么都上正则。
我建议你把上面 10 段代码逐条复制到一个 test_regex.py 里跑一遍,改改输入看匹配怎么变——真正写熟正则,靠的是亲手调,不是背表。



