Python技术迷

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 re

bad_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 re

error_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 re

text = "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 re

pattern = 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 re

line = "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 re

ip_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 re

ticket = "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(),或者拿空结果去做统计,坑才露出来。