1. 引言正则表达式Regular Expression简称 regex是一种强大的文本匹配工具它使用特定的模式字符串来描述、匹配和操作文本。无论是数据清洗、日志分析、表单验证还是爬虫提取正则表达式都能大幅提升处理文本的效率。Python 内置的re模块提供了完整的正则表达式支持。本文将从零开始带你系统地掌握 Python 中正则表达式的使用方法。2. 正则表达式基础2.1 什么是正则表达式正则表达式本质上是一段描述「字符模式」的字符串。例如模式hello就表示要匹配文本中出现的hello这个连续字符序列。2.2 元字符正则表达式中某些字符具有特殊含义称为元字符元字符含义示例匹配结果.匹配除换行符外的任意字符a.cabc、a1c、a c\d匹配数字\d123\w匹配字母、数字、下划线\whello_123\s匹配空白字符\s空格、制表符等*匹配前一个字符 0 次或多次ab*a、ab、abb匹配前一个字符 1 次或多次abab、abb?匹配前一个字符 0 次或 1 次colou?rcolor、colour{n}恰好匹配 n 次\d{4}2024{n,m}匹配 n 到 m 次\d{2,4}12、123、1234^匹配字符串开头^Hello以 Hello 开头$匹配字符串结尾world$以 world 结尾[...]字符集匹配其中任意一个[aeiou]任意元音字母[^...]排除字符集[^0-9]非数字字符\转义字符\.字面意义的点2.3 常用字符类\d# 数字等价于 [0-9]\D# 非数字等价于 [^0-9]\w# 单词字符等价于 [a-zA-Z0-9_]\W# 非单词字符\s# 空白字符等价于 [ \t\n\r\f\v]\S# 非空白字符3. Python 的 re 模块使用正则表达式前先导入模块importrere模块提供了两大类操作方式模块级函数直接使用re.match()、re.search()等适合简单场景。编译后的 Pattern 对象使用re.compile()预编译模式适合多次复用同一模式的场景性能更好。importre# 方式一模块级函数resultre.search(r\d,订单号12345)# 方式二编译后使用patternre.compile(r\d)resultpattern.search(订单号12345)print(result.group())# 输出12345提示在正则字符串前加上r前缀raw string可以避免反斜杠被 Python 当作转义字符处理这是推荐写法。4. 常用函数详解4.1 match()re.match()从字符串开头开始匹配如果开头不匹配则返回None。importre resultre.match(r\d,123abc)print(result.group())# 输出123# 开头不匹配时返回 None直接调用 .group() 会抛出 AttributeErrorresultre.match(r\d,abc123)# 安全写法先判断是否为 NoneifresultisnotNone:print(result.group())else:print(未匹配到数字)# 输出未匹配到数字4.2 search()re.search()在整个字符串中搜索第一个匹配位置不要求从开头匹配。importre resultre.search(r\d,订单号12345)print(result.group())# 输出12345# 未找到匹配时返回 None直接调用 .group() 会导致 AttributeErrorresultre.search(r\d,没有数字)# 安全写法利用 Match 对象的真值判断ifresult:print(result.group())else:print(未找到匹配内容)# 输出未找到匹配内容4.3 findall()re.findall()返回所有匹配的结果以列表形式返回。importre text我的电话是 13800138000备用 13900139000phonesre.findall(r1[3-9]\d{9},text)print(phones)# 输出[13800138000, 13900139000]4.4 finditer()re.finditer()与findall()类似但返回一个迭代器每个元素是 Match 对象可以获取匹配位置等更多信息。importre text第1次第12次第345次formatchinre.finditer(r\d,text):print(f匹配值{match.group()}, 位置{match.span()})4.5 sub()re.sub()用于替换匹配到的内容。importre text我的手机号是 13800138000maskedre.sub(r\d{11},***********,text)print(masked)# 输出我的手机号是 ***********# 使用函数进行动态替换defmask_phone(match):phonematch.group()returnphone[:3]****phone[-4:]maskedre.sub(r1[3-9]\d{9},mask_phone,text)print(masked)# 输出我的手机号是 138****80004.6 split()re.split()使用正则表达式作为分隔符来分割字符串。importre textapple,banana;orange pearfruitsre.split(r[,;\s],text)print(fruits)# 输出[apple, banana, orange, pear]5. 模式语法进阶5.1 贪婪与非贪婪匹配默认情况下量词是贪婪的会尽可能多地匹配加上?后变为非贪婪尽可能少地匹配。importre texthtmlbody内容/body/html# 贪婪匹配greedyre.search(r.*,text)print(greedy.group())# 输出htmlbody内容/body/html# 非贪婪匹配lazyre.search(r.*?,text)print(lazy.group())# 输出html5.2 分组与捕获使用圆括号()可以创建分组分组可以被单独提取。importre text姓名张三年龄25patternr姓名(\w)年龄(\d)matchre.search(pattern,text)print(match.group(0))# 整个匹配姓名张三年龄25print(match.group(1))# 第一个分组张三print(match.group(2))# 第二个分组25print(match.groups())# 所有分组(张三, 25)命名分组使用(?Pname...)语法让代码更易读importre patternr姓名(?Pname\w)年龄(?Page\d)matchre.search(pattern,姓名李四年龄30)print(match.group(name))# 输出李四print(match.group(age))# 输出305.3 非捕获分组使用(?:...)可以创建不捕获的分组仅用于逻辑组织不占用分组编号。importre patternr(?:https?|ftp)://(\S)matchre.search(pattern,https://www.example.com)print(match.group(1))# 输出www.example.com5.4 反向引用使用\1、\2可以在模式内部引用前面捕获的分组。importre# 匹配重复出现的单词patternr\b(\w)\s\1\bmatchre.search(pattern,hello hello world)print(bool(match))# 输出True# 匹配重复的两个数字patternr(\d{2})-\1matchre.search(pattern,12-12)print(bool(match))# 输出True5.5 前瞻与后顾正向先行断言(?...)后面必须跟着指定模式负向先行断言(?!...)后面不能跟指定模式正向后行断言(?...)前面必须是指定模式负向后行断言(?!...)前面不能是指定模式importre text价格100元折扣价80元# 匹配后面跟着“元”的数字pricesre.findall(r\d(?元),text)print(prices)# 输出[100, 80]# 匹配前面是“价格”的数字pricere.search(r(?价格)\d,text)print(price.group())# 输出1006. 标志位Flagsre模块提供了多种标志位来改变匹配行为标志简写含义re.IGNORECASEre.I忽略大小写re.MULTILINEre.M^和$匹配每一行的开头结尾re.DOTALLre.S.也匹配换行符re.VERBOSEre.X允许在模式中写注释和空白importre# 忽略大小写resultre.findall(rpython,Python PYTHON python,re.I)print(result)# 输出[Python, PYTHON, python]# 多行模式text第一行\n第二行\n第三行linesre.findall(r^第\w,text,re.M)print(lines)# 输出[第一行, 第二行, 第三行]# DOTALL 模式text开始\n结束resultre.search(r开始.*结束,text,re.S)print(bool(result))# 输出True7. re.compile() 与性能优化如果同一个正则表达式需要多次使用建议先编译importre patternre.compile(r1[3-9]\d{9})# 多次复用编译后的模式phonespattern.findall(电话13800138000备用13900139000)maskedpattern.sub(***********,电话13800138000)编译后的 Pattern 对象拥有与模块级函数对应的方法如match()、search()、findall()、sub()、split()等同时还提供了一些额外属性importre patternre.compile(r1[3-9]\d{9})print(pattern.pattern)# 模式字符串print(pattern.flags)# 标志位print(pattern.groups)# 分组数8. 实战示例8.1 提取邮箱地址importre text请发送邮件到 supportexample.com 或 infotest.org# 使用非捕获分组匹配合法的邮箱地址patternre.compile(r[\w.-][\w-]\.[\w.])emailspattern.findall(text)print(emails)# 输出[supportexample.com, infotest.org]8.2 提取 URLimportre text官网https://www.example.com/path?query1备用ftp://files.example.org/datapatternre.compile(r(?:https?|ftp)://[^\s])urlspattern.findall(text)print(urls)8.3 清理日志中的时间戳importre logs[[2024-08-13 23:24:36] INFO 服务启动成功,[2024-08-13 23:25:01] ERROR 连接数据库失败,[2024-08-13 23:26:15] WARN 磁盘空间不足,]patternre.compile(r\[(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2})\] (\w) (.*))forloginlogs:matchpattern.search(log)ifmatch:date,time,level,messagematch.groups()print(f日期{date}时间{time}级别{level}内容{message})8.4 验证身份证号格式importredefvalidate_id_card(id_card):验证中国居民身份证号18 位格式patternr^\d{17}[\dXx]$returnbool(re.match(pattern,id_card))print(validate_id_card(110101199003077758))# Trueprint(validate_id_card(1234))# False8.5 数据清洗去除多余空白importre text 这是 一段 含有多余 空白的 文本 # 去除首尾空白并将内部连续空白压缩为单个空格cleanedre.sub(r\s, ,text).strip()print(cleaned)# 输出这是 一段 含有多余 空白的 文本8.6 综合案例分析批量解析订单信息前面的示例偏向单一功能的提取本节通过一个综合案例演示如何把命名分组、非贪婪匹配、re.S标志位与finditer()结合起来完成一条稍复杂的数据处理任务。需求从一段订单确认文本中批量提取每笔订单的订单编号、下单时间、金额和商品信息并输出为结构化字典。importre text 订单编号20240813001下单时间2024-08-13 23:26:19金额¥199.00商品Python 实战书籍 订单编号20240813002下单时间2024-08-13 23:30:01金额¥45.50商品机械键盘 订单编号20240813003下单时间2024-08-13 23:31:12金额¥12.00商品鼠标垫 patternre.compile(r订单编号(?Porder_id\d).*?r下单时间(?Pcreated_at\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}).*?r金额¥(?Pamount\d\.\d{2}).*?r商品(?Pproduct[\w ]),re.S)formatchinpattern.finditer(text):print(match.groupdict())运行结果{order_id: 20240813001, created_at: 2024-08-13 23:26:19, amount: 199.00, product: Python 实战书籍} {order_id: 20240813002, created_at: 2024-08-13 23:30:01, amount: 45.50, product: 机械键盘} {order_id: 20240813003, created_at: 2024-08-13 23:31:12, amount: 12.00, product: 鼠标垫}要点解析(?Porder_id\d)等命名分组让groupdict()输出的字典键名清晰可读便于后续处理。.*?使用非贪婪匹配只“跳过”字段之间不关心的分隔文本避免一次吞掉多条记录。re.S让.可以匹配换行符保证跨行文本也能被完整扫描。finditer()返回迭代器逐条产出匹配结果在日志或订单量较大时比一次性返回列表更省内存。在得到字典结果后还可以继续结合 Python 做数据统计amounts[float(m[amount])forminpattern.finditer(text)]print(f订单数量{len(amounts)})print(f订单总金额{sum(amounts):.2f})运行结果订单数量3 订单总金额256.50这个案例展示了正则表达式在实际业务中的典型用法先定义清晰的结构化模式再交给re模块批量提取最后用 Python 完成数值处理与统计。9. 常见问题与最佳实践9.1 使用 raw string始终使用r前缀编写正则模式避免反斜杠转义问题# 推荐patternr\d# 不推荐容易出错pattern\\d9.2 避免灾难性回溯正则表达式中嵌套量词可能导致严重的性能问题。例如(a)b在遇到大量a而没有b结尾时会指数级回溯。importre# 有风险的写法bad_patternre.compile(r(a)b)# 优化后的写法good_patternre.compile(rab)9.3 正则不是万能的正则适合处理规则明确的文本。对于 HTML/XML 等嵌套结构、JSON 等有严格语法的数据应使用专门的解析器如html.parser、json模块而不是硬套正则。9.4 善用 re.VERBOSE 提高可读性复杂的正则表达式可以使用re.VERBOSE模式配合注释提升可读性importre patternre.compile(r ^ # 字符串开头 (?Parea\d{3,4}) # 区号3 到 4 位数字 [- ] # 分隔符横杠或空格 (?Pnumber\d{7,8}) # 电话号码7 到 8 位数字 $ # 字符串结尾 ,re.VERBOSE)matchpattern.match(010-12345678)print(match.group(area))# 输出010print(match.group(number))# 输出123456789.5 常见陷阱与调试技巧初学者在使用正则表达式时经常因为一些细节导致匹配结果不符合预期。下面列举几个高频陷阱。陷阱一转义问题如果把正则写成普通字符串\b、\t等字符会被 Python 先按转义字符解释导致模式“悄悄变形”。importre# 错误普通字符串中的 \b 会被解释为退格符pattern\bword\bprint(re.search(pattern,a word here))# 输出None# 正确使用 raw string保留正则原有的边界语义patternr\bword\bprint(re.search(pattern,a word here))# 输出re.Match object; span(2, 6), matchword陷阱二贪婪匹配导致匹配范围过大默认量词是贪婪的会尽可能多地匹配。在提取标签、引号等内容时容易把整段内容都吞掉。importre textdiv第一个/divdiv第二个/div# 贪婪匹配把两个标签一起捕获matchre.search(rdiv.*/div,text)print(match.group())# 输出div第一个/divdiv第二个/div# 非贪婪匹配只匹配到第一个闭合标签matchre.search(rdiv.*?/div,text)print(match.group())# 输出div第一个/div陷阱三.默认不匹配换行符.默认不匹配换行符。如果文本跨行需要加上re.Sre.DOTALL标志位。importre text第一行\n第二行# 不匹配换行结果为 Noneresultre.search(r第一行.*第二行,text)print(result)# 输出None# 加上 re.S 后可跨行匹配resultre.search(r第一行.*第二行,text,re.S)print(bool(result))# 输出True调试技巧使用re.DEBUGPython 的re模块提供了re.DEBUG标志位它会把正则表达式的编译过程打印出来帮助我们确认模式被解析成了什么结构。importre# 打印模式解析后的内部结构re.compile(r\b\d{3}-\d{4}\b,re.DEBUG)运行后会输出类似AT WORD_BOUNDARY、DIGIT、LITERAL等指令可以用来检查边界、量词和捕获分组是否按预期解析。调试技巧借助在线工具调试复杂正则时推荐使用在线正则工具它们会实时高亮匹配内容并解释每个语法结构的含义regex101.com支持 Python 语法可以查看匹配分组、解释模式和性能分析。regexr.com界面直观适合边写边看匹配效果。这些工具通常还提供代码生成功能调试完成后可以直接复制为 Python raw string避免手写转义错误。10. 总结本文系统介绍了 Python 正则表达式从基础到进阶的完整知识体系基础元字符、字符类、量词核心函数match、search、findall、finditer、sub、split进阶技巧分组捕获、命名分组、反向引用、零宽断言工程实践编译缓存、标志位、性能优化、实战场景正则表达式是处理文本的利器但要掌握它需要大量练习。建议在日常开发中遇到文本处理需求时先想清楚「文本的模式是什么」再尝试用正则表达出来。熟练之后你会发现它极大地提升了处理文本的效率和代码的简洁度。