Python正则表达式完整教程:从入门到实战,解决文本匹配所有难题

一、前言

在日常Python开发、日志解析、爬虫、数据清洗、大模型文本提取场景中,字符串处理是高频需求。普通str内置方法(split/replace/find)只能处理简单固定文本,面对不规则、分段、跨多行、结构化混杂文本时完全无力。

正则表达式(Regular Expression,简称Regex)是一套通用文本匹配规则,Python内置re模块原生支持正则,可实现:格式校验、内容提取、批量替换、文本分割四大核心能力。

本文覆盖完整学习链路:基础语法→re模块六大核心API→标志位(重点解决换行匹配坑)→分组捕获→贪婪/非贪婪→实战案例→高频踩坑总结,全部代码可直接复制运行。

二、前置准备:re模块与原始字符串r""

1. 导入模块

Python标准库自带re,无需额外安装:

importre

2. 必须使用原始字符串 r"正则"

正则大量使用\d\s\.等转义符,普通字符串"\\d"需要双重反斜杠,极易出错;原始字符串r""会忽略Python转义,推荐全程使用。

# 标准写法pattern=r"\d+"# 不推荐,冗余易出错pattern="\\d+"

三、正则核心基础语法

3.1 通用字符匹配(元字符)

符号含义示例
.匹配任意单个字符,默认不匹配换行\nr"a."匹配ab、a9
\d数字 0-9r"\d{11}"匹配手机号
\D非数字匹配字母、符号
\w字母/数字/下划线 a-z A-Z 0-9 _匹配变量名
\W非字母数字下划线匹配空格、逗号
\s空白字符(空格、制表符、换行)匹配任意空白
\S非空白字符匹配可见字符
[]字符集,匹配括号内任意一个[0-9a-z]小写数字
[^]取反,匹配不在括号内字符[^0-9]匹配非数字

3.2 量词:控制匹配重复次数

符号含义
*前面字符匹配 0次或多次
+前面字符匹配 1次或多次(至少1次)
?前面字符匹配 0次或1次
{m}精确匹配m次
{m,n}匹配m~n次

示例:r"\d{6}"匹配6位数字验证码。

3.3 边界锚点:限定匹配位置

符号作用
^字符串开头
$字符串结尾
\b单词边界(字母/数字与符号间隔)

示例:r"^1[3-9]\d{9}$"完整校验11位手机号。

3.4 分组与或逻辑

  1. ():分组,捕获匹配内容,单独提取子片段
  2. |:或,匹配左右任意一个表达式
# 匹配 http 或 httpsr"https?://"# 分组提取年月日r"(\d{4})-(\d{2})-(\d{2})"

四、re模块六大核心API详解

4.1 re.match():仅从字符串开头匹配

只校验文本最开头是否符合规则,中间匹配不到,返回Match对象/None。

text="abc123def"res=re.match(r"\d+",text)print(res)# None,开头不是数字res2=re.match(r"abc",text)print(res2.group())# abc

4.2 re.search():全局搜索第一个匹配项

扫描整个字符串,返回第一个符合规则的内容,最常用单条提取场景。

text="订单号:ORD20260707,金额999"res=re.search(r"\d+",text)print(res.group())# 20260707

4.3 re.findall():提取所有匹配,返回列表(高频)

找出全部匹配子串,无匹配返回空列表,适合批量提取。

text="123 456 789"nums=re.findall(r"\d+",text)print(nums)# ['123', '456', '789']

你之前提取多行JSON代码块使用的就是该方法。

4.4 re.sub():批量替换文本

语法:re.sub(正则, 替换内容, 原文本, count=0),count=0替换全部。

text="价格100,优惠50"new=re.sub(r"\d+","***",text)print(new)# 价格***,优惠***

4.5 re.split():按正则分割字符串

str.split灵活,支持按多种符号分割。

text="苹果,香蕉;橘子 葡萄"parts=re.split(r"[,; ]",text)print(parts)# ['苹果', '香蕉', '橘子', '葡萄']

4.6 re.compile():预编译正则(高复用场景优化)

如果一段正则需要循环调用上千次,提前编译提升性能。

# 预编译pattern=re.compile(r"\d{11}")text_list=["13800138000","test123","13912345678"]fortintext_list:print(pattern.search(t))

五、Match对象取值:group()分组捕获

当正则存在()分组时,可单独提取分组内容:

  • group()/group(0):完整匹配内容
  • group(1):第一个括号分组
  • groups():所有分组组成元组
