news 2026/9/2 17:59:05

Python数据清洗实战:从zip数据源到干净DataFrame的完整流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python数据清洗实战:从zip数据源到干净DataFrame的完整流程

简介:面向高职与本科院校大数据相关专业学生,也适合转行数据分析的自学者,这份实操数据包聚焦数据清洗环节,覆盖缺失值处理、异常值检测、格式统一、重复值剔除等常见预处理场景。压缩包共含11个文件,包括3个数据库脚本、2个逗号分隔表格、2个文本文档、2个Excel电子表格、1个JSON数据文件和1个XML数据文件,整体体积仅96KB,便于下载和课堂分发。已有528人学习下载,适合教学演示与自主训练。借助这套多格式数据源,读者可配合Python的Pandas库、数据库查询或ETL工具,完整经历从原始数据导入、清洗到输出可用数据集的流程,理解不同文件格式下的编码问题、缺失值填充与一致性检查要点,并体会数据质量对后续分析结论的影响。同时,多类型数据还能用于模拟异构数据合并、清洗规则编写与效果验证等实战任务,为真实项目中的数据预处理奠定扎实基础。

1. 拿到“数据清洗数据源.zip”之后,先别急着解压

做数据分析这行,隔三差五就会收到这样的压缩包:名字叫“数据清洗数据源.zip”,里面可能是几个CSV、Excel文件,也可能夹着JSON、TXT,甚至还有子文件夹。刚入职那会儿我也踩过坑,拿到压缩包直接双击解压,然后拖进编辑器就开始跑,结果数据量一大直接内存爆炸,字段类型全乱套,日期格式五花八门,中文字段名乱码……光清洗就花了三天。

实际上,“数据清洗数据源.zip”这个标题背后藏着一个典型的数据处理场景:你拿到的是一个多文件、多格式、需要统一处理的原始数据集,第一步不是写清洗逻辑,而是先把“数据源”这层皮扒干净。也就是要做好解压、文件结构摸底、编码识别、字段探查这几件基础工作,后面再谈清洗。

这篇文章按我自己的实操路径来写,适用场景是:本地或邮件收到一个zip压缩包,里面是多张表,需要合并清洗后输出给下游分析或建模。涉及的核心工具是Python的pandas加标准库zipfile,也会穿插一些命令行技巧。

2. 解压与数据源摸底:这一步做得好,后面省一半事

2.1 用Python解压而不是双击

双击解压当然方便,但在数据工程场景里,我建议用脚本解压,原因有三个:

  • 可复现:下次拿到同样结构的压缩包,跑一遍脚本就行。
  • 可处理异常:比如压缩包损坏、编码异常、密码保护等,脚本里能捕获。
  • 可自动登记:解压后直接生成文件清单,方便后续做数据字典。

zipfile是Python标准库,不需要额外安装。基础用法如下:

import zipfile from pathlib import Path zip_path = Path("数据清洗数据源.zip") extract_dir = Path("./data_raw") extract_dir.mkdir(exist_ok=True) with zipfile.ZipFile(zip_path, "r") as zf: # 先打印压缩包里的文件清单 for info in zf.infolist(): print(f"{info.filename} | 原始大小: {info.file_size} | 压缩后: {info.compress_size}") zf.extractall(extract_dir)

这里有个细节:直接用extractall()解压,如果压缩包里文件名包含中文,在部分Windows系统上会出现乱码。原因在于zipfile库默认用cp437编码解析文件名,而中文Windows环境下很多压缩包是用GBK编码写入的。解决办法是手动重命名:

import zipfile with zipfile.ZipFile(zip_path, "r") as zf: for info in zf.infolist(): # 尝试用gbk解码文件名,失败则保持原样 try: decoded_name = info.filename.encode("cp437").decode("gbk") except (UnicodeDecodeError, UnicodeEncodeError): decoded_name = info.filename zf.extract(info, extract_dir) # 如果解码后的名字和原始名字不同,重命名文件 if decoded_name != info.filename: (Path(extract_dir) / info.filename).rename(Path(extract_dir) / decoded_name)

注意:以上重命名写法要求文件在压缩包内是平铺的,如果有嵌套目录,需要先拼接完整路径再做rename。更稳妥的做法是遍历zf.infolist(),对每个entry构建目标路径后再移动。

2.2 用命令行unzip快速摸底

如果你在Linux服务器或macOS终端上操作,unzip -l这个命令我几乎每次都用,它在不真正解压文件的情况下列出压缩包内容,效率极高:

unzip -l 数据清洗数据源.zip

输出类似:

Archive: 数据清洗数据源.zip Length Date Time Name --------- ---------- ----- ---- 1024 2024-01-15 09:30 order_202401.csv 2048 2024-01-15 09:31 user_info.xlsx 512 2024-01-15 09:32 readme.txt

