2. 环保数据文件批量获取与组织

在真正开始清洗数据之前,最容易被低估、却最影响后续效率的一步,是把散落在各处的原始文件收拢成一批“可被程序稳定识别”的输入。环保数据尤其如此:同一个排污口,监测平台导出的可能是按小时命名的 CSV,企业报送文件夹里是按月份堆放的 Excel,邮件附件里又夹着带日期的压缩包。如果直接进入清洗环节,脚本往往要先花大量精力去猜“这个文件是什么、属于谁、对应哪段时间”。本节要解决的,就是把这堆杂乱文件整理成结构清晰、命名统一、可追溯的输入集合。

一、先看清三类来源的差异

环保数据的批量获取通常来自三条通道,它们的文件特征差别很大,处理策略也应不同。

监测平台导出:一般由在线监控系统按查询条件生成,文件名常带站点编号、污染物因子和时间范围,格式多为 CSV 或 xlsx,字段结构相对固定,但导出时可能一次只给一个因子,也可能把多个因子横向拼在一张表里。这类文件的关键是保留导出时的查询条件,否则事后无法判断某列数据对应哪个时间段。

企业报送文件夹:企业按环保部门要求定期上报,常见形式是“企业名+年月”的文件夹,里面放自行监测报告、在线数据导出表、手工监测记录等。这类文件命名随意、格式混杂,甚至同一企业不同月份的表头都不一致,是整理工作的重点。

邮件附件:多来自第三方运维单位或企业环保专员,附件名常带“最终版”“修改版”“以此为准”等字样,容易重复和覆盖。批量收集时要特别关注版本与时间戳,避免把旧版本当成新数据。

环保数据文件批量获取与组织流程 监测平台导出 CSV / xlsx 站点编号·因子·时段 企业报送文件夹 多格式混杂 企业名+年月·表头不一 邮件附件 压缩包 / 多版本 最终版·修改版·时间戳 统一命名规则 区域_企业_数据类型_起止时间_版本.扩展名 目录结构设计 按 年份 / 企业 / 数据类型 分层存放 文件清单生成 文件名·来源·大小·时间·校验状态 清单字段示例 file_name source_type enterprise period_start / end size_bytes md5 / status

二、统一命名规则:让文件名自己说明身份

命名规则的目标不是“好看”,而是让文件名成为可解析的元数据。推荐采用下划线分隔的固定字段顺序,例如:

区域_企业简称_数据类型_起始日期_结束日期_版本.扩展名

示例:
城东区_华兴化工_在线监测_20240101_20240131_v1.csv
城东区_华兴化工_自行监测_20240101_20240131_v2.xlsx

几个实践要点:日期统一用 YYYYMMDD,避免 2024.1.1 与 2024-01-01 混用导致排序错乱;版本号用 v1、v2 递增,而不是“最终版”“最终版2”;企业简称要建立一张对照表,防止“华兴化工”“华兴化工有限公司”“华兴”三种写法并存。命名一旦确定,就应写成规则文档,后续所有来源的文件在入库前都先重命名,而不是保留原名再另建索引。

三、目录结构设计:按查询维度分层

目录结构应围绕“以后怎么找数据”来设计。环保数据最常用的检索维度是时间、企业、数据类型,因此可以采用三级结构:

环保数据/
├── 2024/
│   ├── 华兴化工/
│   │   ├── 在线监测/
│   │   │   ├── 城东区_华兴化工_在线监测_20240101_20240131_v1.csv
│   │   │   └── ...
│   │   └── 自行监测/
│   └── 其他企业/
└── 2023/

这样分层的好处是:按年归档便于跨年对比,按企业聚合便于单企业全量核查,按数据类型区分则避免在线数据与手工监测数据混在同一目录。需要注意的是,原始文件与清洗后文件必须分开放置,例如在根目录下另设 raw/ 与 clean/,原始文件只读不改,所有清洗结果写入 clean 目录,保证任何时候都能回溯到源头。

四、文件清单:批量处理的“账本”

文件收拢、重命名、归档之后,必须生成一份文件清单。清单不是可有可无的附属品,它是后续批量清洗的驱动表:脚本读取清单,逐行处理文件,处理结果再回写到清单的状态列。一份实用的清单至少包含以下字段:

字段含义示例
file_name统一命名后的文件名城东区_华兴化工_在线监测_20240101_20240131_v1.csv
source_type来源通道平台导出 / 企业报送 / 邮件附件
enterprise企业名称华兴化工
period_start / period_end数据覆盖时段2024-01-01 / 2024-01-31
size_bytes文件大小184320
md5内容校验值,用于识别重复文件a3f5...(示例)
status处理状态待清洗 / 已清洗 / 异常

清单可以用 Python 的 pathlib 遍历目录自动生成,核心思路是:遍历目标目录下所有 CSV 与 Excel 文件,提取路径、大小、修改时间,再按命名规则用正则解析出企业、数据类型和时段,写入一张总表。下面是一段可直接改造使用的示例代码:

from pathlib import Path
import hashlib, re, csv

root = Path("环保数据/2024")
rows = []
pattern = re.compile(r"(?P<area>[^_]+)_(?P<ent>[^_]+)_(?P<dtype>[^_]+)_"
                     r"(?P<start>\d{8})_(?P<end>\d{8})_v(?P<ver>\d+)")

for f in root.rglob("*"):
    if f.suffix.lower() not in (".csv", ".xlsx", ".xls"):
        continue
    m = pattern.search(f.stem)
    md5 = hashlib.md5(f.read_bytes()).hexdigest()
    rows.append({
        "file_name": f.name,
        "source_type": "企业报送" if "报送" in str(f) else "平台导出",
        "enterprise": m.group("ent") if m else "",
        "period_start": m.group("start") if m else "",
        "period_end": m.group("end") if m else "",
        "size_bytes": f.stat().st_size,
        "md5": md5,
        "status": "待清洗",
    })

with open("文件清单.csv", "w", newline="", encoding="utf-8-sig") as fp:
    writer = csv.DictWriter(fp, fieldnames=rows[0].keys())
    writer.writeheader()
    writer.writerows(rows)

运行后得到的清单就是后续清洗的入口。这里有两个值得观察的判断点:一是正则未匹配的文件(enterprise 为空),说明命名不合规,需要人工回看并重命名;二是md5 重复的行,往往意味着同一文件被重复收集(例如邮件里既发了附件又发了压缩包),应在清洗前去重,避免同一批数据被计算两次。

建议在清单中额外保留一列 original_path,记录文件重命名前的原始路径。这样即使命名规则调整,也能追溯每个文件从哪来、由谁报送,满足环保数据可审计的要求。

五、批量收集时的常见坑

第一,编码问题。监测平台导出的 CSV 常见 GBK 编码,而企业报送的可能是 UTF-8,直接读取会出现中文乱码。收集阶段可以先统一转存为 UTF-8,或在清单中记录编码,清洗时按编码读取。第二,表头行位置不固定。部分企业报表前几行是标题和说明,真正表头在第 3 行甚至第 5 行,收集时不必强行改表,但要在清单或备注中标记,供清洗阶段跳过。第三,同名覆盖。邮件附件下载到同一目录时,同名文件会被自动加“(1)”,这类文件在重命名前要先比对 md5,确认是否真的不同。

把这三类来源收拢、按规则重命名、按维度归档、再生成一份带校验值的清单,原始数据就从“一堆文件”变成了“一批可被程序稳定处理的输入”。这一步做扎实,后面的批量清洗才能少走弯路。