Python 正则表达式

在 Python 中,引入正则表达式功能需要使用内置模块 re,只需要一行代码:

import re

基础语法与字符匹配

1. 普通字符

普通字符就是你写什么就匹配什么。

例子:正则 abc 只匹配“abc”这三个连续字符。

2. 特殊字符(元字符)

表达式含义匹配举例
.任意一个字符(除了换行符)a.c 可匹配 "abc""a9c""a c"
\d任意数字,等价于 [0-9]\d\d\d 匹配 123456
\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] 表示可以是 abc 中的任一个。

范围写法:[0-9][a-z][A-Z]

否定字符集:[^abc] 表示不能是 abc

举例:

表达式含义匹配示例
[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

Like (0)
LJH的头像LJH
Previous 2025年7月17日 下午2:49
Next 2025年7月23日 下午3:33

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注