在 Python 中,引入正则表达式功能需要使用内置模块 re,只需要一行代码:
import re
基础语法与字符匹配
1. 普通字符
普通字符就是你写什么就匹配什么。
例子:正则 abc 只匹配“abc”这三个连续字符。
2. 特殊字符(元字符)
| 表达式 | 含义 | 匹配举例 | 
|---|---|---|
. | 任意一个字符(除了换行符) | a.c 可匹配 "abc"、"a9c"、"a c" | 
\d | 任意数字,等价于 [0-9] | \d\d\d 匹配 123、456 | 
\w | 单词字符:字母、数字、下划线或中文(等价于 [A-Za-z0-9_]) | \w+ 匹配 "abc123_" | 
\s | 空白字符,包括空格、Tab、换行等空白字符(等价于常见的[ \t\n\r\f\v..],其实还包含了回车、换页、垂直制表等等) | \s 匹配 " "、\t | 
\D | 非数字 | \D+ 匹配 "abc" | 
\W | 非单词字符 | \W+ 匹配 "@#!" | 
\S | 非空白字符 | \S+ 匹配 "abc123" | 
3. 字符集(字符组)
一般写法:[abc] 表示可以是 a、b 或 c 中的任一个。
范围写法:[0-9]、[a-z]、[A-Z]
否定字符集:[^abc] 表示不能是 a、b、c
举例:
| 表达式 | 含义 | 匹配示例 | 
|---|---|---|
[aeiou] | 匹配元音字母 | "apple" 中的 "a" 和 "e" | 
[^0-9] | 匹配非数字 | "abc!" 中的所有字符 | 
4. 量词(重复匹配)
| 表达式 | 含义 | 
|---|---|
* | 匹配 0 次或多次,如 a* 可匹配空字符串、"a"、"aaa" | 
+ | 匹配 1 次或多次,如 a+ 匹配 "a"、"aa" | 
? | 匹配 0 次或 1 次,如 a? 匹配 "" 或 "a" | 
{n} | 精确匹配 n 次,如 \d{4} 匹配 4 位数字 | 
{n,} | 匹配至少 n 次,如 \d{3,} 匹配 3 位及以上数字 | 
{n,m} | 匹配 n 到 m 次,如 \d{2,4} 匹配 2~4 位数字 | 
5. 转义字符
\.匹配真正的.字符\\匹配\\n匹配换行符\t匹配制表符
举例:
正则表达式 3\.14 会匹配 3.14,而不是3x14
基础语法与字符匹配练习
| 题目 | 正则 | 说明 | 
|---|---|---|
| 匹配手机号 | ^1[3-9]\d{9}$ | 1 开头,第二位是 3-9,后面 9 位数字,总共 11 位 | 
| 匹配邮箱地址 | ^\w+@\w+\.\w+$ | 用户名@域名.后缀,其中各部分都是字母数字下划线组成 | 
| 身份证号(简版) | ^\d{17}[\dxX]$ | 前 17 位是数字,最后一位是数字或 x/X | 
| 匹配价格(如 12.99) | ^\d+(\.\d{1,2})?$ | 整数或小数,小数点后最多两位,可选小数部分 | 
| HTML 标签名 | ^<\w+>$ | 例如 <div>、<img>,不带斜杠的起始标签 | 
分组、边界、反向引用
1. 分组(捕获组)
表达式:(abc)
用途:
- 把多个字符当作一个整体
 - 提取组内容
 - 后续引用
 
举例:
re.search(r'(\d{4})-(\d{2})-(\d{2})', "2024-07-20").groups()
# 输出: ('2024', '07', '20')
2. 非捕获分组
- 表达式:
(?:abc) - 用途:
- 用于结构分组但不提取
 
 - 举例:
(?:jpg|png|gif)表示匹配这些后缀但不单独提取
 
3. 反向引用(重复匹配)
- 表达式:
\1、\2表示第 1、2 个分组的内容 - 示例:
(\w+)\s+\1- 匹配:
"hello hello"、"abc abc" 
 
