第36课:Python|正则表达式大全【元字符、匹配规则、re模块全场景实战】

在这里插入图片描述

文章目录


📖 开篇导读

在之前的课程中,我们学习了字符串的各种操作:查找、替换、分割、拼接等。但是,当面对复杂的文本匹配需求时,例如“匹配一个邮箱地址”、“提取日志中的所有IP地址”、“验证手机号格式”,普通的字符串方法就显得力不从心了。这时,就需要用到正则表达式(Regular Expression)

正则表达式是一种用于描述字符串模式的“微型语言”。它通过特定的符号组合,定义一套匹配规则,可以在海量文本中快速查找、替换、提取符合特定模式的子串。几乎所有主流编程语言都支持正则表达式,Python提供了re模块来实现。

💡 工作场景

  • 日志分析:从Nginx/Apache日志中提取IP、URL、状态码。
  • 数据清洗:去除HTML标签、提取数字、替换敏感词。
  • 表单验证:校验邮箱、手机号、身份证号等格式。
  • 爬虫:提取网页中的链接、标题、价格信息。
  • 自然语言处理:分词、匹配特定语法结构。

本课将系统讲解正则表达式的基础语法、re模块的核心函数、高级技巧(贪婪/非贪婪、分组、反向引用、编译标志),并通过丰富的实战案例让你彻底掌握正则表达式。

学完本课,你将能够用几行正则表达式,解决原本可能需要数十行字符串操作的复杂问题。


🎯 学习目标

目标编号具体掌握内容对应面试/工作价值
1️⃣理解正则表达式的基本元字符及作用(.*+?[]()等)编写基础匹配模式
2️⃣掌握re模块的核心函数(matchsearchfindallfinditersubsplit实际应用中的工具选择
3️⃣理解贪婪模式与非贪婪模式,能根据需要切换避免匹配过多字符
4️⃣掌握分组与捕获,以及反向引用\1提取子串
5️⃣掌握编译标志re.Ire.Sre.M等)增强模式灵活性
6️⃣通过实战案例(邮箱验证、HTML标签提取、日志解析)提升应用能力解决实际问题

🔥 面试考点:“写出匹配邮箱的正则表达式”“re.matchre.search的区别”“贪婪与非贪婪的区别”“如何提取括号内的内容?”


📚 知识点理论精讲

一、正则表达式语法基础

正则表达式由普通字符(如字母、数字)和特殊字符(元字符)组成。元字符是具有特殊含义的符号。

1.1 常用元字符

元字符含义示例
.匹配除换行符以外的任意单个字符a.c 匹配 abca c
^匹配字符串开头^Hello 匹配以Hello开头的字符串
$匹配字符串结尾end$ 匹配以end结尾的字符串
*前一个字符出现0次或多次ab* 匹配 aababb
+前一个字符出现1次或多次ab+ 匹配 ababb,不匹配 a
?前一个字符出现0次或1次colou?r 匹配 colorcolour
{n}前一个字符恰好出现n次\d{3} 匹配三个数字
{n,}至少n次\d{3,} 匹配三个及以上数字
{n,m}出现n到m次\d{2,4} 匹配2-4个数字
[]匹配中括号内任意一个字符[aeiou] 匹配任意一个元音字母
|或,匹配左边或右边cat|dog 匹配 cat 或 dog
()分组,捕获子模式(\d{3})-(\d{4}) 捕获两组数字
\转义字符\. 匹配点号本身

1.2 预定义字符集

简写含义等价于
\d数字[0-9]
\D非数字[^0-9]
\w单词字符(字母、数字、下划线)[a-zA-Z0-9_]
\W非单词字符[^a-zA-Z0-9_]
\s空白字符(空格、制表符、换行等)[ \t\n\r\f\v]
\S非空白字符[^ \t\n\r\f\v]
\b单词边界\bword\b 匹配完整单词
\B非单词边界

1.3 字符集合取反

[]内使用^表示取反:[^0-9] 匹配非数字字符。

1.4 贪婪与非贪婪

默认情况下,量词(*+{n,m})是贪婪的,会匹配尽可能多的字符。在量词后加?变为非贪婪(最小匹配)。

import re
text = "<html><head></head></html>"
print(re.findall(r'<.*>', text))    # 贪婪: ['<html><head></head></html>']
print(re.findall(r'<.*?>', text))   # 非贪婪: ['<html>', '<head>', '</head>', '</html>']

二、Python re 模块核心函数

2.1 re.compile()

将正则表达式编译成模式对象,提高效率(尤其在多次使用时)。

pattern = re.compile(r'\d+')