通过这个清单,你能快速判断:有几张表、什么格式、大概多大。如果发现里面有readme.txt数据字典.docx这类文件,先读它,往往比你自己猜字段含义高效得多。

2.3 压缩包损坏?先排查再干别的

如果你解压时遇到invalid zip archive: could not find eocd这类报错,先别急着怀疑代码。这个错误的意思是“找不到中央目录结束标记(End of Central Directory)”,常见原因有两个:

  • 文件下载不完整,zip文件被截断了。
  • 文件本身不是zip格式,只是扩展名改了。

排查方式:

file 数据清洗数据源.zip

如果输出显示Zip archive data,说明格式没问题;如果显示dataHTML document,说明文件就不是zip,硬解压肯定失败。另外也可以用unzip -t测试压缩包完整性:

unzip -t 数据清洗数据源.zip

输出末尾如果显示No errors detected in compressed data of 数据清洗数据源.zip,说明文件完整。

3. 读数据别一把梭:多源格式的读取策略

3.1 先看扩展名,再选读取函数

解压完之后,你手里可能是这样一个文件集合:

文件格式预计用途
order_202401.csvCSV订单表
user_info.xlsxExcel用户信息表
product_detail.jsonJSON商品明细
readme.txt文本字段说明

pandas提供了read_csvread_excelread_json等函数分别对应不同格式,但实际工作中最常见、也最容易出问题的是CSV。我见过太多人在read_csv上栽跟头,核心原因就两个:分隔符不对编码不对

import pandas as pd # 读取CSV时建议显式指定编码和分隔符 df_order = pd.read_csv( "data_raw/order_202401.csv", encoding="utf-8-sig", # 带BOM的utf-8也能读 sep=",", dtype={"order_id": str}, # 防止订单号被读成科学计数法 )

encoding="utf-8-sig"encoding="utf-8"多一个好处:它能自动处理带BOM头的文件。如果你不确定文件编码,推荐用chardetcharset-normalizer检测一下:

from charset_normalizer import from_path result = from_path("data_raw/order_202401.csv").best() print(result.encoding)

Excel文件的读取相对省心,但要注意.xls.xlsx的差异——read_excel底层依赖openpyxl(针对.xlsx)和xlrd(针对.xls,新版本xlrd只支持.xls,不支持.xlsx),如果环境里没装对应的库,读取会直接报错。建议提前安装好依赖:

pip install pandas openpyxl xlrd

3.2 字段类型推断:pandas的“好心”有时候是帮倒忙

pandas读取数据时会自动推断字段类型,这看起来很方便,但实际使用中坑很多。举个例子,订单号如果只有数字组成,且超过15位,pandas会自动读成int64,后面做字符串匹配时就容易出问题;如果没有数据超过15位但字段本身应该保留前导零(如“00123”),pandas会直接去掉前导零。

所以在读取阶段就统一指定dtype,是一门必修课。我通常是这样处理的:

dtype_mapping = { "order_id": str, "user_id": str, "product_id": str, "amount": float, "quantity": int, } df = pd.read_csv("data_raw/order_202401.csv", dtype=dtype_mapping)

注意:如果你指定了dtypestr,但原字段里有空值,pandas读出来的空值会变成NaN(float类型)而不是None或空字符串。这一点在后续清洗时要特别留意。

3.3 多数据源的表结构对齐

如果压缩包里的多张表需要合并,强烈建议先打印每个表的columnsdtypes,形成一张“字段对比表”。别急着merge,先确认哪些字段是公共键、哪些字段同名不同义、哪些字段同义不同名。

for name, df in [("order", df_order), ("user", df_user)]: print(f"=== {name} ===") print(df.columns.tolist()) print(df.dtypes) print(f"行数: {len(df)}")

这一步能帮你发现很多问题:比如订单表里的user_id是字符串,用户表里的user_id是整数,后面merge时明明看起来一样的值却匹配不上,十有八九就是类型不一致。我遇到过的真实案例中,这种问题占比非常高。

3.4 数据量大的时候,别一次性全读进内存

如果你的CSV有几百MB甚至几个GB,pd.read_csv()默认会全部读入内存,很容易导致内存不足。更合理的做法是分块读取或只读取需要的列:

# 只读取需要的列 df = pd.read_csv( "data_raw/order_202401.csv", usecols=["order_id", "user_id", "amount", "created_at"], ) # 分块读取,适合做清洗预览 chunk_iter = pd.read_csv( "data_raw/order_202401.csv", chunksize=50000, # 每次读5万行 ) for chunk in chunk_iter: # 对chunk做清洗,然后增量写入目标文件 process(chunk)