4. 边界匹配符
| 表达式 | 含义 | 示例匹配 | 
|---|---|---|
^ | 行首 | ^abc 只匹配以 abc 开头的 | 
$ | 行尾 | abc$ 匹配以 abc 结尾的 | 
\b | 单词边界 | \bword\b 只匹配独立的 “word” | 
\B | 非单词边界 | \Bword\B 匹配嵌在单词内部的“word” | 
举例说明:
# 匹配整句话中所有以 "he" 开头的单词
re.findall(r'\bhe\w*', 'he hello hey theythe')
# 输出 ['he', 'hello', 'hey']
分组、边界、反向引用练习
| 题目 | 正则 | 解析说明 | 
|---|---|---|
| 匹配重复单词 | (\b\w+\b)\s+\1 | 抓到两个连续重复的词,如 hi hi | 
| 匹配网址 | https?://[\w./]+ | http 或 https 开头的网址 | 
| 提取标签内容 | <(\w+)>(.*?)</\1> | <tag>内容</tag>,使用反向引用来闭合 | 
| 验证密码 | ^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{6,20}$ | 必须包含字母+数字,6-20 位 | 
| 抽取 Markdown 链接 | \[(.*?)\]\((.*?)\) | 抽取 [标题](链接) 中的内容 | 
贪婪模式与断言
1. 贪婪 vs 非贪婪(懒惰)
| 表达式 | 含义 | 举例 | 
|---|---|---|
* | 贪婪,匹配尽可能多,匹配最长的内容 | <.*> 匹配整个 <div>内容</div> | 
*? | 非贪婪,匹配尽可能少,匹配最短的内容 | <.*?> 只匹配 <div> | 
示例:
import re
text = "<div>123</div><p>456</p>"
re.findall(r"<.*?>", text)
# 输出: ['<div>', '</div>', '<p>', '</p>']
2. 断言(Assertions)
正向先行断言(positive lookahead)(?=...)
- 含义:匹配后面是 
...的位置 - 示例:
\d(?=kg)→ 匹配后面紧跟“kg”的数字 
re.findall(r"\d+(?=kg)", "重量是80kg")
# 输出: ['80']
负向先行断言(negative lookahead)(?!...)
- 含义:匹配后面不是 
...的位置 - 示例:
^((?!bad).)*$→ 匹配不含 "bad" 的整行 
正向后行断言(positive lookbehind)(?<=...)
- 含义:匹配前面是 
...的位置 - 示例:
(?<=\$)\d+→ 匹配$后的数字 
re.findall(r"(?<=\$)\d+", "价格是$100")
# 输出: ['100']
负向后行断言(negative lookbehind)(?<!...)
- 含义:匹配前面不是 
...的位置 - 示例:匹配前面不是 
@的邮箱名单词 
贪婪模式与断言练习
| 题目 | 正则表达式 | 思路 | 
|---|---|---|
| 提取所有引号内容 | "([^"]*?)" | 非贪婪匹配双引号之间的内容 | 
| 匹配非 test.jpg 的 JPG 文件 | ^(?!test)\w+\.jpg$ | 用负向先行断言排除 test 开头 | 
| 匹配含 kg 单位的数字 | \d+(?=kg) | 正向先行断言 | 
匹配 $ 后的金额数字 | (?<=\$)\d+ | 正向后行断言 | 
| 匹配不包含敏感词的评论 | `^(?!.*(傻 | 骂)).*$` | 
第一个题目解析:假设文本是这样:
她说:"你好",然后又说:"再见"。
使用贪婪写法:
"([^"]*)"
会匹配:"你好",然后又说:"再见" 整段中的第一个 " 到最后一个 " 之间的所有内容:
匹配结果:
你好",然后又说:"再见
⚠️ 问题:你本来想抓两个引号里的两个词,结果它把两对引号合并成一次匹配,“匹配过头”了!
使用非贪婪写法:
"([^"]*?)"
会分别匹配每一对引号的最短内容:
匹配结果:
你好
再见
这才是我们想要的结果。
正则实战应用
日志处理、网页解析、数据清洗
日志处理示例:提取 Apache 日志的 IP 和时间
原始日志行:
127.0.0.1 - - [12/Jul/2025:10:23:45 +0800] "GET /index.html HTTP/1.1" 200 1024
正则提取:
^(\d{1,3}(?:\.\d{1,3}){3}) .*? \[([^\]]+)\]
含义:
(\d{1,3}(?:\.\d{1,3}){3}):捕获 IP 地址\[([^\]]+)\]:提取方括号中的时间戳
网页解析示例:提取所有 <a> 标签中的链接地址
HTML 示例:
<a href="https://example.com">点击这里</a>
<a href='https://openai.com'>OpenAI</a>
正则:
<a\s+[^>]*?href=["']([^"']+)["']
含义:
<a\s+[^>]*?:匹配以<a开头的标签,非贪婪方式匹配属性href=["']([^"']+)["']:捕获链接地址(单引号或双引号都支持)
数据清洗示例:去除 HTML 标签
HTML 内容:
<p>Hello <b>world</b>!</p>
正则:
<[^>]+>
含义:
<[^>]+>:匹配所有<标签>,用于清洗掉 HTML 标签- 替换为空串即可 
re.sub(r'<[^>]+>', '', text) 
数据清洗示例:替换多个连续空格为一个
示例字符串:
"这是   一个   测试"
正则:
\s{2,}
含义:
- 匹配 2 个以上的空白字符,统一替换为一个空格 
" " 
 Python 中 re 模块常用方法的详细讲解和示例
