【第6章 字符串】Python 字符串之正则表达式完全指南(含丰富示例)

Python 字符串之正则表达式完全指南(含丰富示例·完整版)

正则表达式(Regular Expression)是字符串处理的“瑞士军刀”,广泛应用于数据清洗、文本提取、格式验证等场景。Python 内置 re 模块提供了完整的正则表达式支持,本文将从基础到进阶,系统覆盖正则核心语法、re 模块用法、实战场景,补充花括号量词、反向引用等关键知识点,帮你彻底掌握正则表达式的使用。

一、正则表达式基础(核心元字符与量词)

元字符是正则表达式的“语法符号”,量词用于控制字符重复次数,二者结合构成正则的基础骨架。

1. 普通字符(直接匹配)

  • 字母、数字、下划线等普通字符,直接匹配自身。
  • 示例:"abc" 匹配字符串中的 "abc""123" 匹配 "123""_" 匹配 "_"

2. 核心特殊元字符(必掌握)

元字符 含义 示例
. 匹配任意单个字符(除换行符 \nre.S 模式下可匹配换行) "a.b" 匹配 "aab""acb""a1b"(不匹配 "ab""a\nb"
^ 匹配字符串开头(re.M 模式下匹配行开头) "^abc" 匹配 "abc123"(不匹配 "xabc"
` 元字符 含义
-------- ------ ------
. 匹配任意单个字符(除换行符 \nre.S 模式下可匹配换行) "a.b" 匹配 "aab""acb""a1b"(不匹配 "ab""a\nb"
^ 匹配字符串开头(re.M 模式下匹配行开头) "^abc" 匹配 "abc123"(不匹配 "xabc"
\| 匹配字符串结尾(`re.M` 模式下匹配行结尾) \| `"abc$"` 匹配 `"123abc"`(不匹配 `"abcx"`) |

| [] | 字符集:匹配括号内任意一个字符(支持范围 [a-z]、排除 [^a-z]) | "[abc]" 匹配 "a"/"b"/"c""[0-9a-f]" 匹配十六进制数字 |
| [^] | 否定字符集:匹配括号外任意一个字符(^ 必须在括号开头) | "[^abc]" 匹配除 "a"/"b"/"c" 外的任意字符 |
| () | 分组:将括号内内容视为整体,可用于提取子串、反向引用 | "(ab)+" 匹配 "ab"/"abab""(a)(b)" 可通过 \1/\2 引用 |
| \| | 或:匹配左右任意一个表达式(优先级较低,需配合分组使用) | "a\|b" 匹配 "a"/"b""(ab)\|(cd)" 匹配 "ab"/"cd" |
| \ | 转义字符:将特殊字符转为普通字符(如 \. 匹配 .) | "\." 匹配 ".""\*" 匹配 "*""\\n" 匹配换行符 |

3. 量词(控制重复次数,含花括号完整用法)

量词用于定义前导字符/子表达式的重复次数,支持贪婪与非贪婪模式,是正则的核心特性之一。

量词类型 语法 贪婪/非贪婪 含义 示例(匹配 a
零次或多次 * 贪婪 重复 0 次或多次(优先最多) a* 匹配 ""/"a"/"aaa"
零次或多次 *? 非贪婪 重复 0 次或多次(优先最少) a*? 优先匹配 ""
一次或多次 + 贪婪 重复 1 次或多次(优先最多) a+ 匹配 "a"/"aaa"
一次或多次 +? 非贪婪 重复 1 次或多次(优先最少) a+? 优先匹配 "a"
零次或一次 ? 贪婪 重复 0 次或 1 次(优先 1 次) a? 匹配 ""/"a"
零次或一次 ?? 非贪婪 重复 0 次或 1 次(优先 0 次) a?? 优先匹配 ""
精确次数 {n} 无(固定) 必须重复 n 次 a{3} 匹配 "aaa"
范围次数 {m,n} 贪婪 重复 m~n 次(优先 n 次) a{2,4} 匹配 "aa"/"aaa"/"aaaa"
范围次数 {m,n}? 非贪婪 重复 m~n 次(优先 m 次) a{2,4}? 优先匹配 "aa"
最少次数 {m,} 贪婪 至少重复 m 次(无上限,优先最多) a{2,} 匹配 "aa"/"aaa"...
最多次数 {,n} 贪婪 最多重复 n 次(0~n 次,优先 n 次) a{,3} 匹配 ""/"a"/"aaa"

量词实战示例

import re

text = "aaa aaaaa aa aaaaaaa"

# 1. 精确匹配 3 次 a
print(re.findall(r"a{3}", text))  # 输出:['aaa', 'aaa'](从 'aaaaa' 和 'aaaaaa' 中各匹配前3个a)

# 2. 匹配 2~4 次 a(贪婪:优先取上限4次)
print(re.findall(r"a{2,4}", text))  # 输出:['aaa', 'aaaa', 'aa', 'aaaa']

# 3. 匹配 2~4 次 a(非贪婪:优先取下限2次)
print(re.findall(r"a{2,4}?", text))  # 输出:['aa', 'aa', 'aa', 'aa']

# 4. 至少 3 次 a(无上限)
print(re.findall(r"a{3,}", text))  # 输出:['aaa', 'aaaaa', 'aaaaaa']

# 5. 最多 3 次 a(0~3次)
print(re.findall(r"a{,3}", text))  # 输出:['aaa', 'aaa', 'aa', 'aaa']

4. 预定义字符集(简化写法)

预定义字符集是常用字符集的简写,提高正则可读性,无需手动编写复杂字符集。

预定义字符 等价于 含义 示例
\d [0-9] 匹配任意数字 "\d+" 提取字符串中所有数字
\D [^0-9] 匹配非数字 "\D+" 提取字符串中非数字内容
\w [a-zA-Z0-9_] 匹配字母、数字、下划线 "\w+" 匹配单词(含下划线)
\W [^a-zA-Z0-9_] 匹配非字母、数字、下划线 "\W+" 匹配标点、空格等
\s [ \t\n\r\f] 匹配空白字符(空格、制表符、换行等) "\s" 匹配字符串中的空格
\S [^ \t\n\r\f] 匹配非空白字符 "\S+" 匹配非空白内容
\b - 匹配单词边界(单词与非单词的分隔) "\bhello\b" 匹配独立的 "hello"
\B - 匹配非单词边界 "\Bhello\B" 匹配单词内部的 "hello"

预定义字符集实战示例

import re

text = "Python 3.11 是最好的编程语言!Version_3.12"

# 提取所有数字(含浮点数)
print(re.findall(r"\d+\.?\d*", text))  # 输出:['3.11', '3.12']

# 提取所有单词(字母+数字+下划线)
print(re.findall(r"\w+", text))  # 输出:['Python', '3', '11', 'Version_3', '12']

# 提取所有非空白字符组合
print(re.findall(r"\S+", text))  # 输出:['Python', '3.11', '是最好的编程语言!', 'Version_3.12']

# 匹配独立的单词 "Python"(不匹配包含 Python 的长单词)
print(re.findall(r"\bPython\b", text))  # 输出:['Python']

二、Python re 模块核心函数(实战必备)

re 模块提供了 6 个核心函数,覆盖“匹配、查找、替换、分割”等场景,掌握这些函数就能应对绝大多数字符串处理需求。

1. re.match(pattern, string, flags=0)

  • 功能:从字符串开头匹配正则表达式,仅返回第一个匹配结果。
  • 返回值:匹配成功返回 Match 对象,失败返回 None
  • Match 对象常用方法
    • group():返回匹配的整个字符串。
    • group(n):返回第 n 个分组的内容(n 从 1 开始)。
    • span():返回匹配的起始和结束索引(元组:(start, end))。

示例:验证手机号格式

import re

# 手机号规则:以 1 开头,第二位 3-9,后面跟 9 位数字(共 11 位)
phone_pattern = r"^1[3-9]\d{9}$"

phone_list = ["13812345678", "12345678901", "13998765432", "1881234567"]
for phone in phone_list:
    result = re.match(phone_pattern, phone)
    if result:
        print(f"{phone}:有效(匹配范围:{result.span()})")
    else:
        print(f"{phone}:无效")

# 输出:
# 13812345678:有效(匹配范围:(0, 11))
# 12345678901:无效
# 13998765432:有效(匹配范围:(0, 11))
# 1881234567:无效

2. re.search(pattern, string, flags=0)

  • 功能:在整个字符串中查找第一个匹配项(不限制开头)。
  • 区别于 matchmatch 仅匹配开头,search 匹配任意位置。

示例:提取字符串中的邮箱

import re

text = "我的邮箱是 abc123@qq.com,备用邮箱是 def456@gmail.com,联系电话 13812345678"

# 邮箱正则:用户名(字母/数字/下划线/点/减号)+ @ + 域名 + 顶级域名
email_pattern = r"[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+"

# 查找第一个邮箱
result = re.search(email_pattern, text)
if result:
    print(f"找到邮箱:{result.group()}(位置:{result.span()})")

# 输出:找到邮箱:abc123@qq.com(位置:(6, 18))

3. re.findall(pattern, string, flags=0)

  • 功能:在整个字符串中查找所有匹配项,返回列表(包含所有匹配结果)。
  • 场景:批量提取文本中的目标内容(如所有邮箱、所有数字)。

示例:提取所有数字和邮箱

import re

text = "商品A价格:99元,商品B价格:199.9元,联系邮箱:test123@163.com、demo@outlook.com"

# 1. 提取所有整数(用单词边界避免误匹配)
nums = re.findall(r"\b\d+\b", text)
print("所有整数:", nums)  # 输出:['99', '199', '123', '163']

# 2. 提取所有浮点数和整数
all_nums = re.findall(r"\d+\.?\d*", text)
print("所有数字(整数+浮点数):", all_nums)  # 输出:['99', '199.9', '123', '163']

# 3. 提取所有邮箱
emails = re.findall(r"[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+", text)
print("所有邮箱:", emails)  # 输出:['test123@163.com', 'demo@outlook.com']

4. re.sub(pattern, repl, string, count=0, flags=0)

  • 功能:替换字符串中匹配的内容。
  • 参数
    • repl:替换后的字符串(可使用 \1/\2 引用分组内容)。
    • count:替换次数(默认 0,表示全部替换)。
  • 场景:敏感词替换、格式修正。

示例:敏感词替换与日期格式转换

import re

text = "张三是个笨蛋,李四是个傻瓜,2024-10-01 是国庆节,2024-10-02 是假期第二天"

# 1. 敏感词替换(将“笨蛋”“傻瓜”替换为“***”)
sensitive_pattern = r"笨蛋|傻瓜"
new_text = re.sub(sensitive_pattern, "***", text)
print("敏感词替换后:", new_text)

# 2. 日期格式转换(2024-10-01 → 2024年10月01日)
date_pattern = r"(\d{4})-(\d{2})-(\d{2})"  # 分组匹配年、月、日
new_date_text = re.sub(date_pattern, r"\1年\2月\3日", text)
print("日期格式转换后:", new_date_text)

# 输出:
# 敏感词替换后:张三是个***,李四是个***,2024-10-01 是国庆节,2024-10-02 是假期第二天
# 日期格式转换后:张三是个笨蛋,李四是个傻瓜,2024年10月01日 是国庆节,2024年10月02日 是假期第二天

5. re.split(pattern, string, maxsplit=0, flags=0)

  • 功能:根据正则表达式匹配的内容分割字符串,返回列表。
  • 参数maxsplit:最大分割次数(默认 0,不限制)。
  • 场景:复杂分隔符的字符串分割(如同时用逗号、空格、分号分割)。

示例:多分隔符分割字符串

import re

text = "苹果,香蕉 橙子;葡萄|芒果 西瓜"

# 用逗号、空格、分号、竖线分割
split_pattern = r"[, ;|]"
result = re.split(split_pattern, text)
print("全部分割:", result)  # 输出:['苹果', '香蕉', '橙子', '葡萄', '芒果', '西瓜']

# 限制分割次数(只分割前 2 次)
result2 = re.split(split_pattern, text, maxsplit=2)
print("分割前 2 次:", result2)  # 输出:['苹果', '香蕉', '橙子;葡萄|芒果 西瓜']

6. re.compile(pattern, flags=0)

  • 功能:编译正则表达式模式,生成 Pattern 对象,可重复使用。
  • 优势:多次使用同一正则表达式时,编译后效率更高(避免重复解析正则)。

示例:批量验证手机号

import re

# 编译正则表达式(只编译一次)
phone_pattern = re.compile(r"^1[3-9]\d{9}$")

# 批量验证手机号
phones = ["13812345678", "12345678901", "13998765432", "1881234567"]
for phone in phones:
    if phone_pattern.match(phone):  # 直接调用Pattern对象的match方法
        print(f"{phone}:有效")
    else:
        print(f"{phone}:无效")

# 输出:
# 13812345678:有效
# 12345678901:无效
# 13998765432:有效
# 1881234567:无效

三、进阶特性:分组、引用与断言

掌握基础语法后,进阶特性能让正则处理更复杂的场景(如嵌套结构匹配、条件判断)。

1. 分组与捕获(() 的深度用法)

分组不仅能将子表达式视为整体,还能“捕获”匹配的内容,通过 group(n) 提取。

(1)普通捕获分组

import re

text = "2024年10月01日 天气:晴,温度:25℃"

# 分组匹配日期(年、月、日)和温度
pattern = r"(\d{4})年(\d{2})月(\d{2})日.*温度:(\d+)℃"
result = re.search(pattern, text)

if result:
    print("完整匹配:", result.group())  # 输出:2024年10月01日 天气:晴,温度:25℃
    print("年份:", result.group(1))     # 输出:2024(第1个分组)
    print("月份:", result.group(2))     # 输出:10(第2个分组)
    print("日期:", result.group(3))     # 输出:01(第3个分组)
    print("温度:", result.group(4))     # 输出:25(第4个分组)

(2)命名分组((?P<name>pattern)

给分组命名,通过 group("name") 提取,比索引更易读(Python 特有)。

import re

text = "姓名:张三,年龄:25,身高:175cm"

# 命名分组匹配姓名、年龄、身高
pattern = r"姓名:(?P<name>.*),年龄:(?P<age>\d+),身高:(?P<height>\d+)cm"
result = re.search(pattern, text)

print("姓名:", result.group("name"))    # 输出:张三
print("年龄:", result.group("age"))     # 输出:25
print("身高:", result.group("height"))  # 输出:175

(3)非捕获分组((?:pattern)

仅将子表达式视为整体,不捕获内容(节省内存,无需提取时用)。

import re

text = "apple banana apple orange"

# 非捕获分组:匹配 "apple" 或 "banana",但不捕获分组内容
pattern = r"(?:apple|banana)"
print(re.findall(pattern, text))  # 输出:['apple', 'banana', 'apple'](仅返回匹配结果,无分组信息)

2. 反向引用(\1~\9

引用前面分组捕获的内容,实现“重复匹配已出现的子串”。

示例1:匹配重复的单词或字符

import re

text = "abab 1212 abcabc hellohello"

# 匹配重复两次的两位字符(如 abab → ab 重复)
pattern = r"(\w\w)\1"  # \1 引用第一个分组的两位字符
print(re.findall(pattern, text))  # 输出:['ab', '12', 'he']

# 匹配重复两次的单词(如 hellohello → hello 重复)
pattern2 = r"(\w+)\1"
print(re.findall(pattern2, text))  # 输出:['hello']

示例2:匹配成对的 HTML 标签

import re

text = "<h1>标题</h1><div>内容</div><p>段落</p>"

# 匹配成对标签(如 <h1>...</h1>,确保标签名一致)
pattern = r"<(\w+)>.*?</\1>"  # \1 引用标签名(h1/div/p)
print(re.findall(pattern, text))  # 输出:['h1', 'div', 'p']

3. 零宽断言(匹配位置而非字符)

断言用于匹配“某个位置前后是否满足条件”,不消耗字符(零宽),常用于“排除特定前缀/后缀”的场景。

断言类型 语法 含义 示例
正向先行断言 (?=pattern) 匹配后面是 pattern 的位置 "a(?=b)" 匹配后面是 b 的 a(如 "ab" 中的 a)
负向先行断言 (?!pattern) 匹配后面不是 pattern 的位置 "a(?!b)" 匹配后面不是 b 的 a(如 "ac" 中的 a)
正向后行断言 (?<=pattern) 匹配前面是 pattern 的位置 "(?<=a)b" 匹配前面是 a 的 b(如 "ab" 中的 b)
负向后行断言 (?<!pattern) 匹配前面不是 pattern 的位置 "(?<!a)b" 匹配前面不是 a 的 b(如 "cb" 中的 b)

示例1:提取带特定前缀的数字

import re

text = "价格:99元,优惠价:88元,原价:120元,数量:5"

# 提取“价格:”或“优惠价:”后面的数字(排除“原价:”和“数量:”)
pattern = r"(?<=价格:|优惠价:)\d+"  # 正向后行断言:前面是“价格:”或“优惠价:”
print(re.findall(pattern, text))  # 输出:['99', '88']

示例2:排除特定后缀的单词

import re

text = "apple banana applet orange app"

# 匹配以 "app" 开头但不以 "let" 结尾的单词
pattern = r"app\w+(?!let)\b"  # 负向先行断言:后面不是 "let"
print(re.findall(pattern, text))  # 输出:['apple', 'app'](排除 applet)

四、正则模式修饰符(flags 参数)

修饰符用于改变正则的匹配行为,常见修饰符如下:

修饰符 含义 简写
re.IGNORECASE 忽略大小写匹配 re.I
re.DOTALL . 匹配换行符 \n re.S
re.MULTILINE ^ 匹配行开头,` 修饰符
-------- ------ ------
re.IGNORECASE 忽略大小写匹配 re.I
re.DOTALL . 匹配换行符 \n re.S
匹配行结尾 re.M
re.VERBOSE 允许正则换行和注释,提高可读性 re.X

示例:忽略大小写与多行匹配

import re

# 1. 忽略大小写匹配
text = "Python python PYTHON"
pattern = r"python"
print(re.findall(pattern, text, re.I))  # 输出:['Python', 'python', 'PYTHON']

# 2. 多行模式(匹配每行开头的数字)
text2 = """1. 第一项
2. 第二项
3. 第三项"""
pattern2 = r"^\d+"
print(re.findall(pattern2, text2, re.M))  # 输出:['1', '2', '3'](多行模式下 ^ 匹配每行开头)

# 3. 允许注释的正则(re.X 模式)
pattern3 = re.compile(r"""
    \d+  # 匹配数字部分
    \.   # 匹配小数点
    \d*  # 匹配小数部分(可选)
""", re.X)
print(pattern3.findall("价格:99.5元,折扣:0.8,数量:5"))  # 输出:['99.5', '0.8']

五、实战场景综合案例

案例1:解析日志文件(提取关键信息)

日志内容:

2024-10-01 08:30:25 [INFO] User 'admin' login from 192.168.1.1
2024-10-01 09:15:42 [ERROR] Database connection failed (code: 500)
2024-10-01 10:00:10 [INFO] User 'guest' login from 10.0.0.5

目标:提取所有日志的时间、级别、详情。

import re

log_text = """2024-10-01 08:30:25 [INFO] User 'admin' login from 192.168.1.1
2024-10-01 09:15:42 [ERROR] Database connection failed (code: 500)
2024-10-01 10:00:10 [INFO] User 'guest' login from 10.0.0.5"""

# 正则:分组匹配时间、级别、详情
pattern = r"(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) \[(INFO|ERROR)\] (.*)"
results = re.findall(pattern, log_text)

for time, level, detail in results:
    print(f"时间:{time},级别:{level},详情:{detail}")

# 输出:
# 时间:2024-10-01 08:30:25,级别:INFO,详情:User 'admin' login from 192.168.1.1
# 时间:2024-10-01 09:15:42,级别:ERROR,详情:Database connection failed (code: 500)
# 时间:2024-10-01 10:00:10,级别:INFO,详情:User 'guest' login from 10.0.0.5

案例2:清洗HTML文本(提取纯文字)

HTML内容:

<div class="content">
    <h2>正则表达式指南</h2>
    <p>本文介绍<span style="color:red">Python正则</span>的用法。</p>
</div>

目标:去除所有HTML标签,保留纯文字。

import re

html = """<div class="content">
    <h2>正则表达式指南</h2>
    <p>本文介绍<span style="color:red">Python正则</span>的用法。</p>
</div>"""

# 正则:匹配所有HTML标签(<...>)并替换为空
pattern = r"<.*?>"  # 非贪婪匹配标签,避免跨标签匹配
clean_text = re.sub(pattern, "", html)
# 去除多余空白(换行、空格)
clean_text = re.sub(r"\s+", " ", clean_text).strip()

print(clean_text)  # 输出:正则表达式指南 本文介绍Python正则的用法。

案例3:验证复杂密码(密码强度检测)

密码规则:

  • 长度8~16位
  • 包含至少1个大写字母、1个小写字母、1个数字、1个特殊字符(!@#$%^&*)
import re

def check_password(password):
    # 长度8~16位
    if not re.match(r"^.{8,16}$", password):
        return "密码长度必须8~16位"
    # 包含大写字母
    if not re.search(r"[A-Z]", password):
        return "密码必须包含至少1个大写字母"
    # 包含小写字母
    if not re.search(r"[a-z]", password):
        return "密码必须包含至少1个小写字母"
    # 包含数字
    if not re.search(r"\d", password):
        return "密码必须包含至少1个数字"
    # 包含特殊字符
    if not re.search(r"[!@#$%^&*]", password):
        return "密码必须包含至少1个特殊字符(!@#$%^&*)"
    return "密码强度合格"

print(check_password("Aa123456"))        # 输出:密码必须包含至少1个特殊字符(!@#$%^&*)
print(check_password("Aa!12345678901234"))  # 输出:密码强度合格
print(check_password("aa!123456"))       # 输出:密码必须包含至少1个大写字母

六、常见问题与避坑指南

  1. 贪婪与非贪婪的选择

    • 嵌套结构(如HTML标签、多组引号)必须用非贪婪(.*?),避免跨结构匹配;
    • 简单文本(无重复结束符)用贪婪更高效(如提取URL中的域名)。
  2. 转义字符的坑

    • 正则中的特殊字符(. * + ? ^ $ () [] {} | \)需用 \ 转义;
    • Python字符串中 \ 本身需转义(如匹配 \ 需写 r"\\""\\\\")。
  3. 性能优化

    • 多次使用的正则务必用 re.compile 编译;
    • 避免过度使用 .*(贪婪匹配可能导致大量回溯),尽量用具体字符集(如 [^>]+ 匹配标签内容)。
  4. 不要用正则解析HTML/XML

    • 复杂HTML/XML嵌套结构(如标签嵌套、属性含特殊字符)无法用正则完美解析,应使用 BeautifulSoup 等专用库。

七、总结

正则表达式是Python字符串处理的核心工具,掌握其语法规则(元字符、量词)、re 模块函数、进阶特性(分组、断言)后,能高效解决文本提取、格式验证、内容清洗等问题。

学习建议:

  1. 基础阶段:熟记元字符和量词,通过 re.findall re.sub 练习简单场景;
  2. 进阶阶段:掌握分组、反向引用、断言,处理复杂匹配逻辑;
  3. 实战阶段:结合具体业务(日志解析、数据清洗),优先保证正则可读性(必要时添加注释)。

通过本文的系统讲解和示例练习,你已具备应对绝大多数正则场景的能力,剩下的就是在实践中不断优化和积累经验。

posted @ 2025-11-13 12:48  wangya216  阅读(458)  评论(0)    收藏  举报