线上有个日志清洗脚本,明明日志里有订单号,正则也没写错,结果一跑全是空。
我第一眼不太信正则错了,先把代码扒出来看了一眼:
import re
line = "2026-06-04 10:21:33 INFO pay success, order_id=PO202606040001, cost=36ms"
m = re.match(r"order_id=(PO\d+)", line)
print(m.group(1) if m else"not found")
输出:
not found
这个地方就很典型。
不是 order_id 不存在,也不是 \d+ 写错了,是 match() 根本不是这么用的。
match() 只从字符串开头开始匹配。
它的意思不是“帮我在这行文本里找一下”,而是“从第一个字符开始,你能不能匹配上”。
上面这行日志第一个字符是 2,而正则第一个要求是 order_id=,那肯定匹配不上。
换成 search():
import re
line = "2026-06-04 10:21:33 INFO pay success, order_id=PO202606040001, cost=36ms"
m = re.search(r"order_id=(PO\d+)", line)
print(m.group(1) if m else"not found")
输出:
PO202606040001
这才是大多数日志处理脚本里真正想要的效果。
search() 会从字符串里往后扫,只要某个位置能匹配上,就返回第一个匹配结果。
所以这俩区别其实就一句话:
match() 看开头。
search() 看整段。
但这句话太容易听完就忘,真正踩坑一般都在业务代码里。
比如有个接口参数校验,要求用户输入的活动码必须是 ACT- 开头,后面跟 6 位数字。
这种场景我会用 match(),因为我本来就只认开头。
import re
defcheck_campaign_code(code: str) -> bool:
ifnot code:
returnFalse
hit = re.match(r"ACT-\d{6}", code)
return hit isnotNone
print(check_campaign_code("ACT-102938")) # True
print(check_campaign_code("xx ACT-102938")) # False
这个结果没问题。
但是这里有个坑,我以前也见过有人这么写:
import re
defcheck_campaign_code(code: str) -> bool:
return re.match(r"ACT-\d{6}", code) isnotNone
print(check_campaign_code("ACT-102938-debug"))
输出是:
True
这就有点坑了。
因为 match() 只是要求从开头能匹配一段,不要求整串都匹配完。
ACT-102938-debug 的前面确实满足 ACT-\d{6},所以它返回 True。
如果你的意思是“整个字符串必须就是活动码”,别靠 match() 硬撑,直接用 fullmatch()。
import re
defcheck_campaign_code(code: str) -> bool:
return re.fullmatch(r"ACT-\d{6}", code or"") isnotNone
samples = [
"ACT-102938",
"ACT-102938-debug",
"xx ACT-102938",
]
for item in samples:
print(item, check_campaign_code(item))
输出:
ACT-102938 True
ACT-102938-debug False
xx ACT-102938 False
这三个函数放一起看就清楚了。
match():从开头匹配一段。
search():在字符串任意位置找一段。
fullmatch():整条字符串必须全部匹配。
我平时写脚本一般是这么分的。
处理日志、HTML 片段、接口返回文本,用 search() 多一些,因为目标字段通常藏在中间。
校验前缀格式,用 match()。
校验整个字段是否合法,用 fullmatch()。
再看一个更像现场的例子。
有个批量导入用户的 CSV,手机号字段有时候被运营塞成这样:
张三,手机号: 13800138000,北京
李四,phone=13900139000,上海
王五,无手机号,广州
这个时候你要提取手机号,用 match() 就很别扭,因为手机号不在开头。
import re
rows = [
"张三,手机号: 13800138000,北京",
"李四,phone=13900139000,上海",
"王五,无手机号,广州",
]
phone_re = re.compile(r"1[3-9]\d{9}")
for row in rows:
hit = phone_re.search(row)
phone = hit.group(0) if hit else""
print(row, "=>", phone)
输出:
张三,手机号: 13800138000,北京 => 13800138000
李四,phone=13900139000,上海 => 13900139000
王五,无手机号,广州 =>
这类东西别纠结,直接 search()。
但反过来,如果你在处理网关日志,只想找以 ERROR 开头的行,那 match() 就合适。
import re
lines = [
"ERROR timeout when call user-service",
"2026-06-04 ERROR timeout when call pay-service",
"WARN retry user-service",
]
err_head = re.compile(r"ERROR\s+")
for line in lines:
if err_head.match(line):
print("报警:", line)
这里只会打印第一行。
第二行虽然也有 ERROR,但它不是开头。
这就是 match() 的价值,它不是弱版 search(),它只是更挑位置。
还有一个容易绕晕的地方:正则里的 ^。
如果你这么写:
re.search(r"^ERROR", line)
它看起来用了 search(),但因为正则里加了 ^,所以也只能匹配开头。
也就是说,在很多单行文本里:
re.match(r"ERROR", line)
re.search(r"^ERROR", line)
效果差不多。
但我一般不建议把代码写得太绕。
要表达“开头匹配”,就用 match()。
要表达“到处找”,就用 search()。
要表达“整串必须合法”,就用 fullmatch()。
代码是给后面排查问题的人看的,不是给自己炫技的。
最后再贴一个我自己常用的小写法,别每次都 if m is not None 写一坨。
import re
_order_id = re.compile(r"order_id=(PO\d{12,})")
defpick_order_id(raw: str) -> str:
hit = _order_id.search(raw or"")
ifnot hit:
return""
return hit.group(1)
line = "INFO callback ok uid=88 order_id=PO202606040001 cost=21ms"
print(pick_order_id(line))
这种代码放在日志清洗、告警归因、批量补数据脚本里都够用。
记住一点就行:你想从第一个字符开始验,就 match();你想在一整段文本里捞东西,就 search()。
很多正则问题,坏就坏在函数选错了,不是表达式写错了。