| 方法 | 主要用途 | 
|---|---|
| re.search() | 在整个字符串中搜索 第一个匹配项,返回 Match 对象。 | 
| re.match() | 只尝试从字符串 开头 匹配正则表达式,返回 Match 对象。 | 
| re.findall() | 搜索所有匹配项,返回结果的 列表。 | 
| re.finditer() | 搜索所有匹配项,返回结果的 迭代器(带位置信息)。 | 
| re.sub() | 替换所有匹配到的项。可用于信息格式化、清理或替换内容。 | 
| re.split() | 按正则表达式切分字符串,返回切分后的 列表。 | 
| re.compile() | 编译正则表达式,效率更高,适合重复使用同一个模式(如批量处理多个文本)。 | 
1. re.search()
功能: 在字符串中搜索第一个匹配项,并返回一个匹配对象。如果没有匹配项,则返回 None。
示例:
import re
# 搜索第一个匹配项 —— 搜索数字
text = "今天的日期是2023年10月25日。"
result = re.search(r"\d{4}", text)  # 匹配连续4个数字
if result:
    print("匹配结果:", result.group())  # 输出匹配到的内容
else:
    print("未找到匹配项")
匹配结果: 2023
2. re.match()
功能: 从字符串开头开始匹配。如果字符串开头不匹配,返回 None。
示例:
import re
# 从字符串开头匹配
text = "2023年10月25日是星期三。"
result = re.match(r"\d{4}年", text)  # 匹配以数字开头且后跟“年”的字符串
if result:
    print("匹配结果:", result.group())  # 返回匹配到的内容
else:
    print("未找到匹配项")
匹配结果: 2023年
注意:如果正则表达式的模式未从字符串开头开始匹配,则返回 None。
text = "今天是2023年10月25日。"
result = re.match(r"\d{4}年", text)  # 此处无法匹配到,返回 None
print(result)  # 输出 None
3. re.findall()
功能: 找到字符串中所有符合正则表达式的匹配项,并以列表形式返回结果。
示例:
import re
# 返回所有匹配项
text = "今天是2023年10月25日,以及明天是2023年10月26日。"
result = re.findall(r"\d{4}年\d{2}月\d{2}日", text)
print("匹配结果:", result)  # 输出所有匹配的日期
匹配结果: ['2023年10月25日', '2023年10月26日']
4. re.finditer()
功能: 和 re.findall() 类似,但返回的是一个迭代器,迭代器中的每一项是一个 Match 对象,可以获取位置信息。
示例:
import re
# 返回带位置信息的迭代器
text = "今天是2023年10月25日,以及明天是2023年10月26日。"
result = re.finditer(r"\d{4}年\d{2}月\d{2}日", text)
for match in result:
    print("匹配内容:", match.group(), "位置:", match.span())
