CASE-001
Grep & Co. 数据侦探事务所
委托书
委托人
“升级”网吧老板
证物
支付服务器日志。一位顾客坚称自己付了 4.99 美元,可系统里没有记录。
破案条件
在日志里找出出错的行,并且只挑出金额正好是 $4.99 的支付。
上班第一天早上,所长放下咖啡杯,把一张纸推到你面前。“昨天网吧老板来过。顾客说付了 4.99 美元,记录却没有。日志不会说谎。去找。”
日志长这样。今天就从这五行里找证据。
2026-10-20 09:12:01 INFO PAY user=kim amount=$4.99 status=OK
2026-10-20 09:12:07 INFO PAY user=lee amount=$14.99 status=OK
2026-10-20 09:13:44 ERROR PAY user=park amount=$4.99 status=TIMEOUT
2026-10-20 09:15:02 INFO PAY user=choi amount=$4999 status=OK
2026-10-20 09:16:30 ERROR DB connection lost正则表达式(regular expression)是一门小小的语言,用来写下你想找的文字长什么样子。在 Python 里用 re 模块来使用它。最简单的正则就是文字本身:模式 ERROR 找的就是 ERROR 这几个字。
import re
log = "2026-10-20 09:13:44 ERROR PAY user=park"
m = re.search("ERROR", log)
print(m) # <re.Match object; span=(20, 25), match='ERROR'>re.search(模式, 字符串) 会从前往后找出模式在字符串里第一次出现的位置。None。证据分析室
re.search(r"", text)
返回值
Match 对象既是“找到了”的证据,也是一份报告,写明找到了什么、在哪儿找到的。
m = re.search("ERROR", log)
if m: # 找到为真,None 为假
print(m.group()) # ERROR (找到的文字)
print(m.span()) # (20, 25) (开始位置, 结束位置)if m: 这样直接判断真假。没找到时是 None,这时调用 m.group() 会抛出 AttributeError。span() 的结束位置不包含在内。和 log[20:25] 正好是 "ERROR" 是同一个规则。大多数字符只匹配它自己。但下面这 14 个是在正则里有特殊含义的元字符。
. ^ $ * + ? { } [ ] \ | ( )今天只学其中第一个:.。. 表示除换行符以外的任意一个字符。其余的,以后每节课拿出一个来用。
re.search("a.c", "abc") # 匹配:b 落在 . 的位置
re.search("a.c", "a-c") # 匹配:- 也算任意字符
re.search("a.c", "ac") # None:. 一定要占掉一个字符
re.search("a.c", "a\nc") # None:换行符不算 .多特的提醒
事务所的桌子、椅子,连所长的键盘上面,我哪儿都能爬上去。只有一样翻不过去:窗户(换行符)。所以我的名字叫多特(.)。听说哪天有人帮我打开窗户,我就能跳过去了。
证据分析室
re.search(r"", text)
返回值
现在回到案子上。假设最上面那五行日志存放在字符串 pay_log 里。为了找 4.99 美元的支付,写成 4.99 会怎样?
re.findall("4.99", pay_log) # ['4.99', '4.99', '4.99', '4999'] ← 四个?本该是两个的结果变成了四个。$14.99 后半截的 4.99 被抓了进来;又因为 . 是“任意字符”,连 $4999 里的 4999 也混了进来。想把元字符按原样查找,就在它前面加反斜杠 \。\. 就是一个真正的句点。
$ 也是元字符。它的含义第 4 课再讲,现在只要记住“直接写的话它不会去找美元符号”。所以正确答案是 \$4\.99。
re.findall(r"\$4\.99", pay_log) # ['$4.99', '$4.99']证据分析室
re.search(r"", text)
返回值
上面我们在引号前加了 r,写成 r"\$4\.99"。这叫原始字符串(raw string)。
反斜杠在 Python 字符串里本身就有特殊含义:"\n" 是换行,"\t" 是制表符。所以如果用普通字符串写正则,Python 会先把反斜杠解释掉,re 收到的就成了另一个模式。
print(len("\b")) # 1 ← Python 把 \b 变成了一个“退格”字符
print(len(r"\b")) # 2 ← 反斜杠和 b,原样两个字符r"..." 不碰反斜杠,原封不动地交给 re。正则表达式一律用原始字符串来写。"\d" 这类 Python 不认识的转义,会出现 SyntaxWarning。用原始字符串就不会有警告。网吧老板又说:“有位顾客的账号是 kim.99+,这个也帮我找找。”这种文字直接写进模式,. 和 + 会被当成元字符。要按原样查找别人输入的文字,就用 re.escape,它会在元字符前面自动加上反斜杠。
print(re.escape("kim.99+")) # kim\.99\+
re.search(re.escape("kim.99+"), "user=kim.99+") # 匹配总结一下:出错的行用 ERROR 找,金额正好 4.99 美元的支付用 \$4\.99 找。把两个条件叠在一起,就会发现顾客 park 的支付因为 TIMEOUT 错误而没有被记录下来。
for line in pay_log.splitlines():
if re.search("ERROR", line) and re.search(r"\$4\.99", line):
print(line)
# 2026-10-20 09:13:44 ERROR PAY user=park amount=$4.99 status=TIMEOUT结案报告 — 支付服务器在 09:13:44 因超时将该笔支付判为失败,顾客的屏幕上却显示为已完成。建议网吧老板为顾客退款。
INFO 的行。一共有几行?\$1.\.99 会找到什么?在分析室里验证之前,先自己猜一猜。re.escape("1+1=2? [yes]") 的结果,再亲手运行看看。所长的笔记本
大多数字符只匹配它自己。只有 14 个元字符 . ^ $ * + ? { } [ ] \ | ( ) 是特殊的。
. 是除换行符以外的任意一个字符。真正的句点写作 \.
re.search 返回 Match 或 None。用 if m: 判断,用 m.group()、m.span() 查看。
正则一律用 r"..." 原始字符串来写。
按原样查找别人输入的文字,用 re.escape。
订阅新课时和新课程通知
本课程新增课时或推出后续课程时,我们会发邮件通知你。
问答
关于本课有任何疑问都可以在这里留言。所有语言版本页面的提问会汇集在一起。