对于真正的大文件,我还会考虑用polars替代pandas,它的惰性计算和内存效率在多数场景下优于pandas。不过这是另一个话题,今天先不展开。

4. 清洗实操:从无到有建一套可复用的处理流水线

4.1 缺失值处理:先问“为什么缺失”,再决定怎么填

缺失值处理是数据清洗里最核心的环节,但很多人上来就fillna(0)dropna(),这是比较危险的做法。我的原则是:先搞清楚缺失的机制。缺失分为三种:完全随机缺失、随机缺失、非随机缺失。不同的缺失机制对应的处理策略完全不同。

举个例子,订单表中的pay_time字段如果大量缺失,一种可能是用户下单后未支付(业务原因),另一种可能是上游系统没记录(技术原因)。前者不需要填充,后者可能需要从其他表补数据。

实操上,第一步是摸清缺失情况:

import pandas as pd def missing_report(df): missing = df.isnull().sum() missing_pct = missing / len(df) * 100 report = pd.DataFrame({ "缺失数量": missing, "缺失占比(%)": missing_pct.round(2), }) return report[report["缺失数量"] > 0].sort_values("缺失数量", ascending=False) print(missing_report(df_order))

然后根据业务含义分情况处理:

场景处理方式
数值型字段缺失,且业务上不允许为空用中位数或均值填充,但必须记录填充逻辑
分类型字段缺失单独给一个“未知”类别,不要强行填众数
时间字段缺失如果同表有下单时间,可以用下单时间近似
不重要且缺失占比>70%直接删列,但要在清洗报告中记录

提示:任何填充动作都要在清洗报告中留痕。这也是专业数据工作者和业余选手的分水岭。

4.2 重复值处理:不是所有重复都要删

很多重复值处理教程一上来就教你drop_duplicates(),但实际业务里的“重复”需要自己定义——是全部字段重复才算,还是关键字段重复就算?

比如订单表里,同一个订单号可能因为系统重推而出现两行记录,这两行中有一个字段(如update_time)不同。这时候如果你用drop_duplicates(subset=["order_id"]),默认保留第一个出现的记录,你可能会丢掉最新更新的那一版。

正确做法是先按时间排序,再删除重复:

df_order = df_order.sort_values("update_time", ascending=False) df_order = df_order.drop_duplicates(subset=["order_id"], keep="first")

这段代码的含义是:按更新时间倒序排,然后对每个order_id保留最新的记录。要注意的是,keep="first"是保留排序后的第一条,所以先排倒序,再保留“第一条”,取到的就是最新记录。

如果你想看清楚重复的规模,可以先分组统计:

dup_count = df_order.groupby("order_id").size() dup_records = dup_count[dup_count > 1] print(f"重复订单数: {len(dup_records)}")

4.3 格式统一与异常值清洗

格式统一是数据清洗里最琐碎、也最体现耐心的部分。常见问题包括:

  • 日期格式不统一(2024/01/01、2024-01-01、20240101共存)
  • 字符串字段里有不可见字符(全角空格、换行符、\u3000)
  • 数值字段里混入千分位逗号或货币符号
  • 手机号、身份证号等字段被Excel科学计数法“改造”过

日期格式统一是我最常用的操作,推荐用pd.to_datetime(),并指定errors参数:

