正则表达式是 Python 文本处理的 "瑞士军刀",无论是数据校验、信息提取还是文本清洗,都能大幅提升效率。本文整理了 12 个日常开发中高频使用的实战案例,从基础验证到复杂解析,全程带代码、带思路,小白也能跟着练!
一、前言:为什么要学正则表达式?
在 Python 开发中,我们经常遇到这些场景:
- 验证用户输入的手机号、邮箱是否合法
- 从杂乱文本中提取身份证号、金额、链接
- 批量替换文本中的敏感信息(如手机号打码)
- 解析日志文件、HTML 页面中的关键数据
这些需求用普通字符串方法(find、split)实现起来繁琐且易出错,而正则表达式能通过简洁的语法实现复杂匹配逻辑,一行代码搞定重复工作!
本文基于 Python 内置 re 模块,聚焦实战场景,每个案例都包含「需求分析→正则思路→完整代码→结果解析」
二、基础验证类(表单 / 数据校验必备)
案例 1:验证中国大陆手机号
需求 :判断输入是否为 11 位有效手机号(支持 13/14/15/17/18/19 号段)正则思路:
- ^1:强制开头为数字 1(手机号专属前缀)
-
3-9\]:第二位为 3-9(排除 12/11 等无效号段)
- $:结尾符(避免字符串后接多余字符)
python
import re
def is_valid_phone(phone):
# 预编译正则,多次调用更高效
pattern = re.compile(r"^1[3-9]\d{9}$")
return pattern.match(phone) is not None
# 测试用例
test_phones = [
"13812345678", # 有效
"19987654321", # 有效
"1234567890", # 无效(不足11位)
"138abc12345", # 无效(含非数字)
"138123456789" # 无效(超过11位)
]
for phone in test_phones:
status = "✅ 有效" if is_valid_phone(phone) else "❌ 无效"
print(f"{phone} → {status}")
输出结果:13812345678 → ✅ 有效19987654321 → ✅ 有效1234567890 → ❌ 无效138abc12345 → ❌ 无效138123456789 → ❌ 无效
案例 2:验证邮箱格式(支持多级域名)
需求 :兼容常见邮箱格式(如 abc@qq.com、user.name_123@gmail.com、test@163.com.cn)正则思路:
- 用户名:[a-zA-Z0-9_.+-]+(支持字母、数字、下划线、点、加减号)
- @:固定分隔符
- 域名:[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+(支持多级域名,如 .com.cn)
python
def is_valid_email(email):
pattern = re.compile(r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$")
return pattern.match(email) is not None
# 测试用例
test_emails = [
"abc@qq.com",
"user.name_123@gmail.com",
"test@163.com.cn",
"abc@.com", # 域名开头不能是.
"a@b.c" # 极简合法格式
]
for email in test_emails:
status = "✅ 有效" if is_valid_email(email) else "❌ 无效"
print(f"{email} → {status}")
案例 3:验证 18 位身份证号
需求 :支持最后一位为数字或大写 X(身份证校验码规则)正则思路:
- ^\d {17}:前 17 位必须是数字
-
0-9X\]:第 18 位为数字或大写 X
python
def is_valid_id_card(id_card):
pattern = re.compile(r"^\d{17}[0-9X]$")
return pattern.match(id_card) is not None
# 测试用例
test_ids = [
"110101199001011234",
"11010119900101123X",
"11010119900101123", # 不足18位
"11010119900101123x" # 小写x无效
]
for id in test_ids:
status = "✅ 有效" if is_valid_id_card(id) else "❌ 无效"
print(f"{id} → {status}")
三、数据提取类(文本解析核心技能)
案例 4:从文本中提取所有有效手机号
需求 :忽略无关字符,批量抓取文本中的 11 位手机号核心技巧:用 re.findall () 匹配所有符合规则的子串(去掉 ^ 和 $ 不限制位置)
python
text = """
联系我:13812345678(工作),私人电话19987654321,
备用号1234567890(无效),还有13579246801,紧急联系人18800001111
"""
# 提取所有有效手机号
phones = re.findall(r"1[3-9]\d{9}", text)
print("提取到的手机号:", phones)
# 输出:['13812345678', '19987654321', '13579246801', '18800001111']
案例 5:从身份证号提取出生日期
需求 :从 18 位身份证中提取「年 - 月 - 日」格式日期(身份证第 7-14 位为出生日期)核心技巧:用分组捕获年、月、日,再拼接格式
python
def get_birthday_from_id(id_card):
# 分组捕获年、月、日
pattern = re.compile(r"^\d{6}(\d{4})(\d{2})(\d{2})\d{4}[0-9X]$")
result = pattern.match(id_card)
if result:
return f"{result.group(1)}-{result.group(2)}-{result.group(3)}"
return "❌ 无效身份证号"
# 测试
id_card = "110101199001011234"
print("出生日期:", get_birthday_from_id(id_card))
# 输出:出生日期:1990-01-01
案例 6:提取 HTML 中的所有链接
需求 :从 HTML 文本中抓取 <a href="..."> 标签中的 URL核心技巧:非贪婪匹配 .*? 避免匹配多个链接时 "贪多"
python
html_text = '''
<div class="nav">
<a href="https://www.baidu.com">百度</a>
<a href="https://www.python.org">Python官网</a>
<a href="https://github.com" target="_blank">GitHub</a>
</div>
'''
# 提取所有href属性值
urls = re.findall(r'<a href="(.*?)">', html_text)
print("提取到的链接:")
for url in urls:
print(f"🔗 {url}")
案例 7:提取文本中的所有金额
需求 :支持整数(如 99)、小数(如 123.45)、带人民币符号(如 ¥66.8)的金额提取核心技巧:用 ¥? 匹配可选的人民币符号,\d+.?\d* 匹配整数 / 小数
python
text = "价格:99元,优惠后123.45元,限时¥66.8,最高补贴1000元,无门槛券0.5元"
# 提取金额数字(忽略单位)
amounts = re.findall(r"¥?(\d+\.?\d*)", text)
print("提取到的金额:", amounts) # ['99', '123.45', '66.8', '1000', '0.5']
# 转换为浮点数便于计算
amounts_float = [float(amt) for amt in amounts]
print("总金额:", sum(amounts_float)) # 输出:1299.75
四、文本替换类(批量清洗 / 修改文本)
案例 8:手机号中间 4 位打码(隐私保护)
需求 :将手机号 13812345678 转为 138****5678核心技巧:分组捕获前 3 位和后 4 位,替换时引用分组
python
text = "手机号:13812345678,备用号19987654321,紧急联系人18800001111"
# 分组捕获前3位(\d{3})和后4位(\d{4}),中间4位替换为****
masked_text = re.sub(r"(\d{3})\d{4}(\d{4})", r"\1****\2", text)
print("打码后
print("打码后:", masked_text)
# 输出:手机号:138****5678,备用号199****4321,紧急联系人188****1111
案例 9:清除文本中的所有标点符号
需求 :保留汉字、字母、数字,去掉逗号、句号、感叹号等标点核心技巧:用 [^a-zA-Z0-9\u4e00-\u9fa5] 匹配 "非目标字符",替换为空
python
text = "Hello!Python正则,真的很实用~ 你学会了吗?12345!"
# 清除所有标点(保留汉字、字母、数字)
clean_text = re.sub(r"[^a-zA-Z0-9\u4e00-\u9fa5]", "", text)
print("清洗后:", clean_text)
# 输出:HelloPython正则真的很实用你学会了吗12345
案例 10:批量替换日期格式(YYYY-MM-DD → MM/DD/YYYY)
需求 :将 2025-10-01 转为 10/01/2025核心技巧:分组捕获年、月、日,调整分组顺序替换
python
text = "会议时间:2025-10-01,截止日期2025-12-31,发布于2024-09-15"
# 分组捕获(\d{4})-(\d{2})-(\d{2}),替换为\2/\3/\1
new_text = re.sub(r"(\d{4})-(\d{2})-(\d{2})", r"\2/\3/\1", text)
print("替换后:", new_text)
# 输出:会议时间:10/01/2025,截止日期12/31/2025,发布于09/15/2024
五、复杂场景实战(综合应用)
案例 11:解析日志文件中的 404 错误
需求 :从 Nginx 日志中提取 404 错误的 IP 和请求路径日志格式示例:192.168.1.1 - - [10/Oct/2025:14:30:00 +0800] "GET /index.html HTTP/1.1" 200 123410.0.0.1 - - [10/Oct/2025:14:31:00 +0800] "GET /nonexistent HTTP/1.1" 404 567192.168.1.2 - - [10/Oct/2025:14:32:00 +0800] "POST /api/data HTTP/1.1" 404 890
正则思路:
- 捕获 IP:(\d+.\d+.\d+.\d+)(匹配 IPv4 地址)
- 捕获路径:"[A-Z]+ (.*?) HTTP/1.1"(匹配请求方法后的路径)
- 筛选 404:"404"(只匹配状态码为 404 的记录)
python
log_text = '''
192.168.1.1 - - [10/Oct/2025:14:30:00 +0800] "GET /index.html HTTP/1.1" 200 1234
10.0.0.1 - - [10/Oct/2025:14:31:00 +0800] "GET /nonexistent HTTP/1.1" 404 567
192.168.1.2 - - [10/Oct/2025:14:32:00 +0800] "POST /api/data HTTP/1.1" 404 890
'''
# 提取404错误的IP和路径
errors = re.findall(r"(\d+\.\d+\.\d+\.\d+).*?\"[A-Z]+ (.*?) HTTP/1.1\" 404", log_text)
print("404错误详情:")
for ip, path in errors:
print(f"IP: {ip}, 请求路径: {path}")
输出结果:404 错误详情:IP: 10.0.0.1, 请求路径: /nonexistentIP: 192.168.1.2, 请求路径: /api/data
案例 12:验证并提取 URL 中的域名
需求 :从 URL 中提取主域名(如 https://www.baidu.com/s?wd=python → baidu.com)正则思路:
- 匹配协议(可选):(?:https?://)?(非捕获分组,不提取协议)
- 匹配域名:(www.)?([a-zA-Z0-9-]+.[a-zA-Z0-9-.]+)(捕获 www 前缀和主域名)
- 忽略路径:/?.*(匹配域名后的路径和参数)
python
def extract_domain(url):
# 匹配URL并捕获域名
pattern = re.compile(r"^(?:https?://)?(www\.)?([a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+)/?.*$")
result = pattern.match(url)
if result:
return result.group(2) # 返回主域名(去掉www.)
return "❌ 无效URL"
# 测试
urls = [
"https://www.baidu.com/s?wd=python",
"http://github.com",
"www.python.org/doc",
"baidu.com",
"invalid-url"
]
for url in urls:
print(f"{url} → 域名:{extract_domain(url)}")
输出结果 :https://www.baidu.com/s?wd=python → 域名:baidu.comhttp://github.com → 域名:github.comwww.python.org/doc → 域名:python.orgbaidu.com → 域名:baidu.cominvalid-url → 域名:❌ 无效 URL
六、正则实用技巧(避坑 + 效率提升)
- 预编译正则:频繁使用的正则用 re.compile () 预编译,减少重复编译开销
- 原始字符串:正则表达式用 r""包裹,避免反斜杠转义问题(如 r"\d"比"\d" 更安全)
- 非贪婪匹配 :不确定匹配长度时,用 .? 代替 .,避免 "匹配过度"
- 分组命名:复杂场景用 (?P<name>pattern) 命名分组,后续用 group ("name") 提取,更易维护
- 在线调试:遇到复杂正则,用 在线调试(选择 Python 语法)
七、总结
正则表达式的核心是 "用规则匹配文本",本文的 12 个案例覆盖了 80% 的日常开发场景。刚开始学习时可能觉得语法晦涩,但只要多练、多调试,熟悉常用符号(如 ^ $ \d [] () * + ?)的含义,就能快速上手。
如果遇到特定场景(如爬取数据、JSON 解析、复杂日志分析),可以基于本文的思路扩展正则规则,也可以留言交流你的需求,我会补充对应的实战案例!
最后,记住:正则不是越复杂越好,能简洁解决问题的就是好正则~