Python正则表达式入门:从匹配查找替换到分组与贪婪模式实战
简介:这份PDF资料面向正在学习Python、希望系统掌握正则表达式的开发者与编程初学者,聚焦文本匹配、提取与替换等常见场景,帮助读者从语法基础过渡到实际应用。资源包内共1个PDF文件,压缩包大小约661KB,内容以图文讲解为主,便于在电脑或移动设备上随时翻阅。目前已有260人学习下载,适合作为Python正则入门的轻量级参考材料。资料围绕正则表达式核心知识点展开,涵盖元字符、字符集、量词、分组、贪婪与非贪婪匹配、常用函数及典型示例,读者可借此理解匹配规则、梳理易错点,并逐步建立从模式编写到结果验证的完整思路。对于需要处理日志分析、表单校验、爬虫数据清洗等任务的Python学习者,这份文档可作为随查随用的知识手册,帮助快速定位语法细节并巩固实践能力。
1. 正则表达式到底在干嘛:从“在一堆文字里找东西”说起
你有没有过这种经历:在一份几千行的日志文件里,想找出所有包含某个关键词的行;或者在一段乱七八糟的文本里,只想把手机号、邮箱、日期挑出来。手动翻?眼睛会瞎。用普通的查找替换?又不够灵活,因为你要找的东西“长得不完全一样”——手机号有 11 位数字,邮箱有@和域名,日期可能是2024-01-01也可能是2024/1/1。
正则表达式就是干这个的。它是一套用来描述“文字长什么样”的规则,你把这套规则写出来,程序就能拿着它去文本里自动匹配、查找、替换。打个比方,普通查找像是拿一张照片去人群里找一模一样的人;正则表达式则是拿一份“特征清单”——身高一米八、戴眼镜、穿红衣服——去找符合条件的人,哪怕换件衣服、换个发型,只要特征对得上,就能认出来。
Python 里的正则表达式,就是把这套规则用 Python 代码写出来,配合re模块使用。搞懂它,你处理文本的效率会从“手工挑豆子”变成“机器筛沙子”。这也是为什么它成了 Python 学习路上绕不开的一关。
2. 为什么正则表达式值得学:文本处理的“万能钥匙”
2.1 没有正则的日子:字符串方法的天花板
Python 自带的字符串方法其实不少:find()找位置、replace()换内容、split()切分、startswith()判断开头。处理简单任务够用,但一遇到“模糊条件”就露怯。
举个例子,你要从一段文本里找出所有日期。用find()只能找一个固定字符串,比如"2024-01-01",换一天就找不到。用split()按空格切,日期可能和别的词粘在一起。你可能会写一堆if和循环,代码又长又容易翻车。
正则表达式解决的就是“模式匹配”问题。你描述的不是一个具体字符串,而是一类字符串的“形状”。比如\d{4}-\d{2}-\d{2}表示“四个数字,一个横杠,两个数字,一个横杠,两个数字”。不管具体是哪一年哪一月,只要形状对,就能匹配上。
提示:正则表达式不是 Python 独有的,很多语言都支持。但 Python 的
re模块语法清晰、文档齐全,是入门正则的好选择。
2.2 正则的四个核心能力:匹配、查找、替换、切分
Python 的re模块围绕四个动作展开,对应四个常用函数:
| 函数 | 作用 | 典型场景 |
|---|---|---|
re.match() | 从字符串开头匹配 | 校验输入格式,比如手机号 |
re.search() | 在任意位置找第一个匹配 | 从日志里找第一条错误 |
re.findall() | 找出所有匹配,返回列表 | 提取所有邮箱、链接 |
re.sub() | 把匹配到的内容替换掉 | 脱敏、清洗数据 |
这四个函数覆盖了文本处理的大部分需求。match和search的区别在于起点:match只认开头,search全串扫描。很多人第一次用会搞混,后面会专门讲。
再打个比方:match像是检查一篇文章的第一句话是不是标题;search像是整篇文章翻一遍,找到第一个符合条件的地方就停;findall是把所有符合条件的都抄下来;sub是拿橡皮擦掉再写上新的。
2.3 元字符:正则的“字母表”
正则表达式由普通字符和元字符组成。普通字符就是abc、123这些,匹配自己。元字符则有特殊含义,是正则的灵魂。常见的元字符不多,但组合起来变化无穷:
.匹配任意一个字符(除了换行)\d匹配一个数字,等价于[0-9]\w匹配字母、数字、下划线\s匹配空白字符(空格、制表符等)*前面的内容出现 0 次或多次+前面的内容出现 1 次或多次?前面的内容出现 0 次或 1 次{n,m}前面的内容出现 n 到 m 次[]字符集合,匹配其中任意一个^匹配开头$匹配结尾
这些符号单独看都不难,难的是组合起来表达复杂规则。比如^\d{11}$表示“从头到尾正好 11 个数字”,可以用来校验手机号格式。\w+@\w+\.\w+可以粗略匹配邮箱。
注意:正则表达式是“区分大小写”的,除非你加上
re.IGNORECASE标志。写规则时别忘了这一点。
3. 正则表达式怎么用:从写规则到出结果
3.1 编译与匹配:re.compile 和 match/search 的配合
Python 里用正则,有两种方式。一种是直接调用re.match()、re.search()等函数,每次传入规则字符串。另一种是先用re.compile()把规则编译成一个正则对象,再调用对象的方法。
import re # 方式一:直接调用 result = re.search(r'\d+', '订单号 12345') print(result.group()) # 输出 12345 # 方式二:先编译 pattern = re.compile(r'\d+') result = pattern.search('订单号 12345') print(result.group()) # 输出 12345两种方式结果一样。区别在于:如果同一个规则要反复用很多次,先编译效率更高,因为编译过程只做一次。如果只是临时用一下,直接调用更省事。
match和search返回的是匹配对象,不是字符串。要拿到具体内容,得用.group()。如果没匹配到,返回None。所以实际写代码时,通常要先判断:
import re text = '联系邮箱:hello@example.com' pattern = re.compile(r'\w+@\w+\.\w+') match = pattern.search(text) if match: print('找到邮箱:', match.group()) else: print('没有找到邮箱')这段代码先编译规则,再搜索,最后判断结果。\w+匹配字母数字下划线,@是普通字符,\.匹配真正的点号(因为.在正则里是元字符,要加反斜杠转义)。
3.2 分组与捕获:把匹配结果拆开用
正则表达式用圆括号()表示分组。分组有两个作用:一是把一部分规则当成整体,比如(ab)+表示ab重复多次;二是捕获匹配到的内容,方便后续提取。
import re text = '出生日期:1990-05-20' pattern = re.compile(r'(\d{4})-(\d{2})-(\d{2})') match = pattern.search(text) if match: print('完整匹配:', match.group(0)) print('年:', match.group(1)) print('月:', match.group(2)) print('日:', match.group(3))group(0)是完整匹配,group(1)是第一个括号里的内容,以此类推。这样就能把日期拆成年、月、日分别使用。分组是正则表达式从“找到”到“提取”的关键一步。
如果括号很多又不想全部捕获,可以用(?:...)表示非捕获分组,只分组不编号。这在写复杂规则时很有用,避免编号混乱。
3.3 贪婪与非贪婪:为什么匹配结果忽长忽短
正则表达式默认是“贪婪”的,意思是*、+、{n,m}会尽可能多地匹配字符。比如<.*>去匹配<a><b>,结果会匹配整个<a><b>,而不是<a>。因为.*会一直吃到最后一个>。
要改成“非贪婪”,在量词后面加?,变成.*?。这样它会尽可能少地匹配,遇到第一个>就停。
import re text = '<a><b>' print(re.search(r'<.*>', text).group()) # 输出 <a><b> print(re.search(r'<.*?>', text).group()) # 输出 <a>贪婪和非贪婪是初学者最容易翻车的地方之一。写规则时如果发现匹配结果比预期长,先检查是不是该加?。
提示:不是所有场景都要非贪婪。提取 HTML 标签内容时,非贪婪通常更符合直觉;但处理嵌套结构时,正则本身就不擅长,该用解析库就用解析库。
3.4 替换与切分:re.sub 和 re.split 的实用场景
re.sub()用来替换匹配到的内容。它的强大之处在于,替换内容可以引用分组。
import re text = '2024-01-01 和 2024-12-31' # 把日期格式从 YYYY-MM-DD 改成 DD/MM/YYYY result = re.sub(r'(\d{4})-(\d{2})-(\d{2})', r'\3/\2/\1', text) print(result) # 输出 01/01/2024 和 31/12/2024这里\1、\2、\3分别代表第一、二、三个分组的内容。替换字符串里用反斜杠加数字引用分组,就能实现格式重排。
re.split()则按正则规则切分字符串,比普通split()更灵活。比如按“逗号、分号、空格”任意一种切分:
import re text = 'apple, banana; orange grape' result = re.split(r'[,;\s]+', text) print(result) # 输出 ['apple', 'banana', 'orange', 'grape'][,;\s]+表示“逗号、分号、空白字符中的任意一种,出现一次或多次”。这样不管分隔符是哪种、有几个,都能正确切分。
4. 常见误区与易混概念:别在这些地方栽跟头
4.1 match 和 search 到底差在哪
易混点:两个函数名字都像“查找”,返回值也类似,很多人随手用。
为什么混:match和search在简单例子里结果一样,比如规则是\d+、文本是123abc,两者都匹配。但一旦文本开头不是数字,match就返回None,search还能找到。
怎么区分:match只从字符串第一个字符开始尝试匹配,相当于自带^;search会扫描整个字符串,找到第一个匹配就返回。校验格式用match,提取内容用search。
import re text = 'abc123' print(re.match(r'\d+', text)) # None print(re.search(r'\d+', text)) # <re.Match object; span=(3, 6), match='123'>4.2 转义字符:什么时候要加反斜杠
易混点:.、*、+、?、(、)、[、]这些符号在正则里有特殊含义,但有时候你就是想匹配符号本身。
为什么混:写规则时脑子里想的是“匹配一个点”,但正则把.解释成“任意字符”,结果匹配到不该匹配的东西。
怎么区分:想匹配元字符本身,前面加反斜杠。比如\.匹配真正的点号,\*匹配星号,\(匹配左括号。在 Python 字符串里,反斜杠本身也要转义,所以通常用原始字符串r'...'来写正则,避免双重转义。
import re print(re.search(r'\.', 'a.b').group()) # 输出 . print(re.search(r'\.', 'axb')) # None,因为 axb 里没有真正的点4.3 字符集与分组的区别:[] 和 () 不是一回事
易混点:[abc]和(abc)看起来都是把几个字符放一起,但含义完全不同。
为什么混:两者都用了括号,视觉上像同类结构。
怎么区分:[]是字符集,匹配其中任意一个字符。[abc]表示匹配a或b或c中的一个。()是分组,把abc当成一个整体,匹配的是连续的abc三个字符。[abc]+匹配的是a、b、c任意组合的连续串;(abc)+匹配的是abc、abcabc这样的重复。
import re print(re.findall(r'[abc]+', 'abcabc')) # ['abcabc'] print(re.findall(r'(abc)+', 'abcabc')) # ['abc']4.4 贪婪匹配的坑:为什么结果总比想的长
易混点:.*看起来是“匹配任意内容”,但它的贪婪本性会让结果超出预期。
为什么混:直觉上觉得.*会匹配到第一个符合条件的就停,实际上它会一直吃到最后一个符合条件的字符。
怎么区分:需要“最短匹配”时,在量词后加?。.*?表示非贪婪,遇到第一个符合条件的就停。但也要注意,非贪婪不是万能的,有些场景需要更精确的规则,比如用[^>]*代替.*?来匹配标签内容。
import re text = '<div>内容1</div><div>内容2</div>' print(re.findall(r'<div>.*?</div>', text)) # 输出 ['<div>内容1</div>', '<div>内容2</div>']4.5 findall 和 finditer 的选择
易混点:两者都是找所有匹配,但返回形式不同。
为什么混:名字像,功能描述也像。
怎么区分:findall返回列表,元素是字符串或元组(有分组时)。finditer返回迭代器,元素是匹配对象,可以拿到位置信息。如果只需要内容,用findall;如果需要知道匹配在原文中的位置,用finditer。
import re text = 'a1 b2 c3' print(re.findall(r'\d', text)) # ['1', '2', '3'] for m in re.finditer(r'\d', text): print(m.group(), m.start()) # 输出:1 1;2 4;3 75. 什么时候该用正则,什么时候该收手
正则表达式很强大,但不是所有文本问题都该用它。用对了是利器,用错了是给自己挖坑。
先看一个具体例子。假设你要从一段文本里提取所有电话号码。电话号码格式可能是138-1234-5678、13812345678、(138) 1234-5678。用正则可以写一个相对宽松的规则:
import re text = ''' 联系方式:138-1234-5678 备用:13912345678 座机:(010) 8888-6666 ''' pattern = re.compile(r'\(?\d{3,4}\)?[-.\s]?\d{3,4}[-.\s]?\d{4}') results = pattern.findall(text) for r in results: print(r)这个规则能匹配多种格式,但也不是万能的。如果号码格式再复杂一些,规则会变得又长又难维护。这时候就要考虑:是不是该用更专业的解析工具,或者先做数据清洗再匹配。
适合用正则的场景:格式相对固定、规则清晰、不需要理解语义。比如校验邮箱格式、提取日期、替换敏感词、按多种分隔符切分。
不适合用正则的场景:嵌套结构(比如 HTML、JSON)、需要递归匹配、规则复杂到没人能看懂。HTML 用解析库,JSON 用json模块,别硬扛。
注意:正则表达式写出来容易,读起来难。如果一条规则超过一行还看不懂,加注释或者拆成多步。代码是给人看的,顺便给机器执行。
从那以后我每次写正则,都强制自己先拿几条测试数据跑一遍,确认匹配范围符合预期再放进正式代码。尤其是贪婪模式和非贪婪模式,不测根本不知道会匹配到哪。希望这些内容能帮你少走一点弯路,把正则表达式真正用起来。
本文还有配套的精品资源,点击获取