前言
正则表达式是文本处理的瑞士军刀。无论是验证用户输入、提取网页数据,还是清洗日志文本,正则都能游刃有余。然而很多开发者对正则望而生畏,要么写出一堆难以维护的”神秘符号”,要么遇到问题就百度复制。
本文将帮助您:
- 系统掌握正则的核心语法
- 熟练运用Python re 模块的各种 API
- 积累可直接复用的实战模板
- 避坑常见匹配失败原因
正则基础语法速查
元字符一览
| 元字符 | 说明 | 示例 |
|---|---|---|
. | 任意单个字符(换行除外) | a.c 匹配 “abc” |
\d | 任意数字 [0-9] | \d+ 匹配 “123” |
\D | 任意非数字 [^0-9] | \D+ 匹配 “abc” |
\w | 字母数字下划线 [a-zA-Z0-9_] | \w+ 匹配 “hello_1” |
\W | 非字母数字 [^a-zA-Z0-9_] | \W+ 匹配 “@#” |
\s | 空白字符(空格、Tab、换行) | \s+ 匹配 ” “ |
\S | 非空白字符 | \S+ 匹配 “hello” |
\b | 单词边界 | \bword\b 精确匹配 “word” |
\B | 非单词边界 | \Bword 匹配 “sword” |
^ | 字符串开始 | ^hello 匹配 “hello world” |
$ | 字符串结束 | world$ 匹配 “hello world” |
字符类
# 基本字符类
[abc] # 匹配 a、b 或 c 中的任意一个
[^abc] # 匹配除了 a、b、c 以外任意字符
[a-z] # 匹配小写字母
[A-Z] # 匹配大写字母
[0-9] # 匹配数字
[a-zA-Z0-9] # 匹配字母或数字
# 预定义字符类(更简洁)
\d = [0-9]
\w = [a-zA-Z0-9_]
\s = [ \t\n\r\f\v]
量词
| 量词 | 说明 | 示例 |
|---|---|---|
* | 零个或多个(贪婪) | ab* 匹配 “a”、”ab”、”abbb” |
+ | 一个或多个(贪婪) | ab+ 匹配 “ab”、”abbb”(不匹配 “a”) |
? | 零个或一个 | colou?r 匹配 “color”、”colour” |
{n} | 精确 n 个 | \d{4} 匹配 “2026” |
{n,} | 至少 n 个 | \d{2,} 匹配 “12”、”123″ |
{n,m} | n 到 m 个 | \d{2,4} 匹配 “12” 到 “1234” |
贪婪 vs 非贪婪:
import re
text = "<div>content</div>"
# 贪婪:尽可能多匹配
re.search(r'<.+>', text).group() # 匹配 "<div>content</div>"
# 非贪婪:尽可能少匹配
re.search(r'<.+?>', text).group() # 匹配 "<div>"
捕获组与特殊语法
# () 捕获组:提取特定部分
match = re.search(r'(\d{4})-(\d{2})-(\d{2})', '2026-05-15')
print(match.group(1)) # "2026" — 第一个捕获组
print(match.group(2)) # "05" — 第二个捕获组
print(match.group(3)) # "15" — 第三个捕获组
print(match.groups()) # ('2026', '05', '15') — 全部捕获组
# 命名捕获组(Python 3.6+)
match = re.search(r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})', '2026-05-15')
print(match.group('year')) # "2026"
print(match.groupdict()) # {'year': '2026', 'month': '05', 'day': '15'}
# (?:...) 非捕获组:不创建捕获组(性能更好)
re.findall(r'(?:\d+\.)+\d+', '版本 1.2.3') # ['1.2.3']
# (?=...) 正向先行断言:后面跟着...
re.findall(r'\w+(?=\s+USD)', '100 USD, 200 EUR') # ['100']
# (?!...) 负向先行断言:后面不跟着...
re.findall(r'\d+(?!\s+USD)', '100 USD, 200') # ['200']
# (?<=...) 正向后行断言:前面是...
re.findall(r'(?<=\$)\d+', '$100, €200') # ['100']
# (?<!...) 负向后行断言:前面不是...
re.findall(r'(?<!\$)\d+', '$100, 200') # ['100', '20'] ⚠️ 匹配所有数字
转义字符
# 需要转义的字符:. ^ $ * + ? { } [ ] \ | ( )
# 在字符类 [] 中只需转义:] \ ^ -
# 匹配字面点号
re.search(r'\.', 'abc.def') # 匹配 "."
# 匹配 IP 地址格式(转义点号)
re.search(r'\d+\.\d+\.\d+\.\d+', '192.168.1.1')
# re.escape() 自动转义
pattern = re.escape('[test]') # r'\[test\]'
re.findall(pattern, '[test] string')
Python re 模块详解
六大核心函数
| 函数 | 说明 | 返回值 |
|---|---|---|
re.match() | 从字符串开头匹配 | 匹配对象/None |
re.search() | 扫描整个字符串,首个匹配 | 匹配对象/None |
re.findall() | 扫描整个字符串,所有匹配 | 字符串列表 |
re.finditer() | 扫描整个字符串,所有匹配 | 迭代器 |
re.sub() | 替换匹配项 | 新字符串 |
re.split() | 按匹配项分割 | 字符串列表 |
import re
text = "我的邮箱是 test@example.com,备用邮箱 backup@test.org"
# 1. match:从开头匹配
result = re.match(r'我的', text)
print(result.group()) # "我的"
result = re.match(r'邮箱', text) # None — 不从开头
# 2. search:扫描首个匹配
result = re.search(r'\w+@\w+\.\w+', text)
print(result.group()) # "test@example.com"
# 3. findall:返回所有匹配
emails = re.findall(r'\w+@\w+\.\w+', text)
print(emails) # ['test@example.com', 'backup@test.org']
# 4. finditer:迭代器,适合大文本
for match in re.finditer(r'\w+@\w+\.\w+', text):
print(f"位置 {match.start()}-{match.end()}: {match.group()}")
# 5. sub:替换
new_text = re.sub(r'\w+@\w+\.\w+', '[已隐藏]', text)
print(new_text) # "我的邮箱是 [已隐藏],备用邮箱 [已隐藏]"
# 6. split:分割
parts = re.split(r'[,,]', "苹果,香蕉,橙子")
print(parts) # ['苹果', '香蕉', '橙子']
match 对象详解
text = "2026-05-15 晴朗"
# search 返回 match 对象
match = re.search(r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})', text)
# 常用方法
print(match.group(0)) # "2026-05-15" — 整个匹配
print(match.group(1)) # "2026" — 第一个捕获组
print(match.group('year')) # "2026" — 命名捕获组
print(match.groups()) # ('2026', '05', '15') — 所有捕获组
print(match.groupdict()) # {'year': '2026', 'month': '05', 'day': '15'}
# 位置信息
print(match.start()) # 0 — 匹配开始位置
print(match.end()) # 10 — 匹配结束位置
print(match.span()) # (0, 10) — (开始, 结束)
# 搜索对象
print(match.pos) # 搜索起始位置
print(match.lastindex) # 最后一个捕获组的索引
编译与 flags
# 编译正则表达式(重复使用时更高效)
pattern = re.compile(r'\d{4}-\d{2}-\d{2}')
# 编译后可调用
pattern.findall('2026-05-15 天气晴')
pattern.search('今天 2026-05-15')
pattern.match('2026-05-15') # 从头匹配
# flags 参数
text = "Hello\nWorld"
# re.IGNORECASE / re.I — 忽略大小写
re.findall(r'hello', text, re.I) # ['Hello']
# re.MULTILINE / re.M — ^ 和 $ 匹配行首行尾
text = "第一行\n第二行"
re.findall(r'^\w+', text, re.M) # ['第一行', '第二行']
# re.DOTALL / re.S — . 匹配换行
re.findall(r'.+', text, re.S) # ['第一行\n第二行']
# re.VERBOSE / re.X — 允许注释
pattern = re.compile(r'''
\d{4} # 年份
- # 分隔符
\d{2} # 月份
- # 分隔符
\d{2} # 日期
''', re.VERBOSE)
# 组合使用
re.search(r'hello', text, re.I | re.M)
# 内联 flags(只影响当前组)
re.findall(r'(?i)hello', text) # 忽略大小写
re.findall(r'(?x)\d+ \w+', text) # 允许空格和注释
常用模式实战
1. 邮箱验证
import re
def validate_email(email):
"""验证邮箱格式"""
pattern = r'''
^ # 字符串开始
[a-zA-Z0-9._%+-]+ # 用户名部分
@ # @ 符号
[a-zA-Z0-9.-]+ # 域名部分
\.[a-zA-Z]{2,}$ # 顶级域名
'''
return bool(re.fullmatch(pattern, email, re.VERBOSE))
# 测试
emails = [
"test@example.com",
"user.name+tag@domain.co.uk",
"invalid.email",
"@nodomain.com",
"spaces in@email.com"
]
for email in emails:
result = validate_email(email)
print(f"{email:30} -> {'✅ 有效' if result else '❌ 无效'}")
2. 手机号验证
import re
def validate_phone(phone):
"""验证中国手机号"""
pattern = r'^1[3-9]\d{9}$'
return bool(re.fullmatch(pattern, phone))
def format_phone(phone):
"""格式化手机号:13812345678 -> 138-1234-5678"""
match = re.match(r'1[3-9](\d{3})(\d{4})(\d{4})', phone)
if match:
return f"{match.group(1)}-{match.group(2)}-{match.group(3)}"
return phone
# 测试
phones = ["13812345678", "19912345678", "12345678901", "138123456789"]
for phone in phones:
print(f"{phone:15} 验证: {validate_phone(phone)} | 格式化: {format_phone(phone)}")
3. URL 提取与解析
import re
from urllib.parse import urlparse
def extract_urls(text):
"""提取文本中所有 URL"""
pattern = r'https?://[^\s<>"{}|\\^`\[\]]+'
return re.findall(pattern, text)
def parse_url(url):
"""解析 URL 各部分"""
match = re.match(
r'(?P<scheme>https?)://'
r'(?:(?P<user>\w+):(?P<password>\w+)@)?'
r'(?P<host>[^/:]+)'
r'(?::(?P<port>\d+))?'
r'(?P<path>/[^\s]*)?',
url
)
if match:
return match.groupdict()
return {}
# 使用
text = "访问 https://user:pass@example.com:8080/api/v1/users?id=123"
urls = extract_urls(text)
print(f"提取到 URL: {urls}")
parsed = parse_url(urls[0])
print(f"解析结果: {parsed}")
4. HTML 标签内容提取
import re
def strip_html(html):
"""移除所有 HTML 标签"""
return re.sub(r'<[^>]+>', '', html)
def extract_links(html):
"""提取所有链接和文本"""
pattern = r'<a[^>]+href=["\']([^"\']+)["\'][^>]*>([^<]*)</a>'
return re.findall(pattern, html)
def extract_img_srcs(html):
"""提取所有图片 src"""
pattern = r'<img[^>]+src=["\']([^"\']+)["\'][^>]*>'
return re.findall(pattern, html)
# 示例
html = '''
<a href="https://example.com">示例链接</a>
<img src="https://example.com/img.png" alt="图片" />
<a href="https://example.org">另一个链接</a>
'''
print(f"链接: {extract_links(html)}")
print(f"图片: {extract_img_srcs(html)}")
print(f"纯文本: {strip_html(html)}")
5. 日志解析
import re
from datetime import datetime
from collections import Counter
LOG_PATTERN = re.compile(
r'(?P<timestamp>\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s+'
r'\[(?P<level>\w+)\]\s+'
r'(?P<logger>[\w.]+)\s+-\s+'
r'(?P<message>.+)'
)
def parse_log_line(line):
"""解析单条日志"""
match = LOG_PATTERN.match(line)
if match:
data = match.groupdict()
# 转换时间戳
data['time'] = datetime.strptime(data['timestamp'], '%Y-%m-%d %H:%M:%S')
return data
return None
def analyze_logs(log_content):
"""分析日志统计"""
logs = []
for line in log_content.strip().split('\n'):
parsed = parse_log_line(line)
if parsed:
logs.append(parsed)
# 统计各级别日志数量
levels = Counter(log['level'] for log in logs)
# 统计各 logger 日志数量
loggers = Counter(log['logger'] for log in logs)
return {
'total': len(logs),
'levels': dict(levels),
'loggers': dict(loggers.most_common(5))
}
# 示例日志
sample_log = """
2026-05-15 10:30:00 [INFO] app.server - 服务器启动
2026-05-15 10:30:01 [DEBUG] app.db - 连接数据库
2026-05-15 10:30:05 [ERROR] app.api - 请求处理失败
2026-05-15 10:30:06 [INFO] app.server - 收到新请求
"""
result = analyze_logs(sample_log)
print(f"日志统计: {result}")
6. 数据清洗
import re
def clean_text(text):
"""文本清洗"""
# 移除多余空格
text = re.sub(r'\s+', ' ', text)
# 移除特殊控制字符
text = re.sub(r'[\x00-\x1f\x7f-\x9f]', '', text)
# 规范化引号
text = re.sub(r'[""]', '"', text)
text = re.sub(r'['']', "'", text)
# 移除多余标点
text = re.sub(r'\.{2,}', '.', text)
return text.strip()
def extract_numbers(text):
"""提取所有数字(整数或浮点数)"""
pattern = r'-?\d+\.?\d*'
return [float(x) for x in re.findall(pattern, text)]
def remove_emoji(text):
"""移除 Emoji"""
emoji_pattern = re.compile(
"["
"\U0001F600-\U0001F64F" # emoticons
"\U0001F300-\U0001F5FF" # symbols & pictographs
"\U0001F680-\U0001F6FF" # transport & map symbols
"\U0001F1E0-\U0001F1FF" # flags
"\U00002702-\U000027B0"
"\U000024C2-\U0001F251"
"]+", flags=re.UNICODE
)
return emoji_pattern.sub(r'', text)
# 测试
dirty_text = ' 文本 中间 有 多余 空格 '
print(f"清洗后: '{clean_text(dirty_text)}'") # '文本中间有多余空格'
print(f"提取数字: {extract_numbers('价格: 19.9元, 数量: 5件')}") # [19.9, 5]
print(f"移除Emoji: {remove_emoji('Hello 👋 World 🌍!')}") # 'Hello World !'
高级技巧
1. 前瞻后顾(环视)
import re
text = "apple apple banana apple"
# 正向后顾 (?<=...):前面必须是...
pattern = r'(?<=\bapple\s)banana' # apple 后面跟着的 banana
print(re.findall(pattern, text)) # ['banana']
# 负向后顾 (?<!...):前面不能是...
pattern = r'(?<!red\s)apple' # red 后面跟着的 apple
print(re.findall(pattern, text)) # ['apple', 'apple'](第一个 red apple 被排除)
# 正向前瞻 (?=...)
pattern = r'\w+(?=\s+banana)' # banana 前面的单词
print(re.findall(pattern, text)) # ['apple']
# 负向前瞻 (?!...)
pattern = r'\w+(?!\s+banana)' # 不是 banana 前面的单词
print(re.findall(pattern, text)) # ['apple', 'apple', 'apple']
2. 条件匹配
import re
# (?(id)yes|no) 条件匹配:如果指定组匹配则用 yes,否则用 no
# 示例:匹配带引号的字符串
text = '"hello" and "world" and unquoted'
# 捕获引号
pattern = r'("(?:[^"\\]|\\.)*"|\'(?:[^\'\\]|\\.)*\')'
print(re.findall(pattern, text)) # ['"hello"', '"world"']
3. 递归与平衡组
import re
# 匹配配对符号(如 HTML 标签)
# 注意:正则不适合真正解析 HTML,这里仅作演示
def match_balanced(text, open_char='(', close_char=')'):
"""匹配配对括号内容"""
pattern = rf'''
{re.escape(open_char)} # 开始符号
(?: # 非捕获组
[^(){open_char}{close_char}] # 普通字符
| # 或
(?P<recursion>{re.escape(open_char)}...{re.escape(close_char)}) # 递归
)*
{re.escape(close_char)} # 结束符号
'''
return re.findall(pattern, text, re.VERBOSE)
# 示例
print(match_balanced('(a + (b - c))')) # ['(a + (b - c))']
4. 替换的高级用法
import re
text = "用户: 张三, 年龄: 25, 城市: 北京"
# 替换函数
def normalize_age(match):
age = int(match.group(1))
return f"{age}岁" if age < 100 else f"{age}岁(异常)"
result = re.sub(r'年龄:\s*(\d+)', normalize_age, text)
print(result) # "用户: 张三, 年龄: 25岁, 城市: 北京"
# 使用命名组引用
def swap_name(match):
return f"{match.group('last')}{match.group('first')}"
pattern = r'名字:(?P<first>\w+)(?P<last>\w+)'
text = "名字: 大明"
print(re.sub(pattern, swap_name, text)) # "大明"
# 反向引用(在替换中使用捕获组)
text = "<b>加粗</b> 和 <i>斜体</i>"
# 将 HTML 标签转换为 Markdown
result = re.sub(r'<(\w+)>(.*?)</\1>', r'**\2**', text)
print(result) # "**加粗** 和 **斜体**"
5. split 高级用法
import re
# 按多个分隔符分割
text = "苹果,香蕉;橙子|葡萄-草莓"
result = re.split(r'[,;|\-]', text)
print(result) # ['苹果', '香蕉', '橙子', '葡萄', '草莓']
# 保留分隔符
result = re.split(r'(?<=[,;|])(?=\S)', text)
print(result)
# 按数字分组分割
text = "用户1年龄30用户2年龄25用户3年龄40"
result = re.split(r'(?<=\D)(?=\d)|(?<=\d)(?=\D)', text)
print(result) # ['用户', '1', '年龄', '30', '用户', '2', '年龄', '25', ...]
# 使用捕获组保留分隔符
text = "a,b,c"
result = re.split(r'(,)', text)
print(result) # ['a', ',', 'b', ',', 'c']
常见场景模板
模板1:密码强度验证
import re
def validate_password(password):
"""验证密码强度"""
errors = []
if len(password) < 8:
errors.append("密码长度至少8位")
if not re.search(r'[A-Z]', password):
errors.append("必须包含大写字母")
if not re.search(r'[a-z]', password):
errors.append("必须包含小写字母")
if not re.search(r'\d', password):
errors.append("必须包含数字")
if not re.search(r'[!@#$%^&*(),.?":{}|<>]', password):
errors.append("必须包含特殊字符")
return {
'valid': len(errors) == 0,
'errors': errors
}
# 测试
test_passwords = ["Abc123!", "weak", "NoSpecial1", "Valid@Pass123"]
for pwd in test_passwords:
result = validate_password(pwd)
status = "✅" if result['valid'] else "❌"
print(f"{status} {pwd:20} -> {result['errors'] if result['errors'] else '通过'}")
模板2:身份证号验证
import re
def validate_chinese_id(id_number):
"""验证中国身份证号"""
# 基本格式检查
if not re.fullmatch(r'[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]', id_number):
return False
# 校验位验证
weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]
check_codes = '10X98765432'
total = sum(int(id_number[i]) * weights[i] for i in range(17))
check_code = check_codes[total % 11]
return check_code == id_number[17].upper()
# 测试
ids = ["11010119900101123X", "11010119900101123x", "invalid", "000000000000000000"]
for id_num in ids:
print(f"{id_num} -> {validate_chinese_id(id_num)}")
模板3:IP 地址验证与排序
import re
def validate_ip(ip):
"""验证 IPv4 地址"""
pattern = r'^(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)$'
return bool(re.fullmatch(pattern, ip))
def sort_ip_addresses(ips):
"""按 IP 地址数值排序"""
def ip_to_int(ip):
parts = re.findall(r'\d+', ip)
return sum(int(p) * (256 ** (3 - i)) for i, p in enumerate(parts))
return sorted(ips, key=ip_to_int)
# 测试
test_ips = ["192.168.1.1", "10.0.0.1", "192.168.1.10", "172.16.0.1", "0.0.0.0"]
valid_ips = [ip for ip in test_ips if validate_ip(ip)]
print(f"有效 IP: {valid_ips}")
print(f"排序后: {sort_ip_addresses(valid_ips)}")
模板4:Markdown 链接提取
import re
def extract_markdown_links(text):
"""提取 Markdown 链接"""
pattern = r'\[([^\]]+)\]\(([^\)]+)\)'
matches = re.findall(pattern, text)
return [{'text': m[0], 'url': m[1]} for m in matches]
def markdown_to_html(text):
"""Markdown 链接转 HTML"""
def replace_link(match):
text, url = match.groups()
return f'<a href="{url}" target="_blank">{text}</a>'
return re.sub(r'\[([^\]]+)\]\(([^\)]+)\)', replace_link, text)
# 示例
md_text = '''
欢迎访问 [Python官网](https://python.org)!
另一个 [链接](https://example.com) 在这里。
'''
print("链接列表:")
for link in extract_markdown_links(md_text):
print(f" - {link}")
print("\n转换为 HTML:")
print(markdown_to_html(md_text))
性能优化
1. 预编译正则
import re
import time
# ❌ 循环中重复编译(慢)
start = time.time()
for _ in range(10000):
re.search(r'\d{4}-\d{2}-\d{2}', '2026-05-15')
print(f"重复编译: {time.time() - start:.4f}s")
# ✅ 预编译(快)
pattern = re.compile(r'\d{4}-\d{2}-\d{2}')
start = time.time()
for _ in range(10000):
pattern.search('2026-05-15')
print(f"预编译: {time.time() - start:.4f}s")
2. 避免回溯灾难
import re
import time
# ❌ 回溯灾难:可能导致灾难性回溯
bad_pattern = re.compile(r'(a+)+b') # 对 "aaa...aa" 无 'b' 时极慢
test_str = 'a' * 30
start = time.time()
try:
bad_pattern.match(test_str)
except:
pass
print(f"有回溯问题的正则: {time.time() - start:.4f}s")
# ✅ 优化:使用原子组或更精确的模式
good_pattern = re.compile(r'a+b') # 简单直接
start = time.time()
good_pattern.match(test_str)
print(f"优化后: {time.time() - start:.4f}s")
3. 选择更高效的匹配方式
import re
text = "a" * 10000 + "b"
# ❌ findall 扫描整个字符串
start = time.time()
re.findall(r'a*b', text)
print(f"findall: {time.time() - start:.6f}s")
# ✅ search 找到即停
start = time.time()
re.search(r'a*b', text)
print(f"search: {time.time() - start:.6f}s")
# ✅ match 只检查开头(更快)
start = time.time()
re.match(r'a+b', text) # 不需要,检查开头是 'a' 开头不匹配
print(f"match: {time.time() - start:.6f}s")
4. 使用恰当的字符类
# ❌ 慢
re.findall(r'(?:a|b|c|d|e|f|g)', text)
# ✅ 快
re.findall(r'[a-g]', text)
# ❌ 慢:点号通配
re.findall(r'.+', text)
# ✅ 快:排除更具体
re.findall(r'[^x]+', text)
排错指南
常见错误与解决方案
错误1:匹配结果为空
import re
text = "Hello World"
# ❌ 问题1:大小写不匹配
result = re.findall(r'hello', text) # []
# ✅ 解决:添加 re.I 或 (?i)
result = re.findall(r'(?i)hello', text) # ['Hello']
# ❌ 问题2:没有使用原始字符串
result = re.findall('\n', text) # \n 被解释为换行符
# ✅ 解决:使用 r'' 或双反斜杠
result = re.findall(r'\n', text) # []
result = re.findall('\\n', text) # []
错误2:只匹配部分内容
text = "价格: 100元, 重量: 200克"
# ❌ 只获取到第一个匹配
result = re.search(r'\d+', text).group() # "100"
# ✅ 使用 findall 获取所有
result = re.findall(r'\d+', text) # ['100', '200']
# ❌ search 只返回第一个
# ✅ 需要全部匹配时用 findall
错误3:非贪婪匹配问题
text = "<div>content1</div><div>content2</div>"
# ❌ 非贪婪 .+? 可能匹配错误
result = re.search(r'<div>.+?</div>', text).group() # "<div>content1</div>"
# ✅ 正确
text = "<div><span>content</span></div>"
# ❌ .+? 会停在第一个 </div>
result = re.search(r'<div>.+?</div>', text).group() # "<div><span>content</span></div>"
# ✅ 正确,因为非贪婪会匹配最短
# 如果 HTML 结构复杂,考虑:
# 1. 使用 BeautifulSoup 等专用解析器
# 2. 使用更精确的模式
pattern = r'<div>(?:(?!<div>|</div>).)*</div>'
错误4:转义字符问题
# ❌ 在字符类 [] 中错误转义
result = re.findall(r'[abc\]d]', 'abcde') # [] 中 ] 需要转义
# 结果: ['a', 'b', 'c', 'd'] — 可能不是预期
# ✅ 正确转义
result = re.findall(r'[abc\]d]', 'abcde') # 匹配 a, b, c, ], d
result = re.findall(r'[a-c]d', 'ade bde cde') # 匹配 ad, bd, cd
# ❌ ^ 在字符类中位置错误
result = re.findall(r'[^abc]', 'abc123') # ^ 在开头表示否定
# ✅ ^ 不在开头则字面匹配
result = re.findall(r'[a^bc]', 'abc^') # 匹配 a, b, c, ^
错误5:正则性能问题
import re
import time
# 模拟长文本
text = "abc" * 10000 + "abcx"
# ❌ 低效模式
start = time.time()
pattern = re.compile(r'(abc)+x')
pattern.match(text)
print(f"低效模式: {time.time() - start:.6f}s")
# ✅ 高效模式
start = time.time()
pattern = re.compile(r'abc+x')
pattern.match(text)
print(f"高效模式: {time.time() - start:.6f}s")
调试技巧
import re
# 1. 打印匹配详情
def debug_regex(pattern, text):
"""调试正则表达式"""
match = re.search(pattern, text)
if match:
print(f"✅ 匹配成功: '{match.group()}'")
print(f" 位置: {match.span()}")
if match.groups():
print(f" 捕获组: {match.groups()}")
if match.groupdict():
print(f" 命名组: {match.groupdict()}")
else:
print(f"❌ 匹配失败")
print(f" 尝试模式: {pattern}")
print(f" 输入文本: '{text}'")
# 使用
debug_regex(r'(\w+)@(\w+)\.(\w+)', 'test@example.com')
# 2. 分步构建正则
parts = {
'username': r'[a-zA-Z0-9._%+-]+',
'domain': r'[a-zA-Z0-9.-]+',
'tld': r'\.[a-zA-Z]{2,}'
}
email_pattern = rf"^{parts['username']}@{parts['domain']}{parts['tld']}$"
print(f"构建的正则: {email_pattern}")
# 3. 使用 regex101.com 验证
# 在浏览器中可视化调试正则表达式
总结
核心要点
- 优先使用原始字符串
r'':避免转义地狱 - 预编译
re.compile():重复匹配时性能提升显著 - 选择合适的函数:
match/search/findall各有用途 - 注意贪婪 vs 非贪婪:
.+?vs.+ - 字符类替代点号:
[abc]+优于.+ - 环视不断言:
(?=...)不消耗字符
速查表
┌─────────────────────────────────────────────────────────────┐
│ 正则表达式速查表 │
├──────────────────┬──────────────────────────────────────────┤
│ 场景 │ 模式 │
├──────────────────┼──────────────────────────────────────────┤
│ 邮箱 │ r'^[\w.-]+@[\w.-]+\.\w{2,}$' │
│ 手机号 │ r'^1[3-9]\d{9}$' │
│ URL │ r'https?://\S+' │
│ IP 地址 │ r'\d{1,3}(?:\.\d{1,3}){3}' │
│ 日期 YYYY-MM-DD │ r'\d{4}-\d{2}-\d{2}' │
│ 时间 HH:MM:SS │ r'\d{2}:\d{2}:\d{2}' │
│ HTML 标签 │ r'<[^>]+>' │
│ 中文字符 │ r'[\u4e00-\u9fa5]+' │
│ 纯数字 │ r'^\d+$' │
│ 空白行 │ r'^\s*$' │
│ 捕获组引用 │ r'(\w+)=\1' — 重复单词 │
└──────────────────┴──────────────────────────────────────────┘
推荐工具
| 工具 | 用途 |
|---|---|
| regex101.com | 可视化正则测试,支持 Python |
| regexr.com | 正则学习与测试 |
| Pyrexp | Chrome 正则调试插件 |
| The Regex Coach | 桌面正则工具 |
💡 提示: 正则虽强大,但并非所有场景都适用。解析复杂格式(如 HTML、JSON)建议使用专用解析库。 📚 扩展阅读:
原创内容,版权所有。未经授权,禁止转载。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END














暂无评论内容