正则表达式(Regular Expression,简称 regex/re)是用特殊字符组合匹配、查找、替换文本的工具 ,Python 内置 re 模块支持正则操作,无需额外安装。
这份笔记从基础概念→核心语法→re 模块常用方法→实战案例→避坑指南逐步讲解,全程配 Python 代码示例,小白也能跟着练。
一、前置准备:re 模块基础
Python 正则的核心是内置 re 模块,使用前需导入:
python
import re # 导入正则模块
常用核心函数(先记个大概,后面详细讲)
| 函数名 | 作用 | 返回值 |
|---|---|---|
re.match(pattern, str) |
从字符串开头匹配正则 | 匹配成功返回匹配对象,否则 None |
re.search(pattern, str) |
在整个字符串找第一个匹配项 | 匹配成功返回匹配对象,否则 None |
re.findall(pattern, str) |
查找所有匹配项 | 列表(无匹配返回空列表) |
re.sub(pattern, repl, str) |
用 repl 替换字符串中所有匹配项 | 替换后的新字符串 |
re.split(pattern, str) |
按正则规则分割字符串 | 分割后的列表 |
二、正则基础:字符匹配(最核心的基础)
正则的本质是用 "规则字符" 匹配 "文本字符",先从最基础的字符匹配学起。
1. 普通字符:直接匹配
普通字符(字母、数字、下划线)会精确匹配自身。
python
# 匹配字符串开头的"python"
result = re.match("python", "python学习真有趣")
print(result) # <re.Match object; span=(0, 6), match='python'>
print(result.group()) # 提取匹配结果:python
-
若匹配失败,
re.match返回None:python
result = re.match("java", "python学习真有趣") print(result) # None
2. 特殊字符:需转义(. ^ $ * + ? \ [] {} ( ) | )
这些字符有特殊含义,若要匹配字符本身 ,需用反斜杠 \ 转义。
python
# 匹配字符串中的"."(正则中.是特殊字符,需转义)
result = re.search(r"\.", "python.123")
print(result.group()) # .
提示:Python 中推荐用 ** 原始字符串(r"")** 写正则,避免反斜杠被 Python 解析为转义符(如
\n换行)。
3. 通配符:匹配任意字符(除换行)
. 是通配符,匹配除换行符(\n)外的任意单个字符。
python
# 匹配"p...n"(p开头,中间3个任意字符,n结尾)
result = re.match("p...n", "python")
print(result.group()) # python
# 匹配失败(中间只有2个字符)
result = re.match("p...n", "pythn")
print(result) # None
三、正则核心:重复匹配(控制字符出现次数)
正则中量词用于控制前一个字符 / 分组的出现次数,是正则的核心语法之一。
常用量词表
| 量词 | 含义 | 示例 |
|---|---|---|
* |
匹配0 次或多次 | a* → a、aa、aaa |
+ |
匹配1 次或多次(至少 1 次) | a+ → a、aa、aaa |
? |
匹配0 次或 1 次(可选) | a? → 空、a |
{n} |
匹配恰好 n 次 | a{3} → aaa |
{n,} |
匹配至少 n 次 | a{2,} → aa、aaa |
{n,m} |
匹配n 到 m 次(包含 n、m) | a{2,4} → aa、aaa、aaaa |
代码示例
python
# 1. *:0次或多次
print(re.match("a*", "aaa").group()) # aaa
print(re.match("a*", "").group()) # 空字符串
# 2. +:至少1次
print(re.match("a+", "aaa").group()) # aaa
print(re.match("a+", "")) # None
# 3. ?:0或1次
print(re.match("a?", "a").group()) # a
print(re.match("a?", "").group()) # 空
# 4. {n,m}:指定次数
print(re.match("a{2,4}", "aaa").group()) # aaa
print(re.match("a{2}", "aa").group()) # aa
print(re.match("a{2}", "a")) # None
关键:量词作用范围
量词只作用于前一个字符 / 分组 ,加括号 () 可让量词作用于整个分组。
python
# 无括号:+只作用于b
print(re.match("ab+", "abbb").group()) # abbb
# 有括号:+作用于整个ab
print(re.match("(ab)+", "ababab").group()) # ababab
四、正则进阶:字符集与分组(精准匹配范围)
1. 字符集 []:匹配范围内的单个字符
用方括号 [] 定义字符范围 ,匹配其中任意一个字符(单个字符!)。
常用字符集写法
| 写法 | 含义 | 示例 |
|---|---|---|
[abc] |
匹配 a、b、c 中的任意一个 | [abc] → a、b、c |
[a-z] |
匹配小写字母 a-z | [a-z] → a、b...z |
[A-Z] |
匹配大写字母 A-Z | [A-Z] → A、B...Z |
[0-9] |
匹配数字 0-9(等价于 \d) | [0-9] → 0、1...9 |
[^abc] |
匹配除 a、b、c 外的任意字符(^ 取反) | [^abc] → d、1、@ |
代码示例
python
# 匹配开头是a、b、c中的一个,后面跟任意字符
print(re.match("[abc]123", "b123").group()) # b123
print(re.match("[a-z]python", "xpython").group()) # xpython
# 取反:匹配非数字
print(re.search("[^0-9]", "123a456").group()) # a
2. 分组 ():打包 + 提取 + 反向引用
分组是正则的核心功能,前面讲过,这里结合 Python 详细讲捕获分组 和非捕获分组。
(1)捕获分组:提取匹配内容(最常用)
() 会将括号内的匹配结果捕获 ,可通过 group(n) 提取(n=1,2,3...),group() 或 group(0) 提取整体匹配结果。
案例 1:提取手机号(3 位区号 + 8 位号码)
python
# 正则:(\d{3})-(\d{8}) 两个分组分别捕获区号和号码
text = "联系电话:010-12345678,021-87654321"
result = re.search(r"(\d{3})-(\d{8})", text)
# 提取整体结果
print("整体匹配:", result.group()) # 010-12345678
# 提取第1个分组(区号)
print("分组1(区号):", result.group(1)) # 010
# 提取第2个分组(号码)
print("分组2(号码):", result.group(2)) # 12345678
# 同时提取所有分组
print("所有分组:", result.groups()) # ('010', '12345678')
案例 2:提取日期(年 - 月 - 日)
python
text = "今天是2026-04-10"
result = re.match(r"(\d{4})-(\d{2})-(\d{2})", text)
print("年:", result.group(1)) # 2026
print("月:", result.group(2)) # 04
print("日:", result.group(3)) # 10
(2)非捕获分组:仅分组不提取 (?:)
若只需用分组做整体控制 (如量词作用),不需要提取内容,用 (?:) 表示非捕获分组,不占用分组编号。
python
# 匹配可选的http协议+域名
text = "https://www.baidu.com"
# 非捕获分组:(?:https?://)? 协议可选,但不提取
result = re.match(r"(?:https?://)?www\.\w+\.com", text)
print(result.group()) # https://www.baidu.com
# 无分组1,因为是非捕获
# print(result.group(1)) # 报错:IndexError: no such group
(3)反向引用:重复前面分组的内容 \1
用 \数字 引用前面分组的匹配结果,确保前后内容一致,常用于匹配成对标签、重复字符等。
python
# 匹配成对的标签:<div>内容</div>
text = "<div>Python正则</div>"
# (\w+) 匹配标签名,\1 引用该标签名,保证前后一致
result = re.match(r"<(\w+)>.*?</\1>", text)
print(result.group()) # <div>Python正则</div>
# 匹配失败:标签不一致
text2 = "<div>Python正则</span>"
result2 = re.match(r"<(\w+)>.*?</\1>", text2)
print(result2) # None
3. 预编译正则:提升效率 re.compile()
若同一个正则需要多次使用 ,用 re.compile() 预编译正则表达式,生成正则对象,再调用对象的方法,效率更高。
python
# 预编译正则:匹配手机号
pattern = re.compile(r"(\d{3})-(\d{8})")
# 多次调用匹配
text1 = "010-12345678"
text2 = "021-87654321"
result1 = pattern.search(text1)
result2 = pattern.search(text2)
print(result1.group()) # 010-12345678
print(result2.group()) # 021-87654321
五、re 模块常用方法详细讲解(结合实战)
1. re.match():从开头匹配
- 仅从字符串第一个字符开始匹配,失败则返回 None。
- 常用场景:验证字符串是否以指定格式开头。
python
# 验证是否以字母开头,后面跟3个数字
text = "A123test"
result = re.match(r"[A-Za-z]\d{3}", text)
print(result.group()) # A123
# 失败案例(开头不是字母)
text2 = "123Abc"
result2 = re.match(r"[A-Za-z]\d{3}", text2)
print(result2) # None
2. re.search():全局查找第一个匹配
- 扫描整个字符串,找到第一个匹配项即返回,无需从开头匹配。
- 常用场景:查找文本中任意位置的匹配内容。
python
# 查找文本中第一个数字串
text = "价格:99元,数量:5件"
result = re.search(r"\d+", text) # \d+ 匹配1个或多个数字
print(result.group()) # 99
3. re.findall():查找所有匹配项(最常用)
- 返回列表,包含所有匹配项;无匹配则返回空列表。
- 若正则有分组,返回分组内容组成的元组列表。
案例 1:提取文本中所有数字
python
text = "Python3.11,发布于2022年,最新版本3.12"
result = re.findall(r"\d+\.?\d*", text) # 匹配版本号(如3.11、3.12)和年份(2022)
print(result) # ['3.11', '2022', '3.12']
案例 2:提取所有邮箱(分组提取用户名 + 域名)
python
text = "邮箱:abc@qq.com,def@163.com,ghi@baidu.com"
# 分组:(\w+)@(\w+\.\w+) 提取用户名和域名
result = re.findall(r"(\w+)@(\w+\.\w+)", text)
print(result) # [('abc', 'qq.com'), ('def', '163.com'), ('ghi', 'baidu.com')]
# 遍历提取结果
for user, domain in result:
print(f"用户名:{user},域名:{domain}")
4. re.sub():替换匹配内容
- 语法:
re.sub(pattern, repl, string, count=0)- repl:替换的字符串(也可以是函数)
- count:最大替换次数,0 表示替换所有。
案例 1:替换所有数字为 "#"
python
text = "今天是2026年04月10日"
# 替换所有数字为#
result = re.sub(r"\d+", "#", text)
print(result) # 今天是#年#月#日
案例 2:替换手机号中间 4 位为 ****(分组替换)
python
text = "手机号:13812345678,13987654321"
# 正则:(\d{3})\d{4}(\d{4}) 分组捕获前3位和后4位
# repl:\1****\2 引用分组1和分组2
result = re.sub(r"(\d{3})\d{4}(\d{4})", r"\1****\2", text)
print(result) # 手机号:138****5678,139****4321
5. re.split():按正则分割字符串
- 按正则规则分割字符串,返回列表。
案例 1:按逗号、分号、空格分割
python
text = "苹果,香蕉;橙子 葡萄"
# 分割规则:[,; ] 匹配逗号、分号、空格
result = re.split(r"[,; ]", text)
print(result) # ['苹果', '香蕉', '橙子', '葡萄']
案例 2:分割字符串并保留分割符
python
text = "hello-world_python"
# 分割符为-或_,用()包裹则保留分割符
result = re.split(r"(-|_)", text)
print(result) # ['hello', '-', 'world', '_', 'python']
六、实战案例:小白必练(覆盖日常 80% 场景)
实战 1:验证手机号(11 位数字,以 1 开头)
python
python
def is_phone(phone):
# 正则:^1[3-9]\d{9}$ ^开头,$结尾,1开头,3-9中间,9位数字
pattern = re.compile(r"^1[3-9]\d{9}$")
return pattern.match(phone) is not None
# 测试
print(is_phone("13812345678")) # True
print(is_phone("1234567890")) # False(不足11位)
print(is_phone("199abc12345")) # False(含非数字)
print(is_phone("28812345678")) # False(开头不是1)
实战 2:提取身份证中的出生日期(18 位身份证)
18 位身份证规则:前 6 位地址码 + 8 位出生日期(YYYYMMDD)+3 位顺序码 + 1 位校验码。
python
python
def get_birthday(id_card):
# 匹配18位身份证中的出生日期
result = re.match(r"\d{6}(\d{8})\d{3}[\dX]", id_card)
return result.group(1)
print(get_birthday('62052220090512173X'))