2.2 re.match(pattern, string, flags)

从字符串开头匹配,成功返回匹配对象,否则返回None

m = re.match(r'\d+', '123abc')
if m:
    print(m.group())   # '123'

2.3 re.search(pattern, string, flags)

扫描整个字符串,返回第一个匹配,否则None

m = re.search(r'\d+', 'abc123def')
print(m.group())   # '123'

2.4 re.findall(pattern, string, flags)

返回所有非重叠匹配的字符串列表或元组列表(有分组时)。

re.findall(r'\d+', 'a1b22c333')  # ['1', '22', '333']

2.5 re.finditer(pattern, string, flags)

返回迭代器,每个元素是匹配对象,适合大文本。

for m in re.finditer(r'\d+', 'a1b22c333'):
    print(m.group())

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

替换匹配的子串。repl可以是字符串或函数。

re.sub(r'\d+', 'X', 'a1b22c333')   # 'aXbXcX'

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

按匹配模式分割字符串,返回列表。

re.split(r'[,;]', 'a,b;c')   # ['a', 'b', 'c']

三、匹配对象的方法

match()search()成功时,返回Match对象,常用方法:

  • group():返回匹配的整个字符串。
  • group(n):返回第n个分组的子串。
  • groups():返回所有分组(元组)。
  • groupdict():返回命名分组组成的字典。
  • start()end()span():匹配的起始、结束索引。
m = re.search(r'(\d{3})-(\d{4})', '电话: 123-4567')
print(m.group())     # '123-4567'
print(m.group(1))    # '123'
print(m.group(2))    # '4567'
print(m.groups())    # ('123', '4567')

四、分组与反向引用

4.1 普通分组 ()

括号内的表达式作为一个分组,可以提取或引用。

4.2 非捕获分组 (?:...)

只分组,不捕获,不保存为单独的组。

re.search(r'(?:https?://)(\w+)', 'http://python.org').group(1)  # 'python'

4.3 命名分组 (?P<name>...)

给分组起名字,方便引用。

m = re.search(r'(?P<year>\d{4})-(?P<month>\d{2})', '2025-03')
print(m.group('year'))   # '2025'

4.4 反向引用 \number

在正则表达式内部引用之前捕获的分组。

# 匹配重复的单词
re.search(r'\b(\w+)\s+\1\b', 'hello hello world').group()  # 'hello hello'

五、编译标志(flags)

标志可以改变正则表达式的行为,可以组合使用(re.I | re.M)。

标志缩写作用
re.IGNORECASEre.I忽略大小写
re.MULTILINEre.M多行模式,^$匹配每行的开头/结尾
re.DOTALLre.S使.匹配换行符
re.UNICODEre.U默认启用,让\w等匹配Unicode字符
re.VERBOSEre.X允许正则表达式内写注释和换行
pattern = re.compile(r'''
    \d{3}   # 区号
    -       # 分隔符
    \d{4}   # 号码
''', re.VERBOSE)

六、常见模式举例

需求正则表达式
邮箱地址\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+
IPv4地址\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b
手机号(中国)1[3-9]\d{9}
日期 YYYY-MM-DD\d{4}-\d{2}-\d{2}
URLhttps?://[^\s]+

💻 代码案例实操

案例1:基础匹配与查找

"""
basic_regex.py
演示match、search、findall、finditer
"""

import re

text = "Python 3.9 发布于 2020-10-05,Python 3.10 发布于 2021-10-04"

# match 从开头匹配
m = re.match(r'Python', text)
print("match:", m.group() if m else None)  # Python

# search 查找第一个
m = re.search(r'(\d{4})-(\d{2})-(\d{2})', text)
print("search:", m.group())                # 2020-10-05
print("年:", m.group(1), "月:", m.group(2), "日:", m.group(3))

# findall 全部匹配
dates = re.findall(r'\d{4}-\d{2}-\d{2}', text)
print("findall:", dates)                  # ['2020-10-05', '2021-10-04']

# finditer 迭代器
for m in re.finditer(r'Python (\d\.\d)', text):
    print(f"版本: {m.group(1)}, 位置: {m.span()}")

案例2:替换与分割

"""
sub_split_demo.py
演示sub和split的用法
"""

import re

text = "姓名: 张三, 年龄: 25, 城市: 北京"

# 替换数字为 *
masked = re.sub(r'\d+', '*', text)
print(masked)   # 姓名: 张三, 年龄: *, 城市: 北京

# 用正则分割
items = re.split(r'[,:]', text)
print([item.strip() for item in items])  # ['姓名', '张三', '年龄', '25', '城市', '北京']

