在 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