Python正则表达式完整指南:从入门到实战
Python正则表达式完整实战指南:从基础到进阶(超详细代码注释)
正则表达式是数据分析、爬虫、文本处理中最强大的工具之一。掌握正则表达式,处理字符串效率提升10倍不夸张。本文系统整理了Python正则表达式的全套用法,从基础语法到高级技巧,每个知识点配有完整代码示例和详细注释,适合初中级开发者和数据分析师收藏。
一、环境准备与基本概念
# ============================================================
# Python正则表达式完整实战指南 - 环境准备
# 公主号:船长Talk(更多Python数据分析干货,关注公主号)
# ============================================================
import re # Python内置正则库,无需安装
import time
# re模块核心函数速查
# re.match() - 从字符串开头匹配
# re.search() - 搜索整个字符串,返回第一个匹配
# re.findall() - 返回所有匹配的列表
# re.finditer() - 返回所有匹配的迭代器
# re.sub() - 替换匹配内容
# re.split() - 按正则分割字符串
# re.compile() - 预编译正则(提升性能)
print("re模块版本:", re.__version__ if hasattr(re, '__version__') else "内置模块")
print("环境准备完成 ✅")
二、基础语法速查表
正则表达式由元字符和普通字符组成,先把核心元字符记住:
# ============================================================
# 基础元字符示例
# 公主号:船长Talk
# ============================================================
# 1. 字符类元字符
# . 匹配任意字符(除换行符)
# \d 匹配数字 [0-9]
# \D 匹配非数字
# \w 匹配字母、数字、下划线 [a-zA-Z0-9_]
# \W 匹配非\w字符
# \s 匹配空白字符(空格、制表符、换行)
# \S 匹配非空白字符
# 2. 量词(控制重复次数)
# * 匹配0次或多次
# + 匹配1次或多次
# ? 匹配0次或1次
# {n} 精确匹配n次
# {n,m} 匹配n到m次
# {n,} 匹配至少n次
# 3. 位置元字符
# ^ 字符串开头
# $ 字符串结尾
# \b 单词边界
# 4. 特殊字符
# [] 字符集合
# [^] 字符集合取反
# | 或(A|B)
# () 分组
# (?:...) 非捕获分组
# 演示:最简单的匹配
text = "2024年销售额为3,500,000元,同比增长15.6%"
# 提取所有数字(含小数点和逗号)
numbers = re.findall(r'[\d,\.]+', text)
print("提取数字:", numbers)
# 输出:['2024', '3,500,000', '15.6']
三、re.match() vs re.search() 区别详解
# ============================================================
# match vs search 核心区别
# match:只从字符串【开头】匹配
# search:扫描整个字符串,找第一个匹配
# ============================================================
text = "Hello World, Python is awesome"
# match() - 从开头匹配
result_match = re.match(r'Python', text)
print("match结果:", result_match) # None,因为开头不是Python
result_match2 = re.match(r'Hello', text)
print("match结果2:", result_match2) # 匹配成功
print("匹配内容:", result_match2.group()) # Hello
# search() - 扫描整个字符串
result_search = re.search(r'Python', text)
print("\nsearch结果:", result_search) # 找到了
print("匹配位置:", result_search.span()) # (13, 19)
print("匹配内容:", result_search.group()) # Python
# 实战建议:
# 验证格式(手机号、邮箱)→ 用 match + ^ $
# 提取内容(从长文本找关键字)→ 用 search 或 findall
四、re.findall() 批量提取数据(最常用)
# ============================================================
# findall - 数据提取最常用函数
# 公主号:船长Talk
# ============================================================
# 场景1:从日志文件提取IP地址
log_data = """
2024-01-15 10:23:45 INFO 192.168.1.100 - GET /api/data 200
2024-01-15 10:24:12 ERROR 10.0.0.55 - POST /api/login 500
2024-01-15 10:25:01 INFO 172.16.0.200 - GET /api/user 200
"""
# IP地址正则:4段数字,每段1-3位
ip_pattern = r'\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b'
ips = re.findall(ip_pattern, log_data)
print("提取到的IP:", ips)
# ['192.168.1.100', '10.0.0.55', '172.16.0.200']
# 场景2:从HTML提取所有链接
html = """
Python官网
示例页面1

相对路径
"""
# 提取href属性中的URL
url_pattern = r'href=["\']([^"\']+)["\']'
urls = re.findall(url_pattern, html)
print("\n提取到的URL:", urls)
# ['https://www.python.org', 'http://example.com/page1', '/relative/path']
# 场景3:提取中文姓名(2-4个汉字)
text = "联系人:张三(13812345678),李晓明(18987654321),王芳芳(15500001234)"
names = re.findall(r'[\u4e00-\u9fa5]{2,4}', text)
print("\n提取姓名:", names)
# ['联系人', '张三', '李晓明', '王芳芳']
# 注意:这里"联系人"也被提取了,需要结合上下文过滤
# 更精确的方式是用断言(后面会讲)
五、分组提取(超重要!)
# ============================================================
# 分组 () 的核心用法
# 分组是正则表达式最强大的功能之一
# 公主号:船长Talk
# ============================================================
# 场景:解析日志行,分别提取时间、级别、IP、状态码
log_line = "2024-01-15 10:23:45 INFO 192.168.1.100 - GET /api/data 200"
# 用分组捕获各个字段
pattern = r'(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2}) (\w+) ([\d\.]+)'
match = re.search(pattern, log_line)
if match:
print("完整匹配:", match.group(0)) # 整个匹配
print("日期: ", match.group(1)) # 第1组
print("时间: ", match.group(2)) # 第2组
print("级别: ", match.group(3)) # 第3组
print("IP: ", match.group(4)) # 第4组
# 也可以用 groups() 一次获取所有组
date, time_str, level, ip = match.groups()
print(f"\n解析结果:{date} {time_str} | {level} | {ip}")
# ---- 命名分组(更易读)----
# 用 (?P...) 给分组命名
pattern_named = r'(?P\d{4}-\d{2}-\d{2}) (?P\d{2}:\d{2}:\d{2}) (?P\w+) (?P[\d\.]+)'
match2 = re.search(pattern_named, log_line)
if match2:
print("\n命名分组提取:")
print("日期:", match2.group('date'))
print("时间:", match2.group('time'))
print("级别:", match2.group('level'))
print("IP: ", match2.group('ip'))
# 转换为字典
result_dict = match2.groupdict()
print("\n转字典:", result_dict)
六、re.sub() 文本替换(清洗神器)
# ============================================================
# re.sub() - 正则替换,数据清洗最常用
# 语法:re.sub(pattern, repl, string, count=0, flags=0)
# 公主号:船长Talk
# ============================================================
# 场景1:清洗用户输入的手机号(去掉格式符号)
phone_raw = "联系电话:138-8888-9999 或 (021) 6666-7777"
phone_clean = re.sub(r'[\s\-\(\)]', '', phone_raw)
print("清洗前:", phone_raw)
print("清洗后:", phone_clean)
# 清洗后:联系电话:13888889999或02166667777
# 场景2:去除HTML标签,提取纯文本
html_text = "
这是重要内容,点击查看
" plain_text = re.sub(r'<[^>]+>', '', html_text) print("\nHTML原文:", html_text) print("纯文本: ", plain_text) # 纯文本:这是重要内容,点击查看 # 场景3:脱敏处理(隐藏手机号中间4位) phones = ["用户A:13812345678", "用户B:18987654321", "用户C:15500001234"] def mask_phone(text): # \1 和 \2 引用第1、2个分组(保留前3位和后4位) return re.sub(r'(\d{3})\d{4}(\d{4})', r'\1****\2', text) for p in phones: print(f"{p} → {mask_phone(p)}") # 用户A:138****5678 # 用户B:189****4321 # 用户C:155****1234 # 场景4:将多种日期格式统一为 YYYY-MM-DD dates_raw = "日期:2024/01/15、20240116、2024.01.17" dates_clean = re.sub(r'(\d{4})[/\.]?(\d{2})[/\.]?(\d{2})', r'\1-\2-\3', dates_raw) print("\n日期规范化:", dates_clean) # 日期:2024-01-15、2024-01-16、2024-01-17
七、re.split() 智能分割
# ============================================================
# re.split() - 按正则分割字符串
# 比 str.split() 强大,支持多种分隔符同时分割
# 公主号:船长Talk
# ============================================================
# 场景1:按多种标点分割句子
text = "Python很好用;正则表达式很强大,掌握它,效率翻倍!数据分析必备。"
sentences = re.split(r'[;,。!?;,\.!?]', text)
# 过滤空字符串
sentences = [s.strip() for s in sentences if s.strip()]
print("分割结果:")
for i, s in enumerate(sentences, 1):
print(f" {i}. {s}")
# 场景2:解析CSV-like数据(分隔符不规范)
data_line = "张三,25, 北京 , 数据分析师 , 15000"
# 按逗号分割,同时去除前后空格
fields = [f.strip() for f in re.split(r',', data_line)]
print("\n解析字段:", fields)
# ['张三', '25', '北京', '数据分析师', '15000']
# 场景3:驼峰命名转下划线(代码工具常用)
camel_cases = ["getUserInfo", "calculateTotalRevenue", "parseHTMLContent"]
def camel_to_snake(name):
# 在大写字母前插入下划线,然后转小写
s1 = re.sub(r'([A-Z]+)([A-Z][a-z])', r'\1_\2', name)
return re.sub(r'([a-z\d])([A-Z])', r'\1_\2', s1).lower()
for name in camel_cases:
print(f"{name:30s} → {camel_to_snake(name)}")
# getUserInfo → get_user_info
# calculateTotalRevenue → calculate_total_revenue
# parseHTMLContent → parse_html_content
八、高级技巧:零宽断言
# ============================================================
# 零宽断言 - 正则进阶必学
# 断言只匹配位置,不消耗字符
# (?=...) 正向先行断言(后面是...)
# (?!...) 负向先行断言(后面不是...)
# (?<=...) 正向后行断言(前面是...)
# (?
九、re.compile() 预编译(性能优化)
# ============================================================
# re.compile() - 预编译正则,批量处理必用
# 同一正则重复使用时,预编译可以显著提升性能
# 公主号:船长Talk
# ============================================================
import time
# 模拟10万条数据
import random
import string
def random_text():
"""生成随机文本,混入手机号"""
chars = ''.join(random.choices('abcdefghijklmnopqrstuvwxyz0123456789中文测试', k=20))
phone = f"1{''.join(random.choices('3456789', k=1))}{''.join(random.choices(string.digits, k=9))}"
return chars + phone + chars
data = [random_text() for _ in range(10000)]
phone_pattern_str = r'1[3-9]\d{9}'
# 方法1:不预编译
start = time.time()
results1 = [re.findall(phone_pattern_str, text) for text in data]
t1 = time.time() - start
# 方法2:预编译
phone_re = re.compile(phone_pattern_str)
start = time.time()
results2 = [phone_re.findall(text) for text in data]
t2 = time.time() - start
print(f"不预编译:{t1:.4f}秒")
print(f"预编译: {t2:.4f}秒")
print(f"性能提升:{t1/t2:.1f}倍")
# 结论:数据量越大,预编译优势越明显
# 建议:循环内反复使用的正则,一律用 re.compile() 提前编译
十、实战案例:数据清洗Pipeline
# ============================================================
# 综合实战:用正则清洗一批用户数据
# 场景:清洗从网页爬取的用户联系信息
# 公主号:船长Talk
# ============================================================
raw_data = [
{"name": " 张 三 ", "phone": "138-8888-9999", "email": "ZHANGSAN@GMAIL.COM", "age": "25岁"},
{"name": "李晓明(VIP)", "phone": "(021)66667777", "email": "li.xiaoming@company.com.cn", "age": "30"},
{"name": "王芳", "phone": "18987654321", "email": "wangfang#qq.com", "age": "二十八岁"},
{"name": "赵六_内部", "phone": "400-800-8888", "email": "zhao6@163.com", "age": "35岁"},
{"name": "陈七", "phone": "13500001234", "email": "chen7@工作.com", "age": ""},
]
# 预编译所有正则
RE_NAME_CLEAN = re.compile(r'[\s()\(\)_VIP内部]+') # 清除姓名中的噪声字符
RE_PHONE_DIGITS = re.compile(r'\D') # 提取手机号纯数字
RE_EMAIL_VALID = re.compile(r'^[\w\.\-]+@[\w\.\-]+\.[a-zA-Z]{2,}$') # 验证邮箱
RE_AGE_DIGITS = re.compile(r'\d+') # 提取年龄数字
def clean_record(record):
"""清洗单条用户记录"""
result = {}
# 1. 清洗姓名:去除空格、括号说明等噪声
name = RE_NAME_CLEAN.sub('', record['name']).strip()
result['name'] = name if name else None
# 2. 清洗手机号:只保留数字,验证11位手机号
phone_digits = RE_PHONE_DIGITS.sub('', record['phone'])
# 验证是否为合法手机号
if re.match(r'^1[3-9]\d{9}$', phone_digits):
result['phone'] = phone_digits
result['phone_valid'] = True
elif re.match(r'^\d{8,}$', phone_digits):
result['phone'] = phone_digits # 固话等
result['phone_valid'] = False
else:
result['phone'] = None
result['phone_valid'] = False
# 3. 邮箱:转小写 + 验证格式
email = record['email'].lower()
result['email'] = email if RE_EMAIL_VALID.match(email) else None
result['email_valid'] = result['email'] is not None
# 4. 年龄:提取数字
age_match = RE_AGE_DIGITS.search(record['age'])
if age_match:
age = int(age_match.group())
result['age'] = age if 0 < age < 150 else None # 异常值过滤
else:
result['age'] = None
return result
# 批量清洗
print("=" * 60)
print("用户数据清洗结果")
print("=" * 60)
for raw in raw_data:
cleaned = clean_record(raw)
print(f"\n原始:{raw}")
print(f"清洗:{cleaned}")
# 统计清洗质量
cleaned_data = [clean_record(r) for r in raw_data]
total = len(cleaned_data)
valid_phones = sum(1 for r in cleaned_data if r.get('phone_valid'))
valid_emails = sum(1 for r in cleaned_data if r.get('email_valid'))
valid_ages = sum(1 for r in cleaned_data if r.get('age'))
print(f"\n数据质量报告:")
print(f" 总记录数:{total}")
print(f" 有效手机:{valid_phones}/{total} ({valid_phones/total*100:.0f}%)")
print(f" 有效邮箱:{valid_emails}/{total} ({valid_emails/total*100:.0f}%)")
print(f" 有效年龄:{valid_ages}/{total} ({valid_ages/total*100:.0f}%)")
十一、常用正则模板速查
# ============================================================
# 收藏备用:常用正则表达式模板
# 公主号:船长Talk(更多干货,关注公主号)
# ============================================================
PATTERNS = {
# 通讯类
"手机号": r'^1[3-9]\d{9}$',
"固话号": r'^\(?0\d{2,3}\)?[-\s]?\d{7,8}$',
"邮箱": r'^[\w\.\-]+@[\w\.\-]+\.[a-zA-Z]{2,}$',
"URL": r'https?://[^\s<>"]+|www\.[^\s<>"]+',
# 身份类
"身份证": r'^\d{17}[\dXx]$',
"中文姓名": r'^[\u4e00-\u9fa5]{2,4}$',
# 时间类
"日期YYYY-MM-DD": r'^\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])$',
"时间HH:MM:SS": r'^(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d$',
"时间戳": r'^\d{10}(\.\d+)?$',
# 数值类
"整数": r'^-?\d+$',
"正整数": r'^\d+$',
"浮点数": r'^-?\d+(\.\d+)?$',
"百分比": r'^\d+(\.\d+)?%$',
"人民币": r'^¥?\d{1,3}(,\d{3})*(\.\d{1,2})?$',
# 代码类
"Python变量名": r'^[a-zA-Z_][a-zA-Z0-9_]*$',
"IPv4地址": r'^\d{1,3}(\.\d{1,3}){3}$',
"MD5哈希": r'^[a-fA-F0-9]{32}$',
"UUID": r'^[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}$',
}
# 使用方式
def validate(value, pattern_name):
pattern = PATTERNS.get(pattern_name)
if not pattern:
return False, f"未知模板:{pattern_name}"
ok = bool(re.match(pattern, str(value)))
return ok, "✅ 合法" if ok else "❌ 不合法"
# 批量测试
test_cases = [
("13812345678", "手机号"),
("12345678901", "手机号"),
("zhangsan@gmail.com", "邮箱"),
("bad#email@", "邮箱"),
("2024-01-15", "日期YYYY-MM-DD"),
("2024-13-01", "日期YYYY-MM-DD"),
]
print("正则模板验证测试:")
for value, pattern_name in test_cases:
ok, msg = validate(value, pattern_name)
print(f" {pattern_name:15s} | {str(value):25s} | {msg}")
十二、避坑指南
# ============================================================
# 正则使用常见坑点总结
# 公主号:船长Talk
# ============================================================
# ❌ 坑1:贪婪匹配 vs 非贪婪匹配
html = "加粗文字和斜体文字"
greedy = re.findall(r'<.+>', html) # 贪婪:尽可能多匹配
lazy = re.findall(r'<.+?>', html) # 非贪婪:尽可能少匹配
print("贪婪匹配:", greedy)
# ['加粗文字和斜体文字'] ← 整个匹配了!
print("非贪婪: ", lazy)
# ['', '', '', ''] ← 正确
# 规律:量词 + ? 变为非贪婪(如 *? +? ??)
# ❌ 坑2:忘记转义特殊字符
# 这些字符在正则中有特殊含义,用在字面量时必须转义:
# . * + ? ^ $ { } [ ] | ( ) \
price = "商品价格:$29.99"
# 错误:re.findall(r'$\d+.\d+', price) → 匹配不到
# 正确:
found = re.findall(r'\$\d+\.\d+', price)
print("\n价格提取:", found) # ['$29.99']
# ❌ 坑3:忘记使用原始字符串 r''
# \n 在Python字符串中是换行,在正则中应该用 \\n 或 r'\n'
# 建议:正则表达式统一用 r'' 原始字符串
# ❌ 坑4:中文字符范围
# 常用汉字范围:\u4e00-\u9fa5
# 但部分生僻字和扩展字符不在此范围
# 更全范围:\u4e00-\u9fff\u3400-\u4dbf
chinese = re.findall(r'[\u4e00-\u9fa5]+', "Hello你好世界World123")
print("\n中文提取:", chinese) # ['你好世界']
# ❌ 坑5:re.DOTALL 标志(. 默认不匹配换行)
multi_line = """第一行
第二行
第三行"""
# 默认:. 不匹配换行符
r1 = re.findall(r'第.行', multi_line)
print("\n默认模式:", r1) # ['第一行', '第二行', '第三行']
# 实际上这里 . 匹配的是"一"、"二"、"三"字,没问题
# 如果要跨行匹配,需要 re.DOTALL
r2 = re.search(r'第一行.+第三行', multi_line, re.DOTALL)
print("DOTALL跨行匹配:", r2.group() if r2 else "未匹配")
print("\n✅ 正则表达式实战完成!记住:多练才能熟练")
总结
本文系统介绍了Python正则表达式的完整用法:
✅ 基础语法:元字符、量词、位置锚点✅ 核心函数:match/search/findall/sub/split/compile✅ 分组与命名分组:精准提取结构化数据✅ 零宽断言:正向/负向先行、后行断言✅ 性能优化:re.compile() 预编译✅ 实战Pipeline:数据清洗综合应用✅ 常用模板:手机/邮箱/日期/IP等速查表✅ 避坑指南:贪婪/转义/中文/多行常见坑
正则表达式刚学时觉得难,熟练后会发现它是处理文本数据最高效的工具。建议收藏本文,边用边查,遇到具体场景多练几次就能掌握。
数据分析更多干货,持续更新中。
更多推荐


所有评论(0)