Python正则提取多结构样本检测数据 动态生成Pandas数据表
气体检测记录结构化提取方案
基础需求与文本规则梳理
- 输入为存储了批量气体检测结果的纯文本文件,独立检测条目以
\r\n分隔 - 最终输出为动态列结构的Pandas DataFrame:每行对应一个检测样本,字段包含样本序号、检出气体总数、各气体对应的浓度定性分级、定量占比数值;无匹配内容的字段统一填充占位符
- - 文本自带的固定结构规则:
- 各气体占比总和无需为100%,占比数值兼容整数、浮点数格式
- 气体名称可由多个单词组成,所有组成单词首字母大写
- 浓度定性分级可由多个单词组成,首字母大写(如"Very low""Medium"这类表述)
- 浓度过低的检出气体以"Traces of"为前缀统一列在记录末尾,这类气体不计入官方标注的检出气体总数
- 存在完全无气体检出的空记录场景
- 无法预先获知所有可能出现的气体种类,DataFrame列需根据文本实际出现的气体字段动态生成
- 当前实现卡点:正则匹配逻辑始终无法对齐文本结构,无法提取出类似
['Very low','Helium','1.5','Medium','Oxygen','20',...]的结构化片段,对regex101的匹配运行逻辑没有清晰认知
可运行实现步骤与核心逻辑
1. 预拆分独立样本
利用每条样本都以Detected X gas in sample开头的固定特征,用零宽正则切片把长文本拆成独立的样本片段,不会把固定的开头标识切掉:re.split(r'(?=Detected \d+ gas in sample)', 原始读取的文本内容)
原理:用零宽正向先行断言(?=...),只在满足“后续是Detected开头的样本头”的位置切开,不消耗匹配到的字符串本身。
2. 逐样本解析固定字段
对拆分后的每个样本片段,先提取两个固定属性:
- 样本序号:按拆分顺序从1开始计数
- 检出气体总数:通过
re.search(r'Detected (\d+) gas', 样本文本).group(1)匹配开头标注的数字,转为整数存储
3. 解析气体组分
组分部分分两类处理,所有匹配都依托文本的固定结构做锚定,避免孤立匹配单词导致的混乱:
- 带定量占比的常规气体:逐行匹配气体组分行,正则规则为
^(?P<grade>[A-Z][a-z]+(?: [A-Z][a-z]+)*) (?P<gas_name>[A-Z][a-z]+(?: [A-Z][a-z]+)*) \((?P<ratio>[\d.]+)% total\)$
逐段拆解匹配逻辑:^/$锁定行首尾,匹配完整的单行气体记录,不会跨行抓混内容- 命名捕获组
grade:匹配多词的浓度分级,结构为“大写开头单词,后续可接0个或多个空格分隔的大写开头单词”,覆盖"Very high""Low"这类分级表述 - 命名捕获组
gas_name:匹配多词的气体名称,和分级的匹配规则一致,覆盖"Carbon monoxide""Helium"这类气体名 - 固定文本锚点
(、% total):直接定位到占比数值的前后边界,ratio捕获组抓取括号内的整数/浮点数,完全不会抓入文本中无关的数字
- 痕量气体:匹配以
Traces of开头的行,拆分逗号分隔的气体名,这类气体对应的定性值统一标记为Trace,定量值填充占位符-,不改动样本开头标注的检出气体总数
4. 动态构建DataFrame列
遍历所有样本收集全部出现过的气体名,作为DataFrame的二级列前缀(比如对于Helium气体,生成Helium_分级、Helium_占比%两个字段),每个样本对应字段无值时统一填充-
完整可运行示例代码:
import re import pandas as pd # 读取原始文本 with open("gas_records.txt", "r", encoding="utf-8") as f: raw_text = f.read().replace("\r\n", "\n") # 拆分独立样本 samples = re.split(r'(?=Detected \d+ gas in sample)', raw_text) samples = [s.strip() for s in samples if s.strip()] # 存储所有样本的解析结果 parsed_samples = [] all_gases = set() # 逐样本解析 for sample_idx, sample in enumerate(samples, 1): # 提取检出气体总数 total_detected = int(re.search(r'Detected (\d+) gas', sample).group(1)) sample_data = { "样本序号": sample_idx, "检出气体总数": total_detected } # 拆分组分行 lines = [line.strip() for line in sample.split("\n") if line.strip()] for line in lines: # 跳过样本头的行 if line.startswith("Detected"): continue # 匹配带定量值的常规气体 gas_match = re.match( r'^(?P<grade>[A-Z][a-z]+(?: [A-Z][a-z]+)*) (?P<gas_name>[A-Z][a-z]+(?: [A-Z][a-z]+)*) \((?P<ratio>[\d.]+)% total\)$', line ) if gas_match: gas_info = gas_match.groupdict() gas_name = gas_info["gas_name"] all_gases.add(gas_name) sample_data[f"{gas_name}_浓度分级"] = gas_info["grade"] sample_data[f"{gas_name}_占比%"] = gas_info["ratio"] continue # 匹配痕量气体 if line.startswith("Traces of"): trace_gases = [g.strip() for g in line.replace("Traces of", "").split(",")] for gas_name in trace_gases: all_gases.add(gas_name) sample_data[f"{gas_name}_浓度分级"] = "Trace" sample_data[f"{gas_name}_占比%"] = "-" parsed_samples.append(sample_data) # 构建完整列名,填充缺失值 all_columns = ["样本序号", "检出气体总数"] for gas in sorted(all_gases): all_columns.extend([f"{gas}_浓度分级", f"{gas}_占比%"]) df = pd.DataFrame(parsed_samples).reindex(columns=all_columns).fillna("-")
匹配逻辑的常见问题
之前尝试的“孤立匹配大写开头单词、括号内数值”的思路之所以难以对齐预期,核心是没有依托文本的固定结构做匹配锚点:
- 脱离位置的孤立单词匹配,会把"Traces of"里的Traces误判为浓度分级、of误判为气体名,把承担不同功能的文本片段混为一类
- 没有固定前后锚点的数值匹配,容易把文本中其他位置的无关数字抓入结果
- 多词内容的匹配必须用非捕获组
(?:...)做循环匹配,才能覆盖多词分级、多词气体名的场景,避免只匹配到词组的第一个单词
内容的提问来源于stack exchange,提问作者Eliam
相关产品推荐
相关产品推荐

