用Python处理Excel数据,这些坑你踩过吗

📅 2026/8/13 7:59:19
用Python处理Excel数据,这些坑你踩过吗
你打开Jupyter敲下df pd.read_excel(销售数据.xlsx)看着前五行输出像模像样心里暗想“就这”可接下来按月份汇总时日期列变成了“2023-01-01 00:00:00”销售金额列里藏着几个文本型数字总数差了几千。你翻遍数据发现罪魁祸首是个空白单元格它在那儿笑你。Excel是给人眼设计的Python是给逻辑设计的两者之间有一道布满暗坑的沼泽地。这篇文章我们一起趟一遍。数字不是数字而是“伪装者”很多人在Excel里存长数字比如身份证、订单号、卡号。Excel为了省地方会自动转成科学计数法1.23E17。你用pandas读取它默认按数值类型处理于是精度丢了尾号全变0。你说那好我指定dtypestr读结果读出来的是123456789012345600或者1.23E17因为你没明白Excel保存数据时已经牺牲了精度。你以为是字符串它其实是浮点数你以为是浮点数它变成了科学计数法。更烦的是“文本型数字”。在Excel界面里单元格左上角有一个绿色小三角意思是“这里的数字是文本格式”。pandas读完这一列是object dtype你df[金额].sum()会把所有数字拼接成一个大字符串。你需要pd.to_numeric(df[金额], errorscoerce)把非法值置为NaN再处理。Excel的单元格格式只是化妆数据在底层已经定型格式挡不住pandas的一意孤行。日期比前任还难猜的格式read_excel处理日期行为像天气一样不可预测。有时返回Timestamp对象有时返回字符串有时返回一串五位数——那是Excel内部的日期序列号比如44828代表2023年9月1日。你打印出来明明是一个整数却要掰着手指头从1900年1月1日往后算。日期在Excel眼里只是一个数字在pandas眼里是一套时区系统在你眼里是一切。如果你用parse_dates参数指定日期列它还算靠谱。一旦列里混了“2023/09/01”和“2023年9月1日”两种写法read_excel可能直接罢工把整列读成字符串。你转头用pd.to_datetime强制解析它又会把“2023年”解析成2023-01-01因为年后面没有月日默认补成1月1日。唯一稳妥的办法是写自己的日期解析函数而不是指望Excel自觉。空值一个比薛定谔的猫还复杂的现象Excel里的“空”在Python眼里至少有四种形态真正的空单元格NaN、空字符串、None、字符串“NA”。pandas的read_excel默认把空单元格和“NA”字符串都转成NaN看起来好像很友好。但当你fillna()后原本是空字符串的单元格根本不会被填充因为它们不是NaN。你再一数还有几行写着“#N/A”和“-”你没处理到最后统计结果仍然缺着。空值不是一个值而是一堆值每种空都等着你给个说法。更抓狂的是pandas把“NA”当作缺失值但你的业务数据里真的存在城市代码“NA”。你想保留它于是设置keep_default_naFalse。结果所有真正的空单元格又变成空字符串而不是NaN你的isna().sum()统计彻底失效。pandas的缺失值判断是一套默认协议但不吻合你的业务逻辑。要收拾这种局面最好老老实实先astype(str)再逐项判断各种各样的空值形态。合并单元格数据界的连环诈骗合并单元格是Excel给人类视觉的馈赠却是数据分析师的毒药。pandas读取时被合并的区域只有左上角有值其余全是NaN。比如一个表格中“华东区”合并了三行读进来后只有第一行的“华东区”有值下面两行显示NaN。你按区域分组出现了一堆“空组”。合并单元格是给眼睛看的不是给代码用的。你可能会用fillna(methodffill)来向下填充好像解决了。但如果合并单元格不是垂直排列而是水平合并你填充方向就错了。更麻烦的是当你把处理好的数据写回Excel想恢复合并单元格pandas没有原生支持你得手动用openpyxl的merge_cells一个个处理。用Python处理Excel最贵的不是代码而是你反复试错的时间。公式与缓存你看到的是结果代码读到的是公式遇到公式是家常便饭。在Excel里你看到B10单元格显示100因为公式SUM(B2:B9)算好了。用openpyxl直接读B10.value可能返回字符串SUM(B2:B9)。如果你不检查类型把这个字符串丢进报告里老板会以为是乱码。虽然pandas的read_excel底层会用openpyxl缓存的数值但缓存存在的前提是这个文件在Excel里被打开并保存过。公式是Excel的生命却是Python的陷阱。如果文件是从在线Excel导出的或者由某些库生成的缓存值压根儿不存在。你读到的可能是None。你心想None就当成0吧。结果本月的“合计”一栏在你输出时变成了0在Excel里却写着大大的1000。不要相信read_excel读到的值除非你确认公式已经被Excel计算过。实在不行用data_onlyTrue碰碰运气同时做好后备方案。性能循环一千行你就以为自己在写爬虫曾经有位同事用openpyxl对一万行数据逐行读取、逐行计算、逐行写入整个脚本跑了二十分钟。他说Python太慢。其实不是Python慢是他用错了工具。pandas的Vectorized操作跑同量级数据只需几十毫秒。逐行操作pandas就像开着跑车在胡同里倒车入库。性能的另一个大坑是Excel的“垃圾格式”。你的工作表明明只有几行数据但某人按了CtrlEnd把格式刷到了第1048576行。pandas读取时会尝试让每一行都有对应的索引于是那个孤零零的格式占用量被解释成上百万个NaN内存瞬间爆炸。解决办法是读取前先检查Excel的“最后一格”或者指定nrows和usecols把无辜的空白区域挡在门外。Excel的空白不是真空是格式的暴风雪随时拖垮你的内存。索引的阴谋0和1的世界与1和0的世界你df.drop(index2)删掉一行然后df.loc[3]想取原来的第四行取到了。下一次你又删了几行索引变得稀疏你df[金额][df[金额]100]之后再想reset_index()却忘了加dropTrue结果原索引变成一列“index”写进Excel后和你的目标列对不上。索引是pandas的翅膀也是你的深渊。更隐蔽的是当数据里有重复的索引值df.loc[2]会返回一个DataFrame而不报错你以为是一个Series做后续操作时全乱套。你反过来用iloc没问题但行号跟Excel行号之间永远差着1因为Excel第一行是标题。你总得在两者之间跳来跳去一不留神就把“第2行”当成索引2。不要用Excel的坐标思维去理解pandas的索引它们是两个宇宙。文件格式的隐形陷阱xlsx、xls、还有CSV写pd.read_excel(数据.xls)报错“Missing optional dependency xlrd”。你乖乖pip install xlrd结果装的是2.0.1版本只支持.xls不支持.xlsx。于是你又去查手册发现pandas对.xlsx默认用openpyxl对.xls只能用xlrd而老版本xlrd是同时支持的。版本兼容的坑比你想的要深。格式的坑在于你永远猜不到对方手上是什么文件。CSV也一样。Excel保存CSV时默认编码是本地代码页中文系统就是gbk你用utf-8读必然乱码。而且CSV没有样式、没有公式、没有多Sheet一旦原始Excel里有两个工作表你转成CSV就只能保存当前页信息悄悄丢了。别把CSV当成Excel的廉价替代品它本质上是Excel的阉割版。写回Excel的格式地狱pandas的to_excel写出的文件样式基本是素颜没有加粗表头、没有颜色、没有调整列宽公式也全变成了静态数值。更重要的是如果你用Excel函数和数据验证pandas写回后会全部剥离。你辛辛苦苦建好的模板经过一个to_excel就面目全非。对pandas来说样式不重要但对你来说样式可能是老板的全部KPI。要保留格式你得用openpyxl加载原文件然后手动把DataFrame逐格写入指定位置再手动设置字体、边框、列宽。这一套操作下来代码量轻松超过两百行而你还得处理“原文件中有多个Sheet”和“Sheet名有空格”的情况。所谓“用Python处理Excel”到最后你会发现自己其实在写一份Excel操作说明书。编码与字符的玄学读取CSV时你一拍脑袋用encodingutf-8看到一堆“锟斤拷”就知道完了。换成gbk可能又因为某个特殊字符报UnicodeDecodeError。你试着用errorsignore结果字符被吞掉数据悄无声息地丢了。编码问题往往在你觉得“这也要讲”时出现。Excel单元格里还藏有隐形字符\u200b零宽空格、\ufeffBOM、不间断空格\xa0。它们不显示但你的字符串匹配永远失败。最搞笑的是Excel的TRIM函数也去不掉这些鬼东西Python的strip()同样无能为力。你得祭出正则表达式才能还单元格一片干净。Excel的单元格是一个杂物间Python要当保洁员。重复列名和同名陷阱Excel表头里有两个“金额”一个叫“金额”另一个叫“金额 ”后面多了空格。pandas读入后会把你拆开一个叫“金额”另一个可能被自动改成“金额.1”。也有时会完全重复你df[金额]返回一列但你可能忘了那一列是哪个。列名在Excel里可以重复但在pandas里必须唯一这是两个世界的基本法律。另外如果表头有换行符或者特殊符号pandas会一并保留你在select时写错了报KeyError。你检查了一遍发现明明看起来一样就是选不出来因为多了个不可见字符。用df.columns打印一下也许能发现端倪但很多人在这一步之前已经放弃了。不要相信用肉眼看到的列名要用编码和清单验证。收尾讲点方法论踩了这么多坑你可能会说那我干脆不用Python了。其实不是。这些问题背后有一个共同的规律你是在用Excel的经验去操作一个编程工具而编程工具的默认假设与Excel完全不同。Excel把数据、格式、公式、显示方式捆在一起而Python希望你把它们拆开。如果你想少踩坑处理任何Excel前先备份原文件读进来后先别急着用用info()和head()观察每一列的真实类型写回文件前明确你到底要保留什么是数值还是样式还是公式。在你打开read_excel之前先问自己三个问题数据是什么类型、缺失值是怎么表示的、公式要怎么处理。想清楚这些再动手也不迟。在这些问题得到答案之前你写的代码都只是在给Excel擦屁股。要的话就让Python成为你的利器而不是另一台制造垃圾的机器。