☰
数据清洗数据源.zip:用pandas打造可复现的脏数据清洗流水线
2026/9/26 23:25:53 网站建设 项目流程

简介:数据清洗是数据分析流程中决定数据质量的关键前置环节。这份《数据清洗数据源.zip》面向数据分析初学者与大数据应用培训人群,提供了一组可直接上手的多格式练习素材,用于实践缺失值处理、异常值检测、格式转换与一致性校验等常见操作,帮助学员和从业者建立规范的数据预处理手感。包体内共11个文件,涵盖sql数据库脚本、csv/xlsx/xls表格数据、txt文本日志、json与xml半结构化数据等,合计约96KB,覆盖课程信息、租房记录、文本抽取等不同业务场景,便于按数据类型开展差异化的清洗实验。学习者可据此配合Pandas、Kettle或SQL等工具还原脏数据修复流程,比较各类文件的编码与结构特点,为后续分析建模夯实数据底子。该资源已有528人学习下载,适合作为课堂实训或自学数据预处理时反复使用的起点素材。

1. 数据清洗数据源.zip 到底是什么:一份能交接的清洗任务,不是一个压缩包

做数据的人几乎都遇到过这种场景:甲方或同事甩过来一个 zip 包,名字就叫「数据清洗数据源」,里面几十个 CSV、几个 Excel,外加一个不知道哪个版本的脚本。你以为解压完就能直接用,结果打开一看:日期格式五花八门,同一家客户的名称有三种写法,金额列里混着「元」和「万元」,甚至还有被 Excel 改成科学计数法的订单号。这个 zip 其实不是压缩包,而是一份「可交付的脏数据清洗工程包」——它把原始数据源、清洗规则、输出约定打成一个包,目的是让接手的人能复现同一套清洗逻辑,而不是靠手工在 Excel 里删删改改。

这篇文章就围绕「数据清洗数据源.zip」这类工程包展开:拿到手之后怎么拆、怎么理解里面的数据源和清洗规则、怎么用 pandas 把清洗逻辑落成可复现代码、多数据源怎么统一接入,以及我踩过的几个真实坑。适合谁看?需要经常处理 Excel/CSV 脏数据的分析师、要接手别人清洗任务的开发、以及想把清洗工作从「人肉 Excel」升级成「脚本流水线」的从业者。看完你能直接照着搭一条自己的清洗管道。

2. 解压后的第一件事:把 zip 包结构和数据字典摸清楚

拿到包先别急着跑脚本。多数人踩的第一个坑就是解压后直接 python main.py,结果报错「File not found」或者读完数据发现列名对不上。原因几乎都是没先看包内结构。一个规范的清洗数据源 zip 包,通常包含四类东西:原始数据文件(CSV/Excel/TXT/JSON)、数据字典或 README、清洗脚本(py 或 ipynb)、配置文件(json/yaml/ini)。有时还会带一个 output 目录,里面是清洗结果的示例。

我一般会先做一件事:把 zip 解压到一个独立目录,然后打印完整的文件树,不放过任何一层子目录。用命令行可以这样做:

mkdir -p ./data_clean_project && cd ./data_clean_project unzip ../数据清洗数据源.zip -d ./unpacked find ./unpacked -type f | sort

这条命令把 zip 内容解压到unpacked目录,然后用find列出所有文件路径。看输出时重点关注三样东西:有没有README或数据字典这类说明文件、数据文件的后缀分布(是清一色 CSV 还是混合类型)、以及是否存在output或result目录——这决定了你要不要把清洗结果写回原包结构里。如果你用的是 Windows,没有unzip命令,那就用 Python 来解压并同时打印目录结构,顺带检查文件编码,一步到位:

import zipfile from pathlib import Path zip_path = Path("./数据清洗数据源.zip") extract_dir = Path("./unpacked") with zipfile.ZipFile(zip_path, "r") as zf: # 先看有没有加密标记 bad_files = [i for i in zf.infolist() if i.flag_bits & 0x1] if bad_files: print("警告:以下文件带加密标记", [i.filename for i in bad_files]) zf.extractall(extract_dir) print("解压完成,目录结构如下:") for p in sorted(extract_dir.rglob("*")): if p.is_file(): # 读前几百字节尝试判断编码 head = p.read_bytes()[:4] if head.startswith(b"PK"): # 内部还有层 zip print(f"[内嵌zip] {p.relative_to(extract_dir)}") else: print(f"[文件] {p.relative_to(extract_dir)} (大小: {p.stat().st_size})")