df["created_at"] = pd.to_datetime( df["created_at"], format="mixed", # pandas 2.0+支持自动混合格式解析 errors="coerce", # 解析失败转成NaT,而不是直接报错 )

注意:解析失败转成NaT后,你需要回头检查这些无法解析的值,它们往往隐藏着数据质量问题的线索。

字符串清洗方面,我习惯用.str系列方法:

# 去首尾空白(包括全角空格) df["user_name"] = df["user_name"].astype(str).str.strip() # 去掉中间的全角空格 df["user_name"] = df["user_name"].str.replace("\u3000", "") # 统一大小写(比如邮箱) df["email"] = df["email"].str.lower()

4.4 中文乱码的终极大法

处理中文数据源,最怕的就是乱码。解决思路分三步:

  1. charset_normalizer检测原始编码。
  2. 读取时明确指定编码。
  3. 输出时统一用utf-8-sig,方便Excel直接打开不乱码。
df_order.to_csv("output/order_cleaned.csv", index=False, encoding="utf-8-sig")

如果数据源已经乱码了,想“还原”几乎不可能,因为信息已经在编码转换中丢失了。这种情况下只能回到源头,重新获取正确编码的文件。这也是为什么我一直在强调:读文件时先检测编码,不要用默认参数。

5. 数据源合并:多表关联的正确打开方式

5.1 merge前的“三查”

多数据源清洗的最后一步通常是合并,而合并之前的准备工作决定了合并的质量。我每次merge前都会做三件事:

  1. 查键类型:关联字段类型必须一致,见4.3中提到的user_id例子。
  2. 查键重复:如果关联键在左表或右表有重复,merge后会产生笛卡尔积,行数会暴增。所以先检查重复。
  3. 查键覆盖率:用isinmerge(how="left", indicator=True)确认左表有多少记录能匹配到右表。
df_merged = df_order.merge( df_user, on="user_id", how="left", indicator=True, ) # 检查匹配情况 print(df_merged["_merge"].value_counts())

_merge列的值有三种:both(两边都有)、left_only(只在左表)、right_only(只在右表)。如果left_only占比过高,说明很多订单对应的用户信息缺失,这种数据在下游分析时是要特别标注的。

5.2 concat追加多表时的字段对齐

如果压缩包里是多个结构相同的月度文件(比如order_202401.csvorder_202402.csv),用pd.concat纵向拼接:

df_all = pd.concat([df_jan, df_feb, df_mar], ignore_index=True)

这里有个隐藏坑:如果某个月份文件新增了一列,其他月份没有,直接concat后缺失的列会自动补NaN。这本身不是问题,但如果新列的业务含义需要统一,就要在合并前对齐列名。

一种更稳妥的做法是先统一列顺序:

common_cols = df_jan.columns.tolist() df_feb = df_feb.reindex(columns=common_cols)

5.3 多数据源的“数据血缘”记录

数据清洗做完之后,我强烈建议你顺手生成一份“数据血缘说明”,至少包含:每个字段来自哪个源文件、做了哪些清洗操作、清洗前后的行数和关键指标对比、输出文件路径。这份说明不仅是给同事看的,也是给未来的自己看的——三个月后你回过头来用这批数据时,如果没有这份记录,几乎等于重新踩一遍坑。

6. 常见问题排查:打包好的避坑指南

6.1 zip解压相关的报错

报错信息可能原因解决办法
could not find eocd文件下载不完整或不是zipfile命令确认格式,重新获取文件
not all files were readable压缩包内某个文件损坏unzip -t逐文件检查,单独提取损坏文件
文件名乱码编码问题(GBK vs UTF-8)cp437重编码再解码,或改解压库
缺少分卷zip分卷压缩后只拿到一部分确认所有.z01.zip文件在同一目录

6.2 pandas读数据报错

报错信息可能原因解决办法
ParserError: Error tokenizing data分隔符不是逗号指定sep,常见的有\t;、`
UnicodeDecodeError编码不对charset_normalizer检测编码
MemoryError文件太大分块读取、只读必要列、换polars
Excel file format cannot be determined文件扩展名和实际格式不符file命令确认,改用正确读取方式

6.3 清洗过程中容易“翻车”的三个细节

  • 不要在原DataFrame上直接改:建议操作前先df_copy = df.copy(),避免后续代码出错时数据已经被污染。
  • 不要用循环逐行清洗:pandas的向量化操作比逐行iterrows()快几个数量级,数据量大时差距尤其明显。
  • 不要忽视索引:合并或分组后索引可能乱掉,需要时用reset_index(drop=True)

根据我自己的实操经验,数据清洗这件事,80%的时间花在“处理数据源本身的问题”上,只有20%的时间真正花在写清洗逻辑上。很多人一上来就写清洗函数,结果被乱七八糟的数据源折磨得欲哭无泪。这个数据清洗数据源.zip的案例正好说明:从解压、摸底、读取、探查到清洗、合并,每一步都有章可循。把这套流程跑熟了,后面遇到任何“新压缩包”都不会慌。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/2 17:53:49

用Grok Bot编排AI团队:单人开发效率翻倍的工程实践

“一个人就是一支团队”这句话,过去更多是打鸡血用的。直到我把 AI 工具真正编排起来,才发现它正在变成一种可复制的工程方法。 我最近用 Grok Bot 作为核心协作节点,搭配几种常见的 AI 编程工具,尝试了一次“单人 AI 团队”的开…

作者头像 李华
网站建设 2026/9/2 17:53:47

ComfyUI V100中文整合包:一键部署AI绘画节点工作流

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/2 17:49:56

SH79F1611驱动源码全解析:8051内核MCU从外设到移植实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/2 17:48:51

2025手机内存怎么选?8GB/12GB/16GB与端侧AI需求全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/2 17:48:27

计算机网络入门:小白必懂的核心概念与应用场景

计算机网络入门:小白必懂的核心概念与应用场景📝 本章学习目标:本章是基础概念部分,帮助零基础读者建立计算机网络的初步认知。通过本章学习,你将全面掌握"计算机网络入门:小白必懂的核心概念与应用场…

作者头像 李华