跳转至

正则表达式教程(软件测试人员专用)

本教程面向软件测试工程师,聚焦测试工作中高频使用的正则表达式场景:日志分析、数据提取、接口断言、数据校验。


前置要求

无前置要求,零基础可直接学习。


新手导读

正则表达式不适合死记硬背。测试人员第一遍只需要掌握几类常用模式:数字、字母、任意字符、分组、重复次数、非贪婪匹配。

建议先会这些:

  1. \d+:匹配数字。
  2. .*?:匹配任意内容但尽量少匹配。
  3. ():提取分组。
  4. ^$:限制开头和结尾。
  5. \s+:匹配空白字符。

学习时一定配合在线工具或 Python re 模块练习,边试边看匹配结果。


一、正则表达式基础

1.1 什么是正则表达式

正则表达式(Regular Expression,简称 regex/regexp/re)是一种 文本模式匹配工具,用特定的字符序列描述一类字符串。

1.2 测试人员为什么要学正则

场景 用途
日志分析 从海量日志中提取错误信息、时间戳、ID
接口断言 验证响应中的动态字段(如 token 格式、订单号)
数据提取 从文本中提取手机号、邮箱、URL
数据校验 验证输入格式是否合法
数据清理 批量替换、清洗测试数据
抓包工具 Fiddler/JMeter 过滤规则

1.3 基本概念

正则表达式:\d{4}-\d{2}-\d{2}
匹配目标:今天日期是 2026-06-08
匹配结果:2026-06-08
  • 字面字符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 非贪婪

贪婪(默认,尽可能多匹配):
  <.*>     匹配 <div>hello</div>  → 整个字符串

非贪婪(尽可能少匹配):
  <.*?>    匹配 <div>hello</div>  → <div>
贪婪 非贪婪 含义
* *? 0 次或多次(最少)
+ +? 1 次或多次(最少)
? ?? 0 次或 1 次(最少)
{n,m} {n,m}? n 到 m 次(最少)

测试常用: 提取 JSON 字段值时用非贪婪避免匹配过多。


四、分组与捕获

4.1 捕获组 ()

(\d{4})-(\d{2})-(\d{2})

匹配 2026-06-08: - 第 1 组:2026 - 第 2 组:06 - 第 3 组:08

4.2 命名捕获组

(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})

可通过名称 yearmonthday 引用。

4.3 非捕获组 (?:)

只分组不捕获,节省资源:

(?:http|https)://(.+)

匹配 URL,但 http/https 不单独捕获。

4.4 反向引用

(\w+)\s+\1

匹配重复的单词,如 hello hellothe 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
正则表达式:"token":"(eyJ[^"]+)"
模板:$1$
匹配号:1

后续用 ${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 调试技巧

  1. 先用简单模式匹配,再逐步精确
  2. 用 regex101 实时查看匹配结果和解释
  3. 注意贪婪 vs 非贪婪
  4. 复杂正则加注释(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 中文匹配问题

# Python 3 默认支持 Unicode
re.findall(r"[一-鿿]+", "hello 你好 world 世界")
# ['你好', '世界']

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 实时调试。实践中积累比死记硬背有效。

推荐下一步

根据你的学习进度,选择下一步:

  1. 如果你想继续学工具:学习 网络知识教程,掌握 HTTP 协议和排障
  2. 如果你想学接口测试:学习 接口测试方法论,掌握用例设计
  3. 如果你想进入下一阶段:学习 Python 接口自动化,用代码做数据校验

通关检查

完成本阶段后,使用 第2阶段-工具实战通关 检查是否可以进入下一阶段。