You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python正则提取多结构样本检测数据 动态生成Pandas数据表

气体检测记录结构化提取方案

基础需求与文本规则梳理

  • 输入为存储了批量气体检测结果的纯文本文件,独立检测条目以\r\n分隔
  • 最终输出为动态列结构的Pandas DataFrame:每行对应一个检测样本,字段包含样本序号、检出气体总数、各气体对应的浓度定性分级、定量占比数值;无匹配内容的字段统一填充占位符-
  • 文本自带的固定结构规则:
    1. 各气体占比总和无需为100%,占比数值兼容整数、浮点数格式
    2. 气体名称可由多个单词组成,所有组成单词首字母大写
    3. 浓度定性分级可由多个单词组成,首字母大写(如"Very low""Medium"这类表述)
    4. 浓度过低的检出气体以"Traces of"为前缀统一列在记录末尾,这类气体不计入官方标注的检出气体总数
    5. 存在完全无气体检出的空记录场景
    6. 无法预先获知所有可能出现的气体种类,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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.29 06:06:04