这段代码比命令行多做两件事:一是检查flag_bits & 0x1,这是 zip 的加密标记位,后面避坑章节会展开;二是递归打印全部文件并标注内嵌 zip。注意这里没有做编码自动识别,只取了文件头的前四个字节,真正判断编码要用chardet或charset_normalizer。把结构打印出来之后,下一步不是看脚本,而是看数据字典。

数据字典是整个包的「地图」。它通常是一张表,列名包含:字段名、字段含义、数据类型、取值示例、是否允许为空、清洗备注。你要做的不是通读,而是对照着检查原始数据文件——重点看「清洗备注」这一列有没有写着「去除前后空格」「统一为 yyyy-MM-dd」「金额单位转换为元」这类规则。这些备注就是后面写 pandas 清洗函数的直接需求来源。如果包内没有数据字典,只有一堆 CSV,那就先用df.dtypes和df.head()快速过一遍每张表的字段类型,自己逆向建一张字典表,不然后面清洗全是猜。

3. 清洗规则落地:用 pandas 把脏数据清洗变成一条可复现的流水线

结构摸清之后,真正核心的工作是把「清洗规则」变成「可复现代码」。常见做法是写一个清洗管道函数:输入原始 DataFrame,输出清洗后的 DataFrame。而不是在 Jupyter 里一个个格子地改——格子改法没法交接,换个人就跑不出同样的结果。整个管道按顺序执行六步:列名规范化、缺失值处理、重复值处理、格式统一、异常值处理、派生字段。下面这份代码是我比较常用的一套骨架:

import pandas as pd import numpy as np def clean_frame(df: pd.DataFrame, config: dict) -> pd.DataFrame: """通用清洗管道,按 config 里的规则逐项清洗""" # 1. 列名规范化:去掉首尾空格、全角转半角、统一小写 df.columns = [ str(c).strip().replace(" ", "_").replace("(", "(").replace(")", ")").lower() for c in df.columns ] # 2. 缺失值处理:字符串列填空串,数值列先保留,交由后续规则决定 str_cols = df.select_dtypes(include=["object"]).columns df[str_cols] = df[str_cols].fillna("") # 3. 重复值处理:按 config 里指定的关键列去重,保留第一次出现 subset = config.get("dedup_keys") if subset: df = df.drop_duplicates(subset=subset, keep="first") # 4. 格式统一:去除字符串列首尾空格,日期列统一格式 df[str_cols] = df[str_cols].apply(lambda s: s.str.strip() if s.dtype == "object" else s) date_cols = config.get("date_cols", []) for col in date_cols: if col in df.columns: df[col] = pd.to_datetime(df[col], errors="coerce").dt.strftime("%Y-%m-%d") # 5. 异常值处理:数值列中负数且小于下限的按缺失处理 numeric_cols = df.select_dtypes(include=[np.number]).columns limits = config.get("range_limits", {}) for col, (low, high) in limits.items(): if col in df.columns: df.loc[df[col] < low, col] = np.nan df.loc[df[col] > high, col] = np.nan # 6. 派生字段:例如把金额单位从万转换为元 for new_col, expr in config.get("derived", {}).items(): if expr["type"] == "multiply": df[new_col] = df[expr["source"]] * expr["factor"] return df

每个步骤都对应一类真实需求。第 1 步的列名规范化看似简单,但实际中「客户名称」「客户 名称」「客户名称(全角)」会同时出现在一张表里,不统一后面按列名取数就全乱了。第 2 步缺失值处理,我刻意只对字符串列填空串,数值列的缺失留到第 5 步——因为有些缺失值是伪缺失,比如金额填了「-」,直接dropna会误删,先转成空串再在异常值环节处理更稳。第 4 步日期统一最容易被低估,Excel 里日期显示「2024/1/5」,但底层存的是字符串,to_datetime配errors="coerce"能把非法日期转成 NaT 而不是抛异常,这样后续可以做缺失统计。

调用这套管道时,把规则集中在配置里传进去,比散落在函数体内好维护得多:

config = { "dedup_keys": ["订单号"], # 按订单号去重 "date_cols": ["下单日期", "发货日期"], # 统一为 %Y-%m-%d "range_limits": {"金额": (0, 1_000_000)}, # 金额限 0~100 万 "derived": { "金额_元": {"type": "multiply", "source": "金额_万", "factor": 10000} } } df_raw = pd.read_csv("./unpacked/data/orders.csv") df_clean = clean_frame(df_raw, config) df_clean.to_csv("./unpacked/output/orders_clean.csv", index=False, encoding="utf-8-sig")

注意这里的encoding="utf-8-sig"是关键。utf-8不带 BOM 的话,Windows 上的 Excel 打开 CSV 会中文乱码,utf-8-sig带 BOM,Excel 认它。这种细节在交付清洗结果时特别重要——对方如果打不开或者看到乱码,清洗做得再对也被认为是错了。这套管道的边界在哪儿?它不适合处理流式数据、不适合超大数据集(几千万行建议上 Spark),但在大多数企业级的「数据源.zip」场景里,单机 pandas 足够。另一个边界是:如果源数据里有嵌套 JSON,需要先展开成表结构再进管道,这个我在下一章讲多数据源时一并处理。

4. 多数据源接入:Excel、CSV、SQL Server 怎么在清洗前统一成一个 DataFrame

「数据清洗数据源.zip」里的「数据源」很少只有一种格式。最常见的组合是:主表是 CSV,辅表是 Excel,还有一份从数据库导出的 TXT。如果清洗逻辑只对 CSV 写死,换成 Excel 就崩。多数据源问题的本质是「读入阶段不统一,清洗阶段就没法统一」。我一般会写一个 dispatcher 函数,按文件路径后缀或 SQL 连接串前缀分发到不同的读取逻辑,最终全部返回 DataFrame,这样上层清洗管道就完全不关心数据来自哪:

import pandas as pd from pathlib import Path def read_source(path_or_sql: str, **kwargs) -> pd.DataFrame: """统一数据源入口:支持 csv/excel/txt/json 文件,以及 sql 连接串""" s = str(path_or_sql) if s.startswith("sqlite://") or s.startswith("mssql://") or s.startswith("mysql://"): # 数据库数据源:连接串走 kwargs 里的 query query = kwargs.pop("query") return pd.read_sql(query, s, **kwargs) p = Path(s) suffix = p.suffix.lower() if suffix == ".csv" or suffix == ".txt": # 分隔符优先取 kwargs,缺省按逗号;engine 指定 python 应对分隔符不规则 sep = kwargs.pop("sep", ",") encoding = kwargs.pop("encoding", "utf-8") return pd.read_csv(p, sep=sep, encoding=encoding, engine="python", **kwargs) if suffix == ".xlsx" or suffix == ".xls": # 多 sheet 时 sheet_name 传 None,返回 dict,再 concat sheet_name = kwargs.pop("sheet_name", 0) if sheet_name is None: sheets = pd.read_excel(p, sheet_name=None, **kwargs) return pd.concat(sheets.values(), ignore_index=True) return pd.read_excel(p, sheet_name=sheet_name, **kwargs) if suffix == ".json": # 常见两种:记录列表 / 嵌套字典。嵌套的展开放在清洗前处理 return pd.read_json(p, **kwargs) raise ValueError(f"不支持的数据源类型: {suffix}")

这个 dispatcher 解决了三个典型的「源侧坑」。第一个坑是 CSV 编码:直接用默认utf-8读 GBK 导出的文件必乱码,调用时传encoding="gbk"或者errors="replace"兜底。但errors="replace"会引入「�」这类字符,这类字符在后续清洗里极难清干净,所以能明确编码就明确编码,不要依赖 replace。第二个坑是 Excel 多 sheet:很多清洗任务要求把一年 12 个月的表合并成一张年度表,每张表字段结构相同但顺序可能不同。此时传sheet_name=None,读出来是{sheet名: DataFrame}的 dict,pd.concat按列名对齐拼接,就不会因为列顺序不一致而出错。

第三个坑是 SQL 数据源。zip 包里一般不会放数据库连接串,但实际交接任务时常遇到「数据源在服务器上,导不出来」。这种情况我会建议对方把连接串写进 config 文件,不进代码仓库。用pd.read_sql接上之后,有个高频问题:数字列变成object类型。原因通常是数据库里该列是nvarchar或varchar,存的是「1,234.56」这种带千分位的字符串。这种列即使后续清洗也救不回来,最好在 SQL 查询阶段就处理:

SELECT order_id, CAST(REPLACE(amount, ',', '') AS DECIMAL(18,2)) AS amount FROM orders

在 SQL 里把千分位去掉、类型转成DECIMAL,pandas读出来直接是float64,省去下游的astype转换。如果 SQL 已经没法改,那就只能在 pandas 里df["amount"] = df["amount"].str.replace(",", "").astype(float),但这有个隐患:如果字符串里混着空串,astype会直接抛异常,必须先把空串替换成np.nan再转。这类顺序问题就是「数据清洗数据源.zip」这个工程包存在的意义——把这些规则固化下来,而不是每次重写。

多数据源统一之后,还有一个容易被忽略的点:来源标记。我建议在 concat 多张表时加一列source_tag,记录每行来自哪个文件或哪个 sheet。一旦清洗后发现某批数据整体异常(比如某个月的订单金额全为 0),可以立刻定位到具体数据源,而不是整张表从头查。这个习惯在接多数据源任务时能省下大量排查时间。

5. 解压与运行踩坑清单:伪加密、中文乱码和 inplace 失效

同一份 zip 包,在不同机器上跑出来的结果不一样,这类问题最磨人。我整理了几个在这个场景下反复出现的坑,按「现象 → 原因 → 解决」写出来,你大概率会遇到其中一两个。

第一个坑是 zip 伪加密。现象:WinRAR 能正常打开 zip 包,但 Python 的zipfile.extractall()直接报RuntimeError: File is encrypted。原因:部分压缩工具(尤其是某些国产网盘导出的包)会给 zip 文件打上「加密标记位」,但实际上文件并没有真正加密,这就是伪加密。解决:不修改文件内容,只把标记位清零。做法是用二进制方式打开 zip 文件,找到对应条目头里的flag_bits(校验位),把第 0 位从 1 改成 0。也可以直接换用 7-Zip 命令行解压,它对伪加密的容忍度高得多。

7z x ./数据清洗数据源.zip -o./unpacked -y

第二个坑是中文 CSV 乱码。现象:pd.read_csv("客户表.csv")读出来所有中文变成乱码,或者直接报UnicodeDecodeError。原因:文件实际编码是 GBK/GB18030,而 pandas 默认按utf-8解码。解决:先判断编码,再指定解码方式:

with open("客户表.csv", "rb") as f: raw = f.read(10000) enc = chardet.detect(raw)["encoding"] df = pd.read_csv("客户表.csv", encoding=enc)

但这里有一个进阶坑:chardet对小文件检测结果不可靠,10000 字节不够,有时会误判成ISO-8859-1。更稳的做法是直接读文件头几个字节,如果看不到UTF-8的 BOM 也就是\xEF\xBB\xBF,就默认尝试gbk解码,全部乱码再退回utf-8。因为国内业务系统导出的 CSV,十有七八是gbk,这个「业务先验」比任何检测库都准。

第三个坑是 Excel 日期在 pandas 里读成了数字序列。现象:df["日期"]显示为45292而不是2024-01-05。原因:Excel 内部日期就是序列号,1900-01-01 是 1,读入时没有做日期解析。解决:精确定位日期列,用pd.to_datetime并指定unit="D",origin 设成 Excel 的起始日期:

df["日期"] = pd.to_datetime(df["日期"], unit="D", origin="1899-12-30")

第四坑也是最隐蔽的:inplace=True不生效。现象:在函数里执行df.drop_duplicates(inplace=True),函数返回后原表还是没变,也不报错。原因:pandas 在inplace=True时大部分操作仍然返回新对象,原 DataFrame 的引用在函数参数传递时被覆盖了,但外部变量还指向旧对象。解决:不要依赖 inplace,统一用「重新赋值」模式:

def clean_frame(df): df = df.drop_duplicates(subset=["订单号"], keep="first") df = df.reset_index(drop=True) # 去重后索引有空洞,重置避免后续问题 return df

这个坑的可怕之处在于「不报错」,等到你发现结果不对时,中间可能已经过了好几个处理步骤,排查成本极高。我现在的习惯是:清洗函数内部一律不写inplace=True,全部用返回值重新赋值,减少不确定性。

第五个坑是 SQL 数据源掉类型。现象:从 SQL Server 导出的数据在pandas里全是object,连订单金额都是object。原因:pandas.read_sql对decimal和nvarchar的推断保守,宁愿给object也不愿猜错类型。解决:读入后显式做类型映射:

dtype_map = {"金额": "float64", "数量": "int64", "下单日期": "datetime64[ns]"} df = df.astype(dtype_map)

但注意:astype("datetime64[ns]")要求源列已经是可解析的日期格式,如果是20240105这种纯数字日期,要先转成"2024-01-05"字符串再转 datetime。顺序反了就会得到1970-01-01这种离谱结果,而且同样不报错。

这些坑看起来零散,但背后只有一个共同点:清洗代码的「可复现性」不止取决于逻辑正确,还取决于外部环境的一致性。文件编码、zip 标记位、Excel 日期序列、pandas 版本的inplace行为,任何一个不一致,同一个 zip 包在不同机器上就会跑出不同的结果。这也是为什么我强烈建议在清洗前先输出一份「环境说明」,把 Python 版本、pandas 版本、文件编码判定结果写进一个environment.log,随清洗结果一起交付。

6. 验证清洗效果:一张质量报告表和两个能救命的习惯

清洗做完不等于任务做完。交付之前,我会跑一个质量报告脚本,把清洗前后的指标打在屏幕上,用数字说话:

def quality_report(raw_df, clean_df): metrics = { "行数": [len(raw_df), len(clean_df)], "缺失值总数": [int(raw_df.isna().sum().sum()), int(clean_df.isna().sum().sum())], "重复行数": [ int(raw_df.duplicated().sum()), int(clean_df.duplicated().sum()), ], "唯一订单数": [ raw_df["订单号"].nunique() if "订单号" in raw_df else 0, clean_df["订单号"].nunique() if "订单号" in clean_df else 0, ], } report = pd.DataFrame(metrics, index=["清洗前", "清洗后"]) print(report)

这份报告至少有三个作用:第一,让接手的人一眼看到「去掉了多少重复、补了多少缺失、最终保留多少行」;第二,如果某个指标异常,比如清洗后行数比预期少太多,说明去重键选得不对,趁早回头;第三,它作为一个「验收标准」,后续再有人改清洗逻辑,拿同一份数据跑一遍,数字对得上才算改对。我见过太多「清洗完直接覆盖原文件」的交付方式,连最基础的清洗前后行数对比都没有,出了问题根本无法定位是清洗逻辑的错还是源数据的错。

围绕这份报告,我有两个用了很久的习惯。第一个习惯是:清洗函数只返回新 DataFrame,绝不修改原文件,也不覆盖原目录的源数据。源文件是「后悔药」,一旦清洗规则写错,原文件还在就能重跑;覆盖了就再也回不去。第二个习惯是:每次清洗都会在output目录下带上当天的日期后缀,比如orders_clean_20250118.csv,而不是固定写orders_clean.csv。这样即使清洗逻辑改了重跑,旧结果也不会被覆盖,对比新旧版本时直接看文件日期就行。

回到「数据清洗数据源.zip」这个标题本身,它真正想传达的是一种交付思维:数据源、清洗规则、输出物打包成一个可移交的工程,而不是一次性手工活。把本文这套流程跑通之后,你可以把解压、读源、清洗、报告这四段写成自己的模板,下次再有人丢给你一个 zip 包,十分钟就能出干净数据加质量报告。我自己早期接过一个全是 GBK 编码和伪加密 Excel 的包,当时不懂这些,硬是用 Excel 手工洗了两天,后来写成脚本重跑只花了 40 秒——那一刻我才意识到,清洗工作最大的成本从来不是机器跑不动,而是规则没有固化。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询