# 使用函数进行动态替换
def add_one(match):
    num = int(match.group())
    return str(num + 1)

result = re.sub(r'\d+', add_one, "版本 3.9 -> 4.0")
print(result)   # 版本 4.10 -> 5.0(注意替换后小数部分未保持,仅示范)

案例3:邮箱验证

"""
email_validation.py
验证邮箱格式(简化版)
"""

import re

def is_valid_email(email):
    # 简单模式:允许字母数字+._-,@后域名有效
    pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
    return re.match(pattern, email) is not None

emails = ["user@example.com", "user.name@domain.co.uk", "invalid@.com", "user@domain"]
for e in emails:
    print(f"{e}: {is_valid_email(e)}")

案例4:提取日志中的IP和状态码

"""
log_extract.py
从Nginx日志行提取IP、请求方法、URL、状态码
"""

import re

log_line = '192.168.1.1 - - [10/Oct/2025:13:55:36 +0800] "GET /index.html HTTP/1.1" 200 2326'

# 解析日志
pattern = r'(\d+\.\d+\.\d+\.\d+) - - \[.*?\] "(\w+) (.*?) HTTP/.*?" (\d+)'
match = re.search(pattern, log_line)
if match:
    ip = match.group(1)
    method = match.group(2)
    url = match.group(3)
    status = match.group(4)
    print(f"IP: {ip}, Method: {method}, URL: {url}, Status: {status}")

案例5:贪婪与非贪婪对比

"""
greedy_vs_non_greedy.py
演示量词的贪婪与非贪婪
"""

import re

html = "<div><p>段落1</p><p>段落2</p></div>"

# 贪婪:尽可能匹配更多字符
greedy = re.search(r'<.*>', html)
print("贪婪:", greedy.group())   # 整个字符串

# 非贪婪:最小匹配
non_greedy = re.search(r'<.*?>', html)
print("非贪婪:", non_greedy.group())  # <div>

# 提取所有标签
tags = re.findall(r'<.*?>', html)
print("所有标签:", tags)  # ['<div>', '<p>', '</p>', '<p>', '</p>', '</div>']

案例6:分组与反向引用——匹配重复单词

"""
backreference_demo.py
使用反向引用查找重复单词
"""

import re

text = "This is is a test test sentence."

# 匹配重复的单词(单词边界内,至少重复一次)
pattern = r'\b(\w+)\s+\1\b'
matches = re.findall(pattern, text)
print("重复单词:", matches)  # ['is', 'test']

# 替换重复为单个单词
result = re.sub(pattern, r'\1', text)
print("修正后:", result)    # "This is a test sentence."

案例7:电话号码提取(支持多种格式)

"""
phone_extract.py
提取文本中的中国手机号(简单版)和固定电话
"""

import re

text = "联系我: 13812345678 或 010-12345678, 也可拨打 139-0000-1111"

# 手机号:1开头,第二位3-9,后面9位数字
mobile_pattern = r'1[3-9]\d{9}'
mobiles = re.findall(mobile_pattern, text)
print("手机号:", mobiles)

# 固定电话:区号-号码,区号3-4位,号码7-8位
phone_pattern = r'\d{3,4}-\d{7,8}'
phones = re.findall(phone_pattern, text)
print("固定电话:", phones)

# 综合提取所有联系方式
all_contacts = re.findall(r'1[3-9]\d{9}|\d{3,4}-\d{7,8}', text)
print("所有号码:", all_contacts)

案例8:HTML标签清理(提取文本)

"""
clean_html.py
移除HTML标签,提取纯文本
"""

import re

html = """
<h1>标题</h1>
<p>这是一个<b>重要</b>的段落。</p>
<a href="http://example.com">链接</a>
"""

# 移除标签
clean = re.sub(r'<[^>]+>', '', html)
print("清理后:\n", clean)

# 提取所有链接的href属性
links = re.findall(r'href=["\']([^"\']+)["\']', html)
print("链接:", links)

案例9:使用re.VERBOSE编写复杂正则

"""
verbose_regex.py
使用VERBOSE标志写可读的正则表达式
"""

import re

# 匹配日期格式 YYYY-MM-DD,但要求月份01-12,日期01-31
date_pattern = re.compile(r'''
    ^(?P<year>\d{4})   # 年份
    -(?P<month>0[1-9]|1[0-2])   # 月份01-12
    -(?P<day>0[1-9]|[12][0-9]|3[01])   # 日期01-31
    $''', re.VERBOSE)

