正则表达式教程(软件测试人员专用)¶
本教程面向软件测试工程师,聚焦测试工作中高频使用的正则表达式场景:日志分析、数据提取、接口断言、数据校验。
前置要求¶
无前置要求,零基础可直接学习。
新手导读¶
正则表达式不适合死记硬背。测试人员第一遍只需要掌握几类常用模式:数字、字母、任意字符、分组、重复次数、非贪婪匹配。
建议先会这些:
\d+:匹配数字。.*?:匹配任意内容但尽量少匹配。():提取分组。^和$:限制开头和结尾。\s+:匹配空白字符。
学习时一定配合在线工具或 Python re 模块练习,边试边看匹配结果。
一、正则表达式基础¶
1.1 什么是正则表达式¶
正则表达式(Regular Expression,简称 regex/regexp/re)是一种 文本模式匹配工具,用特定的字符序列描述一类字符串。
1.2 测试人员为什么要学正则¶
| 场景 | 用途 |
|---|---|
| 日志分析 | 从海量日志中提取错误信息、时间戳、ID |
| 接口断言 | 验证响应中的动态字段(如 token 格式、订单号) |
| 数据提取 | 从文本中提取手机号、邮箱、URL |
| 数据校验 | 验证输入格式是否合法 |
| 数据清理 | 批量替换、清洗测试数据 |
| 抓包工具 | Fiddler/JMeter 过滤规则 |
1.3 基本概念¶
- 字面字符:
abc匹配字面 "abc" - 元字符:
.\d\w等有特殊含义 - 量词:
*+?{n}控制重复次数 - 分组:
()将多个字符作为整体
二、元字符详解¶
2.1 常用元字符¶
| 元字符 | 含义 | 示例 | 匹配 |
|---|---|---|---|
. |
任意字符(除换行) | a.c |
abc、a1c、a c |
\d |
数字 [0-9] |
\d+ |
123 |
\D |
非数字 | \D+ |
abc |
\w |
字母数字下划线 [a-zA-Z0-9_] |
\w+ |
hello_123 |
\W |
非字母数字下划线 | \W+ |
@#$ |
\s |
空白字符(空格、Tab、换行) | \s+ |
空格、Tab |
\S |
非空白字符 | \S+ |
hello |
\b |
单词边界 | \bcat\b |
cat(不匹配 category) |
2.2 字符类 []¶
[abc] 匹配 a 或 b 或 c
[a-z] 匹配任意小写字母
[A-Z] 匹配任意大写字母
[0-9] 匹配任意数字(等同 \d)
[a-zA-Z0-9] 匹配字母或数字
[^abc] 匹配除 a、b、c 以外的字符(取反)
[.] 匹配字面的点(元字符在 [] 内失效)
2.3 转义字符¶
三、量词¶
3.1 基本量词¶
| 量词 | 含义 | 示例 | 匹配 |
|---|---|---|---|
* |
0 次或多次 | ab*c |
ac、abc、abbc |
+ |
1 次或多次 | ab+c |
abc、abbc(不匹配 ac) |
? |
0 次或 1 次 | ab?c |
ac、abc |
{n} |
恰好 n 次 | \d{3} |
123 |
{n,} |
至少 n 次 | \d{2,} |
12、123、1234 |
{n,m} |
n 到 m 次 | \d{2,4} |
12、123、1234 |
3.2 贪婪 vs 非贪婪¶
| 贪婪 | 非贪婪 | 含义 |
|---|---|---|
* |
*? |
0 次或多次(最少) |
+ |
+? |
1 次或多次(最少) |
? |
?? |
0 次或 1 次(最少) |
{n,m} |
{n,m}? |
n 到 m 次(最少) |
测试常用: 提取 JSON 字段值时用非贪婪避免匹配过多。
四、分组与捕获¶
4.1 捕获组 ()¶
匹配 2026-06-08:
- 第 1 组:2026
- 第 2 组:06
- 第 3 组:08
4.2 命名捕获组¶
可通过名称 year、month、day 引用。
4.3 非捕获组 (?:)¶
只分组不捕获,节省资源:
匹配 URL,但 http/https 不单独捕获。
4.4 反向引用¶
匹配重复的单词,如 hello hello、the the。
五、零宽断言¶
5.1 四种断言¶
| 断言 | 含义 | 示例 |
|---|---|---|
(?=...) |
正向前瞻(后面是) | \d+(?=元) 匹配 "100元" 中的 "100" |
(?!) |
负向前瞻(后面不是) | \d+(?!元) 匹配非 "元" 前的数字 |
(?<=...) |
正向回顾(前面是) | (?<=\$)\d+ 匹配 "$100" 中的 "100" |
(?<!...) |
负向回顾(前面不是) | (?<!\$)\d+ 匹配非 "$" 后的数字 |
5.2 测试常用示例¶
# 提取金额数字(去掉"元")
\d+(?=元) "100元" → "100"
# 提取冒号后的值
(?<=:)\s*\S+ "name:张三" → "张三"
# 匹配非 200 的状态码
(?<!2)\d{3} 匹配 404、500 等
六、常用正则模式¶
6.1 常用数据格式¶
| 需求 | 正则 |
|---|---|
| 手机号 | 1[3-9]\d{9} |
| 邮箱 | [a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,} |
| 身份证 | [1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx](简化版,含地区/生日校验) |
| IP 地址 | \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3} |
| URL | https?://[^\s]+ |
| 日期 | \d{4}-\d{2}-\d{2} |
| 时间 | \d{2}:\d{2}:\d{2} |
| 中文 | [一-鿿]+(基本汉字区,覆盖常用字;需更全可用 regex 库的 \p{Han}) |
| 订单号 | [A-Z]{2}\d{14} |
| UUID | [0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12} |
6.2 日志分析常用¶
| 需求 | 正则 |
|---|---|
| 提取 ERROR 行 | ^.*ERROR.*$ |
| 提取时间戳 | \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} |
| 提取 IP | \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3} |
| 提取 traceId | traceId=[a-z0-9]+ |
| 提取 HTTP 状态码 | "status":\s*\d{3} |
| 提取响应时间 | "duration":\s*\d+ |
6.3 接口测试常用¶
| 需求 | 正则 |
|---|---|
| Token 格式 | eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+ |
| JSON code 字段 | "code":\s*\d+ |
| JSON msg 字段 | "msg":\s*"[^"]*" |
| 提取数字 ID | "id":\s*(\d+) |
| 提取 URL 中的 ID | /users/(\d+)/orders |
七、Python 正则用法¶
7.1 re 模块基础¶
import re
# 查找第一个匹配
result = re.search(r"\d+", "订单号12345,金额100元")
print(result.group()) # "12345"
print(result.start()) # 3(起始位置)
print(result.end()) # 8(结束位置)
# 查找所有匹配
results = re.findall(r"\d+", "订单号12345,金额100元")
print(results) # ["12345", "100"]
# 完整匹配(整个字符串必须匹配)
result = re.match(r"\d+", "12345abc")
print(result.group()) # "12345"
# 替换
text = re.sub(r"\d+", "*", "电话13800138000")
print(text) # "电话*"
# 分割
parts = re.split(r"[,;\s]", "a,b;c d")
print(parts) # ["a", "b", "c", "d"]
# 编译(多次使用时性能更好)
pattern = re.compile(r"\d+")
pattern.findall("abc123def456")
7.2 分组提取¶
# 普通分组
match = re.search(r"(\d{4})-(\d{2})-(\d{2})", "日期:2026-06-08")
print(match.group(0)) # "2026-06-08"(整个匹配)
print(match.group(1)) # "2026"(第 1 组)
print(match.group(2)) # "06"(第 2 组)
print(match.group(3)) # "08"(第 3 组)
# 命名分组
match = re.search(r"(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})", "2026-06-08")
print(match.group("year")) # "2026"
print(match.group("month")) # "06"
7.3 常用标志¶
# 忽略大小写
re.search(r"error", "ERROR message", re.IGNORECASE)
# 多行模式(^ $ 匹配每行)
re.findall(r"^\d+.*$", "line1\n123abc\nline2", re.MULTILINE)
# 点号匹配换行
re.search(r"<div>.*?</div>", "<div>\nhello\n</div>", re.DOTALL)
7.4 常见错误¶
# ❌ 忘记 raw string
re.search("\d+", text) # \d 被 Python 解释为转义
# ✅ 用 r 前缀
re.search(r"\d+", text)
# ❌ 贪婪匹配导致过多
re.search(r"<.*>", "<div>hello</div>") # 匹配整个
# ✅ 非贪婪
re.search(r"<.*?>", "<div>hello</div>") # 只匹配 <div>
八、测试场景实战¶
8.1 场景一:日志提取 ERROR 行¶
import re
log = """
2026-06-08 10:00:01 INFO - 用户登录成功
2026-06-08 10:00:02 ERROR - NullPointerException at line 42
2026-06-08 10:00:03 INFO - 查询订单完成
2026-06-08 10:00:04 ERROR - Connection timeout
"""
# 提取所有 ERROR 行
errors = re.findall(r"^.*ERROR.*$", log, re.MULTILINE)
for err in errors:
print(err)
8.2 场景二:提取接口响应中的 token¶
import re
response_text = '{"code":0,"data":{"token":"eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjEyMzQ1fQ.abc123"}}'
# 提取 JWT token
match = re.search(r'"token":"(eyJ[^"]+)"', response_text)
if match:
token = match.group(1)
print(f"Token: {token}")
8.3 场景三:验证手机号格式¶
import re
def is_valid_phone(phone):
return bool(re.match(r"^1[3-9]\d{9}$", phone))
assert is_valid_phone("13800138000") == True
assert is_valid_phone("12345678901") == False
assert is_valid_phone("1380013800") == False # 少一位
assert is_valid_phone("138001380001") == False # 多一位
8.4 场景四:提取 URL 中的 ID¶
import re
urls = [
"/api/users/12345/orders",
"/api/users/67890/orders",
"/api/products/111/info"
]
for url in urls:
match = re.search(r"/users/(\d+)/", url)
if match:
user_id = match.group(1)
print(f"URL: {url} → userId: {user_id}")
8.5 场景五:批量替换敏感信息¶
import re
log = "用户 13800138000 登录,邮箱 test@example.com,身份证 110101199001011234"
# 手机号脱敏
log = re.sub(r"(1[3-9]\d)\d{4}(\d{4})", r"\1****\2", log)
# 邮箱脱敏
log = re.sub(r"([a-zA-Z0-9._%+-]+)@([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})", r"***@\2", log)
# 身份证脱敏
log = re.sub(r"(\d{6})\d{8}(\d{3}[\dXx])", r"\1********\2", log)
print(log)
# "用户 138****8000 登录,邮箱 ***@example.com,身份证 110101********1234"
8.6 场景六:JMeter 正则提取器¶
JMeter 的 Regular Expression Extractor 使用正则提取响应数据:
后续用 ${token} 引用。
九、在线工具与练习¶
9.1 在线测试工具¶
| 工具 | 地址 |
|---|---|
| regex101 | https://regex101.com/(推荐,支持 Python/JS/Go) |
| regexr | https://regexr.com/(可视化) |
| 正则表达式在线测试 | https://tool.oschina.net/regex/ |
9.2 练习平台¶
| 平台 | 地址 |
|---|---|
| RegexOne | https://regexone.com/(交互式教程) |
| HackerRank Regex | https://www.hackerrank.com/domains/regex |
| LeetCode(字符串题) | https://leetcode.cn/ |
9.3 调试技巧¶
- 先用简单模式匹配,再逐步精确
- 用 regex101 实时查看匹配结果和解释
- 注意贪婪 vs 非贪婪
- 复杂正则加注释(
re.VERBOSE)
# verbose 模式(允许注释和空格)
pattern = re.compile(r"""
(\d{4}) # 年
- # 分隔符
(\d{2}) # 月
-
(\d{2}) # 日
""", re.VERBOSE)
十、常见问题排查¶
10.1 匹配不到¶
- 检查是否用了
r""raw string - 检查是否遗漏转义(
.\*+等) - 用 regex101 逐步调试
- 检查目标字符串是否有隐藏字符(换行、Tab)
10.2 匹配过多(贪婪问题)¶
# 贪婪:匹配从第一个 < 到最后一个 >
re.findall(r"<.*>", "<div>hello</div>") # ['<div>hello</div>']
# 非贪婪:只匹配最短
re.findall(r"<.*?>", "<div>hello</div>") # ['<div>', '</div>']
10.3 中文匹配问题¶
10.4 性能问题¶
# 多次使用同一正则,先编译
pattern = re.compile(r"\d+")
for line in lines:
pattern.findall(line) # 比 re.findall 快
附录:速查表¶
. 任意字符 \d 数字
\w 字母数字下划线 \s 空白
\b 单词边界 ^ 行首
$ 行尾 [] 字符类
[^] 取反字符类 | 或
() 分组 (?:) 非捕获分组
(?P<>) 命名分组 \1 反向引用
* 0+ 次 + 1+ 次
? 0-1 次 {n} n 次
{n,m} n-m 次 *? 非贪婪
(?=) 正向前瞻 (?!) 负向前瞻
(?<=) 正向回顾 (?<!) 负向回顾
学习建议
不需要背所有语法,记住常用模式(\d+、.*?、())即可。遇到复杂需求去 regex101 实时调试。实践中积累比死记硬背有效。
推荐下一步¶
根据你的学习进度,选择下一步:
- 如果你想继续学工具:学习 网络知识教程,掌握 HTTP 协议和排障
- 如果你想学接口测试:学习 接口测试方法论,掌握用例设计
- 如果你想进入下一阶段:学习 Python 接口自动化,用代码做数据校验
通关检查¶
完成本阶段后,使用 第2阶段-工具实战通关 检查是否可以进入下一阶段。