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等速查表
  • 避坑指南:贪婪/转义/中文/多行常见坑

正则表达式刚学时觉得难,熟练后会发现它是处理文本数据最高效的工具。建议收藏本文,边用边查,遇到具体场景多练几次就能掌握。

数据分析更多干货,持续更新中。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