Python正则如何处理带换行符的多行文本|解决匹配空白、提取失败问题
一、前言
日常开发中使用Pythonre正则时,几乎所有人都会踩同一个坑:单行字符串匹配一切正常,一旦文本包含换行符,正则直接匹配不到内容,re.findall返回空列表。
典型场景:
- 提取Markdown中
json /python 包裹的多行代码块; - 截取多行注释、分段日志、多行接口返回文本;
- 抓取HTML/富文本中跨多行的标签内容;
- 像前文需求一样,提取大段跨换行JSON字符串。
出现该问题的根源不是正则表达式写错,而是Python正则内置的匹配规则限制。本文完整讲解换行处理原理、两套通用解决方案、实战案例与避坑技巧。
二、核心底层规则:点号.默认不匹配换行
正则中最常用的万能匹配符.存在硬性限制:
默认规则:
.匹配除换行符\n之外的所有字符。
举个直观例子:
importre text="""第一行 第二行 第三行"""# 尝试用.+匹配全部内容,会失败res=re.findall(r'.+',text)print(res)# 输出:['第一行', '第二行', '第三行'].+只会匹配单行内容,遇到换行直接终止,无法一次性抓取跨多行的整块文本。
如果我们想匹配json` 和 ```中间跨多行的内容,直接写json(.+?)````必然返回空列表,这就是你之前代码失效的根本原因。
三、两种主流跨行匹配解决方案
方案1:添加标志位 re.S(re.DOTALL)【推荐】
re.S全称DOTALL,作用:让.可以匹配换行符\n,开启后点号真正代表「任意所有字符」,跨行匹配一键生效。
语法:
re.findall(正则,文本,flags=re.S)实战修复你之前提取JSON代码块的案例:
importre str1=''' 随便一段前置文字 ```json[{"content":"第一篇 战略管理","level":0},{"content":"1 目的","level":2}]后置备注文字
‘’’
增加 flags=re.S 支持跨行匹配
res = re.findall(r’json(.+?)', str1, flags=re.S)
if res:
json_content = res[0].strip() # 清除首尾多余换行空格
print(“提取到多行JSON:\n”, json_content)
执行后可以完整取出中间所有换行的JSON文本,完美解决提取空白问题。 补充别名:`re.DOTALL` 和 `re.S` 完全等价,二者随便使用。 ### 方案2:不使用标志位,替换万能匹配符 `[\s\S]` 如果项目规范不允许添加flag、或者部分环境无法传参,可以用 `[\s\S]` 替代 `.`。 - `\s`:所有空白字符(空格、制表符、换行 `\n`) - `\S`:所有非空白字符 二者组合 `[\s\S]` = 全部字符,天然支持换行,不需要额外标志位。 改造上例正则: ```python # [\s\S]+? 替代 .+? res = re.findall(r'```json([\s\S]+?)```', str1)效果和re.S完全一致,适合不方便修改flag参数的场景。
四、补充:容易混淆的 re.M 多行模式(和换行匹配无关)
很多新手会混淆re.M和re.S,这里明确区分:
re.S:控制.是否匹配换行,用于跨多行抓取整块内容;re.M(MULTILINE):仅修改^和$的匹配规则,^匹配每行开头,$匹配每行结尾,不改变点号.的行为,无法解决跨行提取失败问题。
错误示范(很多人踩坑):
# 只加re.M,不加re.S,依旧匹配不到跨多行内容res=re.findall(r'```json(.+?)```',str1,re.M)print(res)# [] 空列表如果需要同时开启两种模式,使用|分隔:
re.findall(pattern,text,re.S|re.M)五、常用实战案例
案例1:提取多行Python代码块
importre md_text=""" 测试文本 ```pythondeftest():print("多行函数")return123“”"
pattern = r’python([\s\S]+?)’
code = re.findall(pattern, md_text)[0].strip()
print(code)
### 案例2:匹配跨多行HTML标签内容 ```python html = """ <div class="box"> 第一行文字 第二行文字 </div> """ res = re.findall(r'<div class="box">(.+?)</div>', html, re.S) print(res[0].strip())案例3:逐行匹配(不需要跨行,仅遍历每行)
如果需求不是抓取整块多行内容,而是单独匹配每一行,无需开启re.S,直接循环结果即可:
log="""2026-01-01 日志1 2026-01-02 日志2 2026-01-03 日志3"""# 匹配所有数字开头日志行lines=re.findall(r'^\d{4}-\d{2}-\d{2}.*',log,re.M)print(lines)六、开发避坑优化技巧
正则字符串统一使用原始字符串 r""
正则表达式前缀加r,避免\n、\s等转义符被Python提前解析,防止匹配异常。
错误写法:'```json(.+?)```'
标准写法:r'```json(.+?)```'提取后使用strip()清理多余换行空格
跨行匹配出来的内容首尾通常自带换行、空白,调用.strip()去除冗余空白字符。非贪婪匹配
+?避免匹配范围过大
使用(.+?)/([\s\S]+?)非贪婪模式,防止文本中存在多个 ```时,一次性匹配到最后一个结束标记。判断匹配结果非空再取值
匹配失败返回空列表,直接取res[0]会报索引错误,增加判断:
res=re.findall(r'```json(.+?)```',str1,re.S)ifres:content=res[0].strip()else:print("未找到对应代码块")七、总结
- Python正则
.默认不能匹配换行符,这是多行文本提取空白的核心原因; - 主流解决方案:添加
flags=re.S让.支持换行,或使用[\s\S]替代.; re.M仅作用于行首尾符^/$,不能解决跨行抓取问题,不要混用混淆;- 处理Markdown代码块、多行标签、分段日志等场景,优先使用
re.S方案,代码简洁易维护; - 规范使用原始字符串、非贪婪匹配、空值判断,减少线上正则报错。
掌握以上规则后,所有带换行符的文本正则匹配问题都可以一次性解决。