dates = ["2025-03-15", "2025-02-30", "2025-13-01"]
for d in dates:
    m = date_pattern.match(d)
    if m:
        print(f"{d} 有效,年={m.group('year')} 月={m.group('month')} 日={m.group('day')}")
    else:
        print(f"{d} 无效")

案例10:使用re.compile提高性能(处理大文件)

"""
compile_performance.py
预编译正则表达式,在循环中重复使用
"""

import re

# 预编译
ip_pattern = re.compile(r'\b(?:\d{1,3}\.){3}\d{1,3}\b')

def extract_ips(text):
    return ip_pattern.findall(text)

# 模拟处理大文本
with open('large_log.txt', 'r') as f:
    for line in f:
        ips = extract_ips(line)
        # 处理ips...

# 预编译显著提高重复匹配的效率

⚠️ 易错点避坑总结

序号坑点描述后果解决方案
1忘记转义特殊字符(如.*?等)匹配结果不准确在特殊字符前加\,或使用re.escape()
2使用match而不是search只能匹配开头,中间内容匹配不到明确需求,使用search扫描全文
3贪婪匹配导致匹配过多字符<.*>匹配了整个HTML而不是一个标签使用非贪婪<.*?>
4分组捕获时未使用(?:)导致生成多余分组影响groups()结果不需要捕获时用(?:...)
5\d匹配数字时包含非ASCII数字(如阿拉伯数字)国际化问题使用re.ASCII标志或[0-9]
6使用re.sub时替换字符串中不处理分组引用替换内容不包含匹配的组repl中使用\1,或使用函数
7在循环内编译正则表达式性能低下使用re.compile一次,反复使用
8忘记标志组合使用re.I | re.M默认标志不生效用`
9多行模式re.M^$的行为变化匹配了每行而非整个字符串明确是否需要
10正则表达式过于复杂,难以维护代码可读性差,易出错使用re.VERBOSE写注释和换行

📝 课后实战练习题

第1题:验证手机号

编写正则表达式,验证中国大陆手机号(以1开头,第二位3-9,共11位数字)。测试用例:13812345678(有效)、12345678901(无效)、1381234567(无效)。

第2题:提取HTML中的所有图片URL

给定HTML字符串,提取所有<img>标签中的src属性值。考虑属性值可能使用单引号、双引号或无引号。

第3题:分割句子

给定文本,以句号、感叹号、问号后面的空格分割(但不破坏省略号)。用re.split实现。

第4题:日期格式统一

将文本中的日期格式从DD/MM/YYYY转换为YYYY-MM-DD。例如15/03/2025转换为2025-03-15。使用re.sub和分组。

第5题:词频统计中的单词清洗

给定文本,使用正则表达式提取所有单词(只包含字母,忽略数字和标点),转为小写,统计频率。

第6题:有效的数字字符串

编写正则,匹配科学计数法表示的数字,如1.23e-4-5.6E+7,包括整数、小数、负号、指数部分可选。验证"123", "12.34", "1e2", "-0.5E-3"

第7题:用户名验证

用户名要求:字母开头,后跟字母、数字、下划线,长度6-20。编写正则并测试。

第8题:日志过滤(综合)

给定包含多行日志的文件,每行格式为:[时间] 级别 消息。提取所有ERROR级别的行,并提取其中的错误代码(形如ERR123)。

🔜 下节课预告

正则表达式是文本处理的利器。下一节课我们将学习数据解析与序列化:JSON、CSV、XML等常见数据格式的处理方法。

第37课:JSON/CSV/XML数据解析与序列化全套实战教程

内容包括:

  • json模块:dump/loaddumps/loads
  • csv模块:reader/writerDictReader/DictWriter
  • xml.etree.ElementTree:解析与生成XML
  • 实战:API响应处理、Excel互转、配置文件读写

数据交换格式是现代编程的必备知识,学会它们你将能处理各种外部数据。

🌟 学习鼓励:正则表达式学习曲线较陡,但一旦掌握,处理字符串的效率将成倍提升。不要死记硬背,多动手练习,尤其是findallsub和分组提取。建议使用在线正则测试工具(如regex101.com)辅助学习。坚持练习,你会爱上这门强大的技术!


🔗《50节课 Python 从入门到精通》系列课程导航

去订阅

🌟 感谢您耐心阅读到这里!
💡 如果本文对您有所启发欢迎:
👍 点赞📌 收藏 📤 分享给更多需要的伙伴。
🗣️ 期待在评论区看到您的想法, 共同进步。
🔔 关注我,持续获取更多干货内容~
🤗 我们下篇文章见~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Thomas.Sir

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值