Python re 模块详解
·
Python re 模块详解 - 正则表达式
核心概念
模式字符串(Pattern) → 匹配 → 目标字符串(String)
常用元字符速查
| 符号 | 含义 | 示例 |
|---|---|---|
. |
任意字符(除换行) | a.c → abc |
* |
0个或多个 | ab* → a, ab, abb |
+ |
1个或多个 | ab+ → ab, abb |
? |
0个或1个 | ab? → a, ab |
^ |
开头 | ^hello |
$ |
结尾 | world$ |
\d |
数字 | \d+ → 123 |
\w |
字母/数字/下划线 | \w+ |
\s |
空白字符 | \s+ |
[] |
字符集合 | [a-z] |
() |
分组捕获 | (ab)+ |
{n,m} |
重复次数 | \d{3,5} |
1.re.compile() - 编译正则表达式
功能: 将正则字符串编译成 Pattern对象,复用时性能更高
返回: re.Pattern对象
import re
# 直接用字符串(每次都重新编译,效率低)
re.findall(r'\d+', '价格123元')
# compile 预编译(推荐!大量使用时效率高)
pattern = re.compile(r'\d+')
print(type(pattern)) # <class 're.Pattern'>
# 编译后可复用
pattern.findall('价格123元')
pattern.findall('数量456个')
pattern.findall('编号789号')
# 常用 flags 参数
pattern = re.compile(r'hello', re.IGNORECASE) # 忽略大小写
pattern = re.compile(r'hello', re.MULTILINE) # 多行模式
pattern = re.compile(r'hello', re.DOTALL) # . 匹配换行符
2. re.match() - 从开头匹配
功能: 只从字符串开头开始匹配,不是开头则失败
返回: 匹配成功 → ``Match对象,失败 → None
import re
# ✅ 从开头能匹配
result = re.match(r'\d+', '123abc')
print(result) # <re.Match object; span=(0, 3), match='123'>
print(result.group()) # '123' → 匹配到的字符串
print(result.span()) # (0, 3) → 起止位置
print(result.start()) # 0
print(result.end()) # 3
# ❌ 不是从开头,返回 None
result = re.match(r'\d+', 'abc123')
print(result) # None
# 安全写法(避免 None 报错)
if result := re.match(r'\d+', '123abc'):
print(result.group())
3. re.search() - 全文搜索第一个
功能: 扫描整个字符串,找到第一个匹配项
返回: 匹配成功 → Match对象,失败 → None
import re
# 不要求从开头,全文搜索
result = re.search(r'\d+', 'abc123def456')
print(result.group()) # '123' → 只返回第一个!
print(result.span()) # (3, 6)
# match vs search 区别
re.match(r'\d+', 'abc123') # None(不是开头)
re.search(r'\d+', 'abc123') # '123'(全文搜索到了)
# 分组捕获
result = re.search(r'(\w+)@(\w+)\.(\w+)', 'user@gmail.com')
print(result.group()) # 'user@gmail.com' → 整体
print(result.group(1)) # 'user' → 第1组
print(result.group(2)) # 'gmail' → 第2组
print(result.group(3)) # 'com' → 第3组
print(result.groups()) # ('user', 'gmail', 'com') → 所有分组
4. re.findall() - 找所有匹配项 ⭐ 最常用
功能: 返回所有匹配结果
返回: 列表 list(无匹配返回空列表)
import re
# 基础用法
result = re.findall(r'\d+', 'abc123def456ghi789')
print(result) # ['123', '456', '789']
# 无匹配返回空列表
result = re.findall(r'\d+', 'abcdef')
print(result) # []
# ⚠️ 有分组时,返回分组内容
text = '张三:18, 李四:25, 王五:30'
# 无分组 → 返回整体匹配
re.findall(r'\w+:\d+', text)
# ['张三:18', '李四:25', '王五:30']
# 有1个分组 → 返回分组内容
re.findall(r'\w+:(\d+)', text)
# ['18', '25', '30']
# 有多个分组 → 返回元组列表
re.findall(r'(\w+):(\d+)', text)
# [('张三', '18'), ('李四', '25'), ('王五', '30')]
5. re.finditer() - 找所有匹配项(迭代器版)
功能: 和 findall 类似,但返回迭代器,节省内存
返回: 迭代器,每个元素是 Match对象
import re
# 大数据量时用 finditer 更省内存!
text = 'abc123def456ghi789'
for match in re.finditer(r'\d+', text):
print(match.group(), match.span())
# 123 (3, 6)
# 456 (9, 12)
# 789 (15, 18)
# findall vs finditer
re.findall(r'\d+', text) # 一次性返回所有结果 → 占内存
re.finditer(r'\d+', text) # 懒加载,逐个取 → 省内存 ✅
6. re.sub() - 替换 ⭐ 清洗数据必备
功能: 将匹配到的内容替换成指定字符串
返回: 替换后的新字符串
import re
# 基础替换
result = re.sub(r'\d+', 'NUM', 'abc123def456')
print(result) # 'abcNUMdefNUM'
# 清洗 HTML 标签
html = '<p>你好<br/>世界</p>'
result = re.sub(r'<.*?>', '', html)
print(result) # '你好世界'
# 清洗特殊字符(数据清洗常用)
text = 'Hello!!! World@@@ 2024###'
result = re.sub(r'[^\w\s]', '', text) # 只保留字母数字空格
print(result) # 'Hello World 2024'
# count 参数:限制替换次数
result = re.sub(r'\d+', 'NUM', 'a1b2c3', count=2)
print(result) # 'aNUMbNUMc3' → 只替换前2个
# 用函数做替换(动态替换)
def double(match):
return str(int(match.group()) * 2)
result = re.sub(r'\d+', double, '价格10元, 数量5个')
print(result) # '价格20元, 数量10个'
7. re.split() - 按正则分割
功能: 按匹配到的模式分割字符串
返回: 列表 list
import re
# 按多种分隔符分割(数据清洗超常用)
text = '苹果,香蕉;葡萄 西瓜|橙子'
result = re.split(r'[,;\s|]+', text)
print(result) # ['苹果', '香蕉', '葡萄', '西瓜', '橙子']
# 普通 split 做不到多种分隔符!
'苹果,香蕉;葡萄'.split(',') # ['苹果', '香蕉;葡萄'] ← 只分了逗号
# 保留分隔符(加括号分组)
result = re.split(r'([,;])', '苹果,香蕉;葡萄')
print(result) # ['苹果', ',', '香蕉', ';', '葡萄']
8. re.fullmatch() - 完整匹配
功能: 整个字符串必须完全符合模式
返回: Match对象 或 None
import re
# 表单验证常用场景
# 验证手机号
re.fullmatch(r'1[3-9]\d{9}', '13812345678') # ✅ Match对象
re.fullmatch(r'1[3-9]\d{9}', '138123456789') # ❌ None(多一位)
# 验证邮箱
re.fullmatch(r'\w+@\w+\.\w+', 'user@gmail.com') # ✅
# 验证日期格式
re.fullmatch(r'\d{4}-\d{2}-\d{2}', '2024-01-15') # ✅
函数返回值汇总
| 函数 | 返回值 | 适用场景 |
|---|---|---|
compile() |
Pattern对象 |
预编译复用 |
match() |
Match对象 / None |
验证开头格式 |
search() |
Match对象 / None |
找第一个匹配 |
findall() |
列表 |
提取所有匹配 |
finditer() |
迭代器 |
大数据量提取 |
sub() |
新字符串 |
替换清洗 |
split() |
列表 |
多规则分割 |
fullmatch() |
Match对象 / None |
表单验证 |
实战:数据清洗综合示例
import re
# 原始脏数据
raw_data = [
" 张三 | 年龄:28 | 电话:138-1234-5678 | 邮箱:zhangsan@qq.com ",
" 李四 | 年龄:32 | 电话:139-8765-4321 | 邮箱:lisi@163.com ",
]
pattern_phone = re.compile(r'(\d{3})-(\d{4})-(\d{4})')
pattern_email = re.compile(r'[\w.]+@[\w.]+')
pattern_age = re.compile(r'年龄:(\d+)')
for row in raw_data:
# 1. 去除首尾空格
row = row.strip()
# 2. 提取姓名(第一个字段)
name = row.split('|')[0].strip()
# 3. 提取年龄
age = pattern_age.search(row).group(1)
# 4. 提取并格式化电话
phone = pattern_phone.sub(r'\1\2\3', row) # 去掉横线
phone = pattern_phone.search(row).group()
# 5. 提取邮箱
email = pattern_email.search(row).group()
print(f"姓名:{name} 年龄:{age} 电话:{phone} 邮箱:{email}")
# 输出:
# 姓名:张三 年龄:28 电话:138-1234-5678 邮箱:zhangsan@qq.com
# 姓名:李四 年龄:32 电话:139-8765-4321 邮箱:lisi@163.com
正则表达式语法详解 & 高级用法
之前的部分
r'[,;\s|]+'
# [] → 字符集合,匹配里面任意一个字符
# , → 逗号
# ; → 分号
# \s → 任意空白(空格/Tab/换行)
# | → 竖线(在[]内就是普通字符,不是"或")
# + → 前面的集合出现 1次或多次
# 整体:匹配 一个或多个 [逗号/分号/空白/竖线]
r'([,;])'
# () → 捕获分组,把匹配内容"存起来"
# [,;] → 匹配逗号或分号
# 整体:匹配并捕获 逗号或分号
r'1[3-9]\d{9}'
# 1 → 字面量 1
# [3-9] → 3到9中任意一个数字
# \d → 任意数字 0-9
# {9} → 前面的\d 重复精确9次
# 整体:1开头 + 第二位3-9 + 后面9位数字 = 11位手机号
r'\w+@\w+\.\w+'
# \w+ → 1个或多个 字母/数字/下划线
# @ → 字面量 @
# \w+ → 域名主体
# \. → 字面量 .(必须转义,否则.代表任意字符)
# \w+ → 顶级域名 com/cn/org
# 整体:简单邮箱格式
r'\d{4}-\d{2}-\d{2}'
# \d{4} → 精确4位数字(年)
# - → 字面量 -
# \d{2} → 精确2位数字(月)
# - → 字面量 -
# \d{2} → 精确2位数字(日)
# 整体:yyyy-MM-dd 日期格式
r'(\w+)@(\w+)\.(\w+)'
# (\w+) → 第1组:用户名
# @ → 字面量 @
# (\w+) → 第2组:域名主体
# \. → 字面量 .
# (\w+) → 第3组:顶级域名
# 整体:邮箱拆成3个部分分别捕获
核心语法完整速查
# ========== 量词 ==========
r'a*' # 0个或多个 a
r'a+' # 1个或多个 a
r'a?' # 0个或1个 a(可选)
r'a{3}' # 精确3个 a
r'a{3,5}' # 3到5个 a
r'a{3,}' # 3个或更多 a
# ========== 贪婪 vs 非贪婪 ==========
r'<.*>' # 贪婪:尽可能多匹配
r'<.*?>' # 非贪婪:尽可能少匹配(加?变非贪婪)
# ========== 字符集 ==========
r'[abc]' # a或b或c
r'[a-z]' # 小写字母
r'[A-Z]' # 大写字母
r'[0-9]' # 数字(等同\d)
r'[^abc]' # 非a非b非c(^在[]内表示取反)
r'[a-zA-Z0-9]' # 字母或数字
# ========== 预定义字符 ==========
r'\d' # 数字 [0-9]
r'\D' # 非数字 [^0-9]
r'\w' # 字母数字下划线 [a-zA-Z0-9_]
r'\W' # 非\w
r'\s' # 空白符(空格/Tab/换行)
r'\S' # 非空白
r'\b' # 单词边界
r'\B' # 非单词边界
# ========== 锚点 ==========
r'^hello' # 以hello开头
r'world$' # 以world结尾
r'^\d+$' # 整个字符串全是数字
# ========== 分组 ==========
r'(ab)+' # 捕获分组,可反向引用
r'(?:ab)+' # 非捕获分组(只分组不存储,性能更好)
r'(?P<name>\w+)' # 命名分组
高级用法 1:命名分组 (?P<name>...)
import re
text = '张三 1990-05-20 13812345678'
pattern = re.compile(
r'(?P<name>\w+)\s+' # 命名分组:姓名
r'(?P<birth>\d{4}-\d{2}-\d{2})\s+' # 命名分组:生日
r'(?P<phone>1[3-9]\d{9})' # 命名分组:手机
)
m = pattern.search(text)
# 用名字取值,比 group(1) 更可读!
print(m.group('name')) # '张三'
print(m.group('birth')) # '1990-05-20'
print(m.group('phone')) # '13812345678'
# 转成字典
print(m.groupdict())
# {'name': '张三', 'birth': '1990-05-20', 'phone': '13812345678'}
高级用法 2:贪婪 vs 非贪婪(爬虫必知)
import re
html = '<div>第一段</div><div>第二段</div>'
# 贪婪模式(默认):.* 尽可能多吃
result = re.findall(r'<div>(.*)</div>', html)
print(result) # ['第一段</div><div>第二段'] ← 吃多了!
# 非贪婪模式:.*? 尽可能少吃
result = re.findall(r'<div>(.*?)</div>', html)
print(result) # ['第一段', '第二段'] ← 正确!
# 规律:量词后加 ? 变非贪婪
# *? +? ?? {n,m}?
高级用法 3:前瞻与后顾(零宽断言)
import re
# ========== 正向前瞻 (?=...) ==========
# 匹配后面跟着某内容的位置,但不消耗字符
text = '100元 200美元 300日元'
# 提取 元 前面的数字
result = re.findall(r'\d+(?=元)', text)
print(result) # ['100', '300'] ← 200美元不匹配
# ========== 负向前瞻 (?!...) ==========
# 匹配后面 不跟着 某内容的位置
result = re.findall(r'\d+(?!元|\d)', text)
print(result) # ['200'] ← 只有美元前的200
# ========== 正向后顾 (?<=...) ==========
# 匹配前面是某内容的位置
text = '价格:99 数量:5 折扣:0.8'
result = re.findall(r'(?<=:)\d+\.?\d*', text)
print(result) # ['99', '5', '0.8'] ← 提取冒号后面的数字
# ========== 负向后顾 (?<!...) ==========
text = '100 $200 300'
result = re.findall(r'(?<!\$)\d+', text)
print(result) # ['100', '300'] ← 不提取带$的
高级用法 4:| 多模式选择
import re
# 匹配多种格式的电话
text = '电话:010-12345678 或 13812345678 或 (021)87654321'
pattern = re.compile(
r'(\(?\d{3}\)?\-?\d{8})' # 座机
r'|' # 或
r'(1[3-9]\d{9})' # 手机
)
for m in pattern.finditer(text):
print(m.group())
# 010-12345678
# 13812345678
# (021)87654321
高级用法 5:re.sub 高级替换
import re
# ===== 用分组反向引用 \1 \2 =====
# 日期格式转换 2024-01-15 → 15/01/2024
text = '今天是 2024-01-15,明天是 2024-01-16'
result = re.sub(r'(\d{4})-(\d{2})-(\d{2})', r'\3/\2/\1', text)
print(result) # '今天是 15/01/2024,明天是 16/01/2024'
# ===== 用命名分组 =====
result = re.sub(
r'(?P<y>\d{4})-(?P<m>\d{2})-(?P<d>\d{2})',
r'\g<d>/\g<m>/\g<y>',
text
)
print(result) # '今天是 15/01/2024,明天是 16/01/2024'
# ===== 用函数动态替换 =====
# 敏感词打码
sensitive = ['傻瓜', '白痴', '废物']
pattern = re.compile('|'.join(sensitive))
def mask(m):
word = m.group()
return word[0] + '*' * (len(word) - 1) # 只保留第一个字
text = '你这个傻瓜和白痴'
print(pattern.sub(mask, text)) # '你这个傻*和白*'
高级用法 6:flags 标志位
import re
text = '''Hello World
hello python
HELLO REGEX'''
# re.IGNORECASE (re.I) → 忽略大小写
re.findall(r'hello', text, re.I)
# ['Hello', 'hello', 'HELLO']
# re.MULTILINE (re.M) → ^ $ 匹配每行首尾
re.findall(r'^\w+', text, re.M)
# ['Hello', 'hello', 'HELLO'] ← 每行开头
# re.DOTALL (re.S) → . 匹配换行符
re.findall(r'Hello.*HELLO', text, re.S)
# ['Hello World\nhello python\nHELLO']
# re.VERBOSE (re.X) → 可以写注释,提高可读性!
phone_pattern = re.compile(r'''
(1) # 第1位必须是1
([3-9]) # 第2位3-9
(\d{9}) # 后9位任意数字
''', re.VERBOSE)
实战综合:脏数据清洗流水线
import re
dirty_data = [
" <张三> | age:28岁 | TEL:138-1234-5678 ",
" 【李四】 | age:32岁 | TEL:139-8765-4321 ",
]
# 全角转半角
def full2half(text):
result = ''
for char in text:
code = ord(char)
if 0xFF01 <= code <= 0xFF5E: # 全角字符范围
result += chr(code - 0xFEE0) # 转半角
elif code == 0x3000: # 全角空格
result += ' '
else:
result += char
return result
patterns = {
'name': re.compile(r'[<【<]([\w]+)[>】>]'),
'age': re.compile(r'age[::]\s*(\d+)'),
'phone': re.compile(r'TEL[::]\s*([\d-]+)'),
}
for row in dirty_data:
row = full2half(row).strip() # 1. 全角→半角,去空格
row = re.sub(r'\s+', ' ', row) # 2. 多个空格→单个
name = patterns['name'].search(row).group(1)
age = patterns['age'].search(row).group(1)
phone = patterns['phone'].search(row).group(1)
phone = re.sub(r'-', '', phone) # 3. 去掉电话横线
print(f"姓名:{name} | 年龄:{age} | 电话:{phone}")
# 输出:
# 姓名:张三 | 年龄:28 | 电话:13812345678
# 姓名:李四 | 年龄:32 | 电话:13987654321
总结
- 找一个用
search - 找所有用
findall - 替换用
sub - 分割用
split - 验证用
fullmatch
更多推荐



所有评论(0)