text="2026-07-07"res=re.search(r"(\d{4})-(\d{2})-(\d{2})",text)print(res.group())# 2026-07-07print(res.group(1))# 2026print(res.groups())# ('2026', '07', '07')

六、正则标志位flags(重点解决换行匹配痛点)

re.match/search/findall都支持flags参数,多个标志用|拼接。

6.1 re.S / re.DOTALL(重中之重)

默认.不能匹配换行符\n,开启后.匹配所有字符(包含换行),解决跨多行文本提取空白问题。

md=""" ```json[{"name":"测试"}]

“”"

不加re.S匹配为空

res = re.findall(r"json(.+?)", md)
print(res) # []

开启跨行匹配

res = re.findall(r"json(.+?)", md, flags=re.S)
print(res[0].strip()) # 完整取出JSON多行内容

替代方案(无需标志位):`[\s\S]` 等价所有字符 ```python res = re.findall(r"```json([\s\S]+?)```", md)

6.2 re.M / re.MULTILINE 多行锚点模式

仅修改^$,让其匹配每一行开头/结尾,不改变.匹配规则,无法解决跨行提取。

text="第一行\n第二行\n第三行"# 匹配所有以“第”开头的行res=re.findall(r"^第.*",text,re.M)print(res)# ['第一行', '第二行', '第三行']

6.3 re.I / re.IGNORECASE 忽略大小写匹配

text="Apple APPLE apple"res=re.findall(r"apple",text,re.I)print(res)# ['Apple', 'APPLE', 'apple']

七、贪婪匹配与非贪婪匹配(开发高频坑)

7.1 贪婪(默认)

*+{m,n}默认尽可能匹配最长文本,容易匹配范围过大。

text="<div>内容1</div><div>内容2</div>"# 贪婪,一次性匹配到最后一个</div>res=re.findall(r"<div>(.+)</div>",text)print(res)# ['内容1</div><div>内容2']

7.2 非贪婪(加?)

量词后增加?,匹配最短符合内容,多标签/多代码块场景必备。

res=re.findall(r"<div>(.+?)</div>",text)print(res)# ['内容1', '内容2']

八、实战业务案例(可直接复用)

案例1:校验手机号完整格式

defcheck_phone(phone):pattern=r"^1[3-9]\d{9}$"ifre.match(pattern,phone):returnTruereturnFalseprint(check_phone("13812345678"))# Trueprint(check_phone("12345678900"))# False

案例2:提取Markdown所有代码块

md_text="""# 测试文档```pythondefhello():print("hello")
{"key":"value"}

“”"

提取代码语言+代码内容

blocks = re.findall(r"(\w+)\n([\s\S]+?)“, md_text)
for lang, code in blocks:
print(f"语言:{lang}\n代码:\n{code.strip()}\n—”)

### 案例3:清洗日志,提取所有数字 ```python log = "2026-07-07 接口耗时 128ms,请求ID 987654,错误码 0" nums = re.findall(r"\d+", log) print(nums) # ['2026','07','07','128','987654','0']

案例4:批量隐藏身份证中间数字

text="张三身份证500101199901011234,李四510202200002024567"new_text=re.sub(r"(\d{6})\d{8}(\d{4})",r"\1********\2",text)print(new_text)# 张三身份证500101********1234,李四510202********4567

九、常见踩坑总结

  1. 跨行文本匹配返回空列表
    原因:.默认不匹配换行;解决方案:加re.S或替换[\s\S]+?
  2. 正则包含\被转义失效
    解决方案:全部使用原始字符串r"表达式"
  3. 多个标签/代码块一次性匹配到底
    原因:贪婪匹配;解决方案:量词后加?改为非贪婪。
  4. match只能匹配开头,误以为全局搜索
    区分:match从头匹配,search全局找第一个,findall提取全部。
  5. 混淆re.M和re.S
    re.M只改变行首尾锚点,不能解决.无法跨换行问题。
  6. 匹配结果为空列表直接取res[0]报错
    规范写法:先判断if res:再取值,增加容错。

十、总结

  1. Python通过内置re模块实现正则,原始字符串r""是规范写法;
  2. 六大核心函数:match/search/findall/sub/split/compile,按业务场景选用;
  3. 跨行文本提取必用re.S标志位,解决.不识别换行的核心痛点;
  4. 多段混杂文本必须使用非贪婪+?,避免匹配范围溢出;
  5. 分组()配合group()可精准提取结构化片段,适合日志、代码块、证件解析;
  6. 正则是文本处理利器,爬虫、数据清洗、接口文本过滤、格式校验场景均不可替代。