一、前言
在日常Python开发、日志解析、爬虫、数据清洗、大模型文本提取场景中,字符串处理是高频需求。普通str内置方法(split/replace/find)只能处理简单固定文本,面对不规则、分段、跨多行、结构化混杂文本时完全无力。
正则表达式(Regular Expression,简称Regex)是一套通用文本匹配规则,Python内置re模块原生支持正则,可实现:格式校验、内容提取、批量替换、文本分割四大核心能力。
本文覆盖完整学习链路:基础语法→re模块六大核心API→标志位(重点解决换行匹配坑)→分组捕获→贪婪/非贪婪→实战案例→高频踩坑总结,全部代码可直接复制运行。
二、前置准备:re模块与原始字符串r""
1. 导入模块
Python标准库自带re,无需额外安装:
importre2. 必须使用原始字符串 r"正则"
正则大量使用\d、\s、\.等转义符,普通字符串"\\d"需要双重反斜杠,极易出错;原始字符串r""会忽略Python转义,推荐全程使用。
# 标准写法pattern=r"\d+"# 不推荐,冗余易出错pattern="\\d+"三、正则核心基础语法
3.1 通用字符匹配(元字符)
| 符号 | 含义 | 示例 |
|---|---|---|
. | 匹配任意单个字符,默认不匹配换行\n | r"a."匹配ab、a9 |
\d | 数字 0-9 | r"\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 分组与或逻辑
():分组,捕获匹配内容,单独提取子片段|:或,匹配左右任意一个表达式
# 匹配 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())# abc4.2 re.search():全局搜索第一个匹配项
扫描整个字符串,返回第一个符合规则的内容,最常用单条提取场景。
text="订单号:ORD20260707,金额999"res=re.search(r"\d+",text)print(res.group())# 202607074.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九、常见踩坑总结
- 跨行文本匹配返回空列表
原因:.默认不匹配换行;解决方案:加re.S或替换[\s\S]+?。 - 正则包含\被转义失效
解决方案:全部使用原始字符串r"表达式"。 - 多个标签/代码块一次性匹配到底
原因:贪婪匹配;解决方案:量词后加?改为非贪婪。 - match只能匹配开头,误以为全局搜索
区分:match从头匹配,search全局找第一个,findall提取全部。 - 混淆re.M和re.S
re.M只改变行首尾锚点,不能解决.无法跨换行问题。 - 匹配结果为空列表直接取res[0]报错
规范写法:先判断if res:再取值,增加容错。
十、总结
- Python通过内置
re模块实现正则,原始字符串r""是规范写法; - 六大核心函数:
match/search/findall/sub/split/compile,按业务场景选用; - 跨行文本提取必用
re.S标志位,解决.不识别换行的核心痛点; - 多段混杂文本必须使用非贪婪
+?,避免匹配范围溢出; - 分组
()配合group()可精准提取结构化片段,适合日志、代码块、证件解析; - 正则是文本处理利器,爬虫、数据清洗、接口文本过滤、格式校验场景均不可替代。