输出:
匹配内容: 2023年10月25日 位置: (3, 15)
匹配内容: 2023年10月26日 位置: (21, 33)
5. re.sub()
功能: 替换字符串中的匹配项,可以通过正则表达式匹配内容,并用指定字符串进行替换。
示例:
import re
# 将日期中的“年”、“月”、“日”替换为“-”
text = "今天是2023年10月25日,以及明天是2023年10月26日。"
result = re.sub(r"年|月|日", "-", text)
print("替换结果:", result)
替换结果: 今天是2023-10-25-,以及明天是2023-10-26-。
6. re.split()
功能: 根据正则表达式,分割字符串,并返回一个列表。
示例:
import re
# 按任意字母进行分割
text = "苹果、香蕉、橙子和猕猴桃。"
result = re.split(r"[,、和]", text)  # 多分隔符
print("分割结果:", result)
分割结果: ['苹果', '香蕉', '橙子', '猕猴桃。']
7. re.compile()
功能: 将正则表达式编译为一个对象,以便在多次复用时提高效率。
示例:
import re
# 编译正则表达式
pattern = re.compile(r"\d{4}年\d{2}月\d{2}日")
text1 = "今天是2023年10月25日。"
text2 = "明天是2023年10月26日。"
# 在多个字符串中复用已编译的模式
result1 = pattern.search(text1)
result2 = pattern.search(text2)
print("匹配结果1:", result1.group())
print("匹配结果2:", result2.group())
匹配结果1: 2023年10月25日
匹配结果2: 2023年10月26日
re.search() 或 re.match() 返回的 Match 对象的方法
| 方法/属性 | 作用 | 
|---|---|
.group() | 获取匹配到的字符串内容。支持分组提取,如 .group(1) 获取第一个分组。 | 
.start() | 返回匹配字符串的 起始位置索引。 | 
.end() | 返回匹配字符串的 结束位置索引(不含该索引)。 | 
.span() | 返回一个元组 (start, end),表示匹配内容的范围。 | 
.pos | 匹配的起始搜索位置,通常默认是 0。 | 
.endpos | 匹配的结束搜索位置,通常默认是原始字符串末尾。 | 
.re | 用于匹配的正则表达式对象。 | 
.string | 原始未处理的字符串(即正则表达式被用来匹配的整个字符串)。 | 
示例:
import re
text = "今天是2023年10月25日,明天是2023年10月26日。"
# 搜索匹配项
match = re.search(r"(\d{4})年(\d{2})月(\d{2})日", text)
if match:
    print("匹配内容:", match.group())        # 整体匹配
    print("匹配年份:", match.group(1))       # 第一个分组
    print("匹配月份:", match.group(2))       # 第二个分组
    print("匹配日期:", match.group(3))       # 第三个分组
    print("匹配起始位置:", match.start())    # 起始索引
    print("匹配结束位置:", match.end())      # 结束索引
    print("匹配范围:", match.span())         # 开始和结束范围
    print("正则对象:", match.re)            # 正则表达式对象
    print("原始字符串:", match.string)       # 被匹配的字符串
输出:
匹配内容: 2023年10月25日
匹配年份: 2023
匹配月份: 10
匹配日期: 25
匹配起始位置: 3
匹配结束位置: 15
匹配范围: (3, 15)
正则对象: re.compile('(\\d{4})年(\\d{2})月(\\d{2})日')
原始字符串: 今天是2023年10月25日,明天是2023年10月26日。
                                                        发布者:LJH,转发请注明出处:https://www.ljh.cool/43164.html