简介数据清洗是数据分析流程中决定数据质量的关键前置环节。这份《数据清洗数据源.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(bPK): # 内部还有层 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(subsetsubset, keepfirst) # 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], errorscoerce).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配errorscoerce能把非法日期转成 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, indexFalse, encodingutf-8-sig)注意这里的encodingutf-8-sig是关键。utf-8不带 BOM 的话Windows 上的 Excel 打开 CSV 会中文乱码utf-8-sig带 BOMExcel 认它。这种细节在交付清洗结果时特别重要——对方如果打不开或者看到乱码清洗做得再对也被认为是错了。这套管道的边界在哪儿它不适合处理流式数据、不适合超大数据集几千万行建议上 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, sepsep, encodingencoding, enginepython, **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_nameNone, **kwargs) return pd.concat(sheets.values(), ignore_indexTrue) return pd.read_excel(p, sheet_namesheet_name, **kwargs) if suffix .json: # 常见两种记录列表 / 嵌套字典。嵌套的展开放在清洗前处理 return pd.read_json(p, **kwargs) raise ValueError(f不支持的数据源类型: {suffix})这个 dispatcher 解决了三个典型的「源侧坑」。第一个坑是 CSV 编码直接用默认utf-8读 GBK 导出的文件必乱码调用时传encodinggbk或者errorsreplace兜底。但errorsreplace会引入「」这类字符这类字符在后续清洗里极难清干净所以能明确编码就明确编码不要依赖 replace。第二个坑是 Excel 多 sheet很多清洗任务要求把一年 12 个月的表合并成一张年度表每张表字段结构相同但顺序可能不同。此时传sheet_nameNone读出来是{sheet名: DataFrame}的 dictpd.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 里把千分位去掉、类型转成DECIMALpandas读出来直接是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, encodingenc)但这里有一个进阶坑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并指定unitDorigin 设成 Excel 的起始日期df[日期] pd.to_datetime(df[日期], unitD, origin1899-12-30)第四坑也是最隐蔽的inplaceTrue不生效。现象在函数里执行df.drop_duplicates(inplaceTrue)函数返回后原表还是没变也不报错。原因pandas 在inplaceTrue时大部分操作仍然返回新对象原 DataFrame 的引用在函数参数传递时被覆盖了但外部变量还指向旧对象。解决不要依赖 inplace统一用「重新赋值」模式def clean_frame(df): df df.drop_duplicates(subset[订单号], keepfirst) df df.reset_index(dropTrue) # 去重后索引有空洞重置避免后续问题 return df这个坑的可怕之处在于「不报错」等到你发现结果不对时中间可能已经过了好几个处理步骤排查成本极高。我现在的习惯是清洗函数内部一律不写inplaceTrue全部用返回值重新赋值减少不确定性。第五个坑是 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 秒——那一刻我才意识到清洗工作最大的成本从来不是机器跑不动而是规则没有固化。希望帮到你。本文还有配套的精品资源点击获取