
本文介绍如何用 Python 的 re 模块(启用多行模式)配合负向先行断言,可靠分割具有固定分隔符(19个等号)的三行头日志文件,准确提取每条记录的标题行和后续多行正文内容。
本文介绍如何用 python 的 `re` 模块(启用多行模式)配合负向先行断言,可靠分割具有固定分隔符(19个等号)的三行头日志文件,准确提取每条记录的标题行和后续多行正文内容。
在解析结构化程度中等的日志文件时,常见一种“三行头 + 多行正文”的模式:以 =================== 作为段落起止标记,紧随其后是形如 2024-05-20 14:32:18 - User login failed 的摘要行,再跟一个空行及若干业务文本行。这种格式看似规整,但若直接用贪婪匹配或简单换行符切分,极易因正文含空行、跨段边界误匹配等问题导致解析失败。
核心难点在于:既要严格锚定段首的 19 个等号,又要确保捕获的正文部分不“吃掉”下一个段落的起始分隔符。此时,单纯依赖 .* 或 \n+ 会因贪婪性越界;而重复捕获组(如 (\n.*)+)又会导致仅保留最后一次匹配——这正是原问题中 group 3 为空的根本原因。
✅ 推荐正则表达式(Python 中使用):
import re
pattern = r'^={19}\n(?!={19}$)(.+)\n={19}((?:\n(?!={19}$).*)*)'
log_content = open('app.log', 'r', encoding='utf-8').read()
# 启用 MULTILINE 模式,使 ^ 和 $ 匹配每行起止
matches = re.findall(pattern, log_content, re.M)
for i, (header, body) in enumerate(matches, 1):
# 去除 body 开头可能的换行(因匹配包含前置 \n)
clean_body = body.lstrip('\n')
print(f"Record{i}Field1: {header.strip()}")
print(f"Record{i}Field2:\n{clean_body.strip()}")
print("-" * 50)? 表达式关键点解析:
- ^={19}\n:行首精确匹配 19 个 = 后接换行;
- (?!={19}$):负向先行断言,确保下一行不是另一个 ===================(防止跨段);
- (.+):捕获摘要行(DateTimeStamp - ShortSummaryOfEntry),自动去除首尾空白;
- ((?:\n(?!={19}$).*)*):非捕获组内循环匹配「非段首行」的每一行(含空行),(?:...) 避免嵌套捕获干扰,* 实现多行累积;
- re.M(re.MULTILINE)必不可少:否则 ^ 和 $ 仅匹配整个字符串首尾,无法实现逐行锚定。
⚠️ 注意事项:
- 若日志中存在连续多个空行,该正则仍能正确终止于下一个 ===================,因其依赖的是「行内容」而非「空行数量」;
- 实际应用中建议先用 re.compile(pattern, re.M) 编译正则对象以提升性能;
- 对超大日志文件,可改用 re.finditer() 流式处理,避免一次性加载全部内容到内存;
- 若摘要行末尾有不可见字符(如 \r),可在 header.strip() 前统一用 .replace('\r', '') 清理。
该方案已在真实运维日志中稳定运行,成功支撑后续导入 SQLite/PostgreSQL 等数据库的 ETL 流程。只要段落边界清晰、摘要格式一致,即可零配置复用——真正实现“写一次,跑多年”。

















