上一篇讲了库,其中标准库的 re 模块就是干这个的:正则表达式

正则(regular expression / regex)= 一段描述”长什么样”的模式。 不是去搜一个固定的词,而是搜”所有符合这个长相的东西”——所有 IP、所有邮箱、所有 4 位数字的设备 ID。对安全分析师来说,这是从一堆日志里捞针的核心武器。

这正是上一篇配套工具里 type() 帮你确认类型之后,真正开始”按模式提取”的工具。


先看全景:用正则只需三步

import re                                  # 1. 导入 re 模块
pattern = "\d+"                            # 2. 写一个模式(存成字符串)
re.findall(pattern, "h32rb17")            # 3. 在目标字符串里找所有匹配 → ['32', '17']

re.findall() 是最常用的函数:返回所有匹配,装进一个列表 两个参数——第一个是模式,第二个是被搜索的字符串。

模式如果只是普通字符,就当普通搜索用:

re.findall("ts", "tsnow, tshah, bmoreno")   # → ['ts', 'ts']  只找字面的 "ts"

真正的威力来自特殊符号。下面两张表是全部核心。


速查表 1:字符类符号(匹配”哪种字符”)

每个符号匹配单个特定类型的字符:

符号匹配什么例子
\w任意字母/数字/下划线(A-z, 0-9, _)"ID_A17" → I,D,_,A,1,7
\d任意单个数字(0-9)"ID_A17" → 1,7
\s任意空白(空格、Tab、换行)"user 1" 里的空格
.任意字符(除了换行)几乎匹配一切
\.字面的句点 .反斜杠转义,只匹配真正的点号

\. 这个转义很关键:. 在正则里是”任意字符”的通配符。当你真的想匹配一个句点(比如 IP 地址里的点、邮箱里的点),必须写 \. 来”取消它的特殊含义”。这和普通字符串里的转义是一个道理。

re.findall("\w", "h32rb17")    # 每个字符都是字母数字 → 7 个元素
re.findall("\d", "h32rb17")    # 只要数字 → ['3','2','1','7']  4 个元素

速查表 2:数量符号(匹配”几个”)

放在字符/字符类后面,指定重复几次:

符号含义例子
+1 个或多个连续\d+ 匹配 1、12、12345
*0 个、1 个或多个包括”零次”,会匹配出空字符串
{n}恰好 n 个\d{4} 匹配 4 位连续数字(如 1234)
{m,n}m 到 n 个之间\d{1,3} 匹配 1、12 或 123

关键对比 + vs *:

re.findall("\d+", "h32rb17")   # → ['32', '17']    一串连续数字算一个
re.findall("\d*", "h32rb17")   # → ['', '32', '', '', '17', '']  零次也匹配,夹一堆空串

实战里 + 远比 * 常用——你几乎总是想要”至少一个”,而不是连空字符串都收进来。* 那串空串往往是 bug 来源。

{n}{m,n} 的精确控制:

re.findall("\d{2}", "h32rb17 k825t0m c2994eh")     # → ['32','17','82','29','94']  正好 2 位
re.findall("\d{1,3}", "h32rb17 k825t0m c2994eh")   # → ['32','17','825','0','299','4']  1~3 位

一个必须懂的机制:Python 从左往右扫,不回头

正则匹配的过程是这样的:

Python 从左到右扫字符串。一旦某段开头符合模式的第一个字符,就继续往后比;模式匹配完成后,从这次匹配的下一个字符重新开始

所以遇到连续三个数字 123,用 \d{2} 会:先匹配 12 → 然后从 3 重新开始(但 3 后面没数字了,不够 2 个,丢弃)。不会回头把 23 也算上。理解这个”不重叠、不回头”的机制,才能预判正则到底会捞出什么。


手把手:构造一个真实 pattern

正则的核心技能是把目标拆成小块,每块对应一个符号

假设这是一行混合日志,每个员工有:员工 ID、用户名(后跟冒号)、当天登录次数、部门:

employee_logins_string = "1001 bmoreno: 12 Marketing 1002 tshah: 7 Human Resources 1003 sgilmore: 5 Finance"

任务:只提取”用户名 + 登录次数”,不要 ID 和部门。

拆解你要找的东西:

想要的部分长什么样对应符号
用户名若干字母\w+
冒号一个 ::
空格一个空格\s
登录次数若干数字\d+

拼起来:

import re
pattern = "\w+:\s\d+"
employee_logins_string = "1001 bmoreno: 12 Marketing 1002 tshah: 7 Human Resources 1003 sgilmore: 5 Finance"
print(re.findall(pattern, employee_logins_string))
# → ['bmoreno: 12', 'tshah: 7', 'sgilmore: 5']

ID 和部门因为不符合”字母+冒号+空格+数字”这个长相,被自动排除了。这就是正则的威力:你描述长相,它负责筛。

⚠️ 正则有风险:模式写松了会捞出多余的东西,写紧了会漏掉想要的。所以永远要拿真实数据测一遍。在线工具 regex101.com 能实时高亮匹配,调试正则神器。


给 vibe-coder:正则是你最该会读的”AI 代码”

正则是 AI 最爱生成、你最该看得懂的东西之一。当 AI 给你一段 re.findall(r"...", log):

  • 先看它想匹配什么 —— 把 pattern 对着上面两张表拆开,就知道它在捞 IP 还是邮箱还是别的
  • 警惕过度匹配 —— AI 给的正则经常”看起来对,实际多捞/漏捞”。拿你的真实数据测,别直接信
  • 安全场景常见用途:从日志提取可疑 特征、提取所有外联 IP、解析告警里的字段、做输入校验(防注入时验证格式)

但也别神化它:正则不擅长嵌套结构(HTML、JSON)。看到 AI 用正则解析 HTML,提醒它——那是 专门的库(如 bs4)的活。

一个经典提醒:“用正则解析 HTML”是程序员圈著名的反模式。结构化数据用结构化的解析器,正则只适合”扁平文本里找模式”。


一句话总结

正则 = 描述长相的模式,re.findall() 把所有符合的捞进列表。两类符号:字符类(\w \d \s . \. 管”哪种字符”)+ 数量(+ * {n} {m,n} 管”几个”)。构造时把目标拆成小块逐一对应,写完一定拿真实数据测。会读会写正则,你就能从一堆原始日志里,几行代码捞出真正关心的那部分——这是安全自动化里使用频率最高的技能之一。