Python 正则表达式中 match 和 search 有什么区别?
日志里明明有 ERROR,脚本跑完却筛出来 0 行。
这类问题我见过不少次,最后一看代码,八成不是日志没问题,也不是正则写错得离谱,而是把 re.match() 当成了 re.search() 用。
比如线上日志长这样:
2026-05-18 10:23:11 payment ERROR order_id=88391 msg=balance_not_enough
2026-05-18 10:23:12 payment INFO order_id=88392 msg=paid
2026-05-18 10:23:13 refund ERROR order_id=88393 msg=refund_timeout
有人想把错误日志捞出来,顺手写了这么一段:
import rebad_lines = []
with open("app.log", "r", encoding="utf-8") as f:
for line in f:
if re.match(r"ERROR", line):
bad_lines.append(line.rstrip())
print(len(bad_lines))
结果是 0。
这地方我第一眼一般不看正则语法,先看你用的是 match 还是 search。
match() 只从字符串开头尝试匹配。
上面每行日志开头是时间,ERROR 在中间,所以它肯定匹配不到。不是 Python 抽风,是你让它从门口找人,它只看门口站没站 ERROR,屋里坐着它不管。
换成 search() 才是你想要的效果:
import reerror_lines = []
with open("app.log", "r", encoding="utf-8") as f:
for line_no, line in enumerate(f, 1):
if re.search(r"\bERROR\b", line):
error_lines.append((line_no, line.rstrip()))
for line_no, text in error_lines:
print(line_no, text)
这里用了 \bERROR\b,不是装讲究。
如果直接搜 ERROR,像 NOERROR、ERROR_CODE 这种字符串也可能被扫进去。日志处理脚本最怕这种“看着差不多”,后面排查时会把人带沟里。
再看一个更直观的例子:
import retext = "user=tom action=login ip=10.2.8.6"
print(re.match(r"user=\w+", text))
print(re.match(r"action=\w+", text))
print(re.search(r"action=\w+", text))
第一行能匹配,因为 user=tom 就在开头。
第二行是 None,因为 action=login 不在开头。
第三行能匹配,因为 search() 会从左到右扫描,只要中间有一段符合规则就返回。
如果把结果打印细一点:
m1 = re.match(r"user=\w+", text)
m2 = re.search(r"action=\w+", text)print(m1.group() if m1 else"not found")
print(m2.group() if m2 else"not found")
输出就是:
user=tom
action=login
这里有个坑,别写成这样:
m = re.match(r"action=\w+", text)
print(m.group())
如果匹配不到,m 是 None,然后你再调 group(),直接报:
AttributeError: 'NoneType' object has no attribute 'group'
这种异常在小脚本里还好,一眼能看出来。放到定时任务里就恶心了,跑了半夜,第二天只留一行空泛的失败日志。
我自己写这类脚本,一般会先把判断拆出来:
import repattern = re.compile(r"order_id=(\d+)")
defpick_order_id(line: str) -> str | None:
found = pattern.search(line)
ifnot found:
returnNone
return found.group(1)
line = "2026-05-18 10:23:11 payment ERROR order_id=88391 msg=balance_not_enough"
order_id = pick_order_id(line)
if order_id:
print("need_check_order:", order_id)
为什么这里用 search()?
因为 order_id 不一定在行首。日志格式稍微一改,前面多一个 traceId、机器名、线程名,match() 立刻废掉。
但 match() 不是没用。
它适合字段必须从开头开始校验的场景,比如校验一行是不是以合法日期开头:
import reline = "2026-05-18 10:23:11 payment ERROR order_id=88391"
m = re.match(r"\d{4}-\d{2}-\d{2}", line)
ifnot m:
print("bad log header:", line)
else:
print("date:", m.group())
这种地方我会用 match(),因为我就是要检查开头。
如果一行日志开头不是日期,那它可能是异常堆栈的后续行,也可能是日志被截断了。这个判断本身就有业务含义,不能用 search() 放宽。
再比如解析 nginx access log,行首必须是 IP:
import reip_head = re.compile(r"\d{1,3}(?:\.\d{1,3}){3}")
line = "10.4.7.21 - - [18/May/2026:10:23:11] GET /api/pay"
m = ip_head.match(line)
if m:
print("client_ip:", m.group())
else:
print("not access log:", line)
这里如果改成 search(),风险就变了。
某些日志内容里也可能带 IP,比如请求参数、User-Agent、错误信息。你一搜,搜到了,不代表这行就是 access log。这个区别在清洗日志时很关键。
还有人喜欢这么写:
re.match(r".*ERROR", line)
能不能用?能。
但我一般不这么写。
你明明想表达“只要行里有 ERROR”,那就用 search(r"\bERROR\b", line)。写 .*ERROR,等于让正则从开头一路吞,吞到后面再回头配。日志量一大,规则再复杂点,这种写法容易把脚本拖慢。不是每次都会出事,但没必要给自己埋这种点。
还有一个容易混的:match() 和 ^ 不是完全一回事,但在普通用法里很像。
re.match(r"ERROR", line)
re.search(r"^ERROR", line)
这两句在“从整行开头找 ERROR”这个场景下,效果差不多。
但读代码的时候,我更愿意这样区分:
想校验开头,用 match()。
想在整段文本里找,用 search()。
想整段文本必须完全符合,用 fullmatch()。
比如校验一个工单号,不希望它前后混杂别的字符:
import reticket = "BUG-20260518-0091"
if re.fullmatch(r"BUG-\d{8}-\d{4}", ticket):
print("valid ticket")
else:
print("bad ticket")
这个地方如果用 search(),xxxBUG-20260518-0091yyy 也可能过。线上脏数据就是这么混进去的,一开始没人注意,后面查库的时候全是奇怪值。
所以这俩方法不用背概念,按排查习惯记就行。
日志筛选、字段提取、页面内容扫描,大多数用 search()。
协议头、日志行首、固定前缀校验,用 match()。
整串格式校验,比如订单号、手机号、配置项值,用 fullmatch()。
正则本身已经够难读了,方法再选错,代码看着没毛病,结果全是歪的。尤其是 match(),它不会提醒你“兄弟,我只看开头”,它只会安静地返回一个 None。等你拿这个 None 去调 group(),或者拿空结果去做统计,坑才露出来。