资讯动态

Pandas批量汇总Excel文件:从环境配置到性能优化的完整指南

发布时间:2026/10/14 1:04:10 来源:尧图企业网站定制
在数据处理这一行摸爬滚打久了你会发现一个特别常见的需求汇总大量Excel。不管是月底财务对账、年终销售业绩合并还是运营同学收集各地周报最后都会落到一个问题上——几十个甚至上百个结构相似的Excel文件怎么快速合并成一个总表。手工复制粘贴效率太低VBA写起来又不够灵活这时候Pandas就是最顺手的工具。这篇博文我就完整梳理一遍用Pandas做Excel批量汇总的实操流程从环境准备到数据清洗再到性能优化把我在实际项目中踩过的坑、总结出的经验一次性说清楚给正在被Excel汇总折磨的各位一个能直接照抄的方案。1. 场景拆解为什么会有汇总大量Excel这个活1.1 需求来源与典型业务场景先聊聊这类需求是怎么来的。我在某公司做数据分析时遇到过这样的事业务部门每个月要各区域门店提交销售明细表每个门店一张Excel表格里是当月每一天的订单记录。到了月底总部需要把所有门店的数据合在一起做经营分析。当时有40多家门店也就是40多个文件每个文件几千行。这还只是月度例行到了年底做年度汇总文件数量直接奔着上百去。类似的场景在各行各业都有。人事部门收集各部门的考勤表、培训记录表供应链收集各仓库的库存盘点表市场部收集各渠道的推广数据表。它们的共同特点是文件数量多、结构相似但不完全一致、数据量总量可观、汇总频率固定周/月/季。这类工作如果靠手工做不仅慢而且错误率极高——复制粘贴漏一行、改错一个单元格、公式引用错位置这些都是常态。1.2 手工汇总的痛点和Pandas的切入点手工汇总Excel的痛点经历过的人都懂。首先是时间成本40个文件每个打开看一眼、复制粘贴一遍熟练的人也得折腾两三个小时。其次是出错概率越做越疲劳越疲劳越容易漏数据。再一个是版本管理问题——汇总到一半发现某个文件更新了你得从头再来。Pandas的出现把这些问题全部解决掉了。它提供了高效的数据读取、清洗、合并、聚合能力几十个文件在几秒内就能全部读入内存再用几行代码完成合并和统计。更重要的是代码写一次可以反复用下个月数据文件更新了重新跑一遍脚本就能拿到最新汇总结果完全不需要重新手工操作。这就是自动化带来的最大价值——把重复劳动变成一次性的代码投入。2. 筹备阶段环境准备与文件摸底2.1 环境依赖与基础代码骨架动手之前先把环境搭好。Pandas本身需要numpy作为底层支持读取Excel则需要额外安装读取引擎。具体来说读.xlsx文件依赖openpyxl读.xls文件依赖xlrd。为了避免各种兼容性问题我一般直接用openpyxl它能同时处理读写.xlsx格式。# 安装依赖建议在虚拟环境中操作 # pip install pandas openpyxl安装完之后验证一下环境是否正常。这里有个细节我踩过坑Pandas版本不同部分API会有细微变化建议用以下命令确认版本。import pandas as pd print(pd.__version__)基础代码骨架大概是这样的先定义文件所在目录、匹配规则然后用glob模块找出所有目标文件逐个读取最后合并。我会在下面的章节逐步展开每一块的具体写法。2.2 文件清单梳理与命名规范检查很多新手拿到任务就直接写代码结果跑起来各种报错。我的习惯是先花十分钟做一次文件摸底。这一步看起来琐碎但能省掉后面大量的调试时间。摸底要看三件事。第一文件装在哪里是全部在一个文件夹里还是分散在多个子目录中。第二文件命名有没有规律比如1月-北京.xlsx、2月-上海.xlsx这种能一眼看明白的命名是最好的否则就需要额外解析文件名来获取维度信息。第三文件数量有多少这决定了后面用哪种读取策略。我遇到过最头疼的情况是文件夹里混入了非目标文件比如某个同事放了一份汇总说明.docx在里面。如果读取时不加筛选程序就会直接报错。所以glob匹配模式一定要精确import glob # 只匹配当前目录下的.xlsx文件 files glob.glob(./data/*.xlsx) print(f发现 {len(files)} 个Excel文件)如果文件分散在多级子目录中就用glob.glob(./data/**/*.xlsx, recursiveTrue)来递归匹配。这个参数很容易被忽略但它决定了能不能把所有子目录下的文件都找出来。2.3 表头结构一致性核对要点文件摸底的第二项关键工作是核对所有文件的表头结构是否一致。理论上同一个部门按同一个模板提交的数据结构应该是统一的。但实际工作中总会有人擅自加一列、删一列、改个列名。如果不提前发现这个问题合并出来的数据会出现列错位、空值大量出现等情况。我的做法是在正式批量读取之前先写一个快速脚本预览每个文件的列名for f in files[:10]: # 先看前10个 df pd.read_excel(f, nrows5) print(f.split(/)[-1], list(df.columns))这里用nrows5只读取前5行目的就是快速获取表头不加载全部数据速度很快。如果发现列名对不上要么让提交方统一格式要么在代码里做列名映射。3. 核心环节Pandas批量读取与合并的完整实现3.1 单文件读取从read_excel的常用参数说起读取单个Excel文件核心函数是pd.read_excel。别看它简单参数用好了能让后续的数据清洗省一大半功夫。我最常用的几个参数是sheet_name、header、dtype、usecols。sheet_name用来指定读取哪个工作表默认读第一个。如果汇总的文件里工作表名称不统一比如有人把Sheet改成了数据那就需要先获取所有工作表名。dtype用来指定列的数据类型这个参数特别有用——比如订单号明明是字符串Excel里却是数字格式读进来就变成了float后面的处理全乱套。遇到这种情况直接指定类型df pd.read_excel( order.xlsx, sheet_nameSheet1, dtype{订单号: str, 门店编号: str}, usecols[门店编号, 日期, 销售额] )usecols可以只读取需要的列减少内存占用提升读取速度。当文件很大但只需要其中几列时这个参数非常实用。3.2 批量遍历glob与os.listdir两种方式的取舍批量读取文件常见的有两种方式glob.glob和os.listdir。这两者各有适用场景。glob的优势在于支持通配符匹配可以直接筛出.xlsx结尾的文件代码简洁。缺点是如果文件在多个嵌套子目录里需要配合recursiveTrue路径拼接也得自己处理。os.listdir拿到的只是一个名字列表需要自己过滤、拼接完整路径。但它更灵活可以做更复杂的过滤逻辑比如筛选文件名包含特定关键字的文件。实际项目中我大多数时候用glob只有需要特殊过滤时才用os.listdir。import os import glob # 方式一glob files glob.glob(./data/*.xlsx) # 方式二os.listdir base_dir ./data files [os.path.join(base_dir, f) for f in os.listdir(base_dir) if f.endswith(.xlsx)]两种方式效果类似选择标准主要看代码可读性和过滤规则复杂度。文件夹里文件很多时我还会对文件列表做一次排序确保合并后的数据顺序是可控的。3.3 纵向合并concat与append的差异和选择Pandas合并DataFrame有pd.concat和df.append两种方式。严格来说append已经不建议使用了官方文档明确标注为弃用新代码一律用pd.concat。合并列表里所有DataFrame的核心代码就几行df_list [] for f in files: df pd.read_excel(f, dtype{订单号: str}) df_list.append(df) # 纵向合并 result pd.concat(df_list, ignore_indexTrue)ignore_indexTrue这个参数很关键。如果不设置合并后的索引会保留各自的原始索引可能出现重复后续loc定位会混乱。设置成True之后Pandas会重新生成0到n-1的连续索引。还有一种情况需要特别处理有些汇总需求要求知道每一行数据来自哪个文件这时候需要添加一个来源列。做法是在读取每个文件时把文件名写入一个新列for f in files: df pd.read_excel(f) df[来源文件] os.path.basename(f) df_list.append(df)这个来源追踪的习惯建议从一开始就养成。数据合并之后如果发现问题有来源列就能快速定位是哪个文件出的问题。3.4 汇总计算groupby与pivot_table的统计场景合并完成之后通常还要做统计。比如按月份汇总销售额、按门店统计订单量。Pandas里groupby是最常用的聚合方式和SQL的GROUP BY逻辑一致# 按门店统计销售额总和 summary result.groupby(门店编号)[销售额].sum() # 按门店和日期两个维度统计 summary result.groupby([门店编号, 日期])[销售额].agg([sum, count, mean])agg可以在一次操作里同时计算多个统计量——总和、数量、均值。这个参数日常用的频率很高建议记住。想要做透视表效果用pd.pivot_table更直观pivot pd.pivot_table( result, values销售额, index门店编号, columns日期, aggfuncsum, fill_value0 )pivot_table适合行、列交叉统计的场景比如行是门店、列是日期单元格里是销售额。最终输出的形式非常接近Excel原生透视表领导看着也熟悉。4. 数据清洗与异常处理真正的坑都在这里4.1 常见脏数据形态空格、类型混乱、编码问题汇总工作真正花时间的不是合并而是清洗。脏数据千奇百怪但归纳下来最常见的就是那么几类。第一类是字符串里的隐藏空格。比如北京 和北京在Excel里肉眼看起来一样但Pandas读进来就是两个不同的值groupby分组时会分成两行。这个问题排查起来相当费劲因为肉眼根本发现不了。解决办法是在读取后统一做一次strip操作# 对指定的字符串列做去空格处理 for col in [门店名称, 城市]: result[col] result[col].str.strip()第二类是日期格式不统一。同一个日期列有人填2024-01-15有人填2024/1/15还有人填1月15日。Pandas读进来会得到混合类型。处理方式是统一用pd.to_datetime做转换转换不了的会变成NaTresult[日期] pd.to_datetime(result[日期], errorscoerce)第三类是数字列里混入了文本。比如销售额列里有人写了3,000元有人写了3000。这种直接读进来就是字符串求和会报错。处理方式是用正则把非数字内容去掉再转float。4.2 空值处理策略不是所有空值都要填空值处理的原则是先理解业务含义再决定怎么处理。数据缺失有时是正常的——某个店当天休息销售额为空是合理的有时是异常——某个店连续一个月没有数据就需要警惕。在Pandas里空值用NaN表示判断方法是df.isna()。过滤掉空值# 删除销售列全为空的行 result result.dropna(subset[销售额]) # 用0填充空值仅限数值列 result[销售额] result[销售额].fillna(0)这里有个容易犯的错误对整个DataFrame直接dropna()会把所有含空值的行都删掉可能误删有意义的记录。正确做法是指定subset参数只针对关键的列做空值过滤。4.3 重复数据处理去重逻辑要谨慎重复数据的处理要谨慎关键问题是判断什么算重复。是按照所有列完全一样来去重还是看某一列是否有相同值逻辑搞错了可能把有效数据删掉。# 完全重复的行去重 result result.drop_duplicates() # 根据订单号去重保留最后一条 result result.drop_duplicates(subset[订单号], keeplast)我处理过的最典型的情况是同一个订单被录了两次原因可能是文件重复提交。这时候按照订单号去重就能解决。但如果允许一个订单拆成多条记录这样去重就会误伤。所以在写去重逻辑之前一定要向业务确认清楚到底以什么维度判断重复。5. 导出与交付结果文件怎么写才规范5.1 to_excel与ExcelWriter多表输出汇总和分析的结果最终要交付给业务方。大部分业务方还是习惯看Excel文件所以Pandas的导出功能也很关键。最基础的导出result.to_excel(汇总结果.xlsx, indexFalse)indexFalse表示不写入行索引否则输出文件里会多出一列Unnamed的索引号看起来很别扭。一次生成多个工作表用pd.ExcelWriterwith pd.ExcelWriter(汇总结果.xlsx, engineopenpyxl) as writer: result.to_excel(writer, sheet_name明细数据, indexFalse) summary.to_excel(writer, sheet_name汇总统计, indexFalse) pivot.to_excel(writer, sheet_name透视分析, indexFalse)用with语句的好处是即使中途出错也会自动保存关闭文件避免文件损坏。5.2 格式美化与列宽调整用openpyxl或xlsxwriterPandas导出的Excel默认不带格式——列宽是自动的数字格式是常规。要想输出美观可以在写入之后用openpyxl打开并调整格式。from openpyxl import load_workbook wb load_workbook(汇总结果.xlsx) for ws in wb.worksheets: # 自动调整列宽 for col in ws.columns: max_length 0 col_letter col[0].column_letter for cell in col: if cell.value: max_length max(max_length, len(str(cell.value))) ws.column_dimensions[col_letter].width min(max_length 2, 30) wb.save(汇总结果_美化.xlsx)这个方法就是遍历每个单元格算出该列最长内容的长度再设置列宽。加了一个min(..., 30)上限防止个别超长文本把列宽撑得太离谱。实用下来效果不错比纯Pandas导出的裸表看起来专业很多。6. 性能优化与实战经验6.1 大数据量下的读取优化当文件总数达到几百个、单文件上万行时性能问题就浮现出来了。优化方向有那么几个实测下来都很有用。第一个方法是只读取必要列。usecols参数减少内存占用。第二个是分批处理而不是一次性把所有文件都读进内存。可以每读10个文件就合并一次释放中间DataFramebatch_size 10 df_batches [] for i in range(0, len(files), batch_size): batch_files files[i:ibatch_size] batch_list [pd.read_excel(f) for f in batch_files] batch_df pd.concat(batch_list, ignore_indexTrue) df_batches.append(batch_df) del batch_list, batch_df # 释放内存 result pd.concat(df_batches, ignore_indexTrue)第三个方法是如果只需统计结果不需要明细可以在每个文件读取后立即做聚合再把聚合结果合并这样中间数据量至少减少一个数量级。比如每个文件几千行订单聚合到按门店汇总之后就剩几十行后面的合并非常轻松。6.2 常见错误与排查速查表我把实际项目中最常遇到的报错信息整理成了一张速查表供大家对照排查报错信息原因解决方案BadZipFile: File is not a zip file文件不是真正的xlsx格式可能是xls改后缀或已损坏用openpyxl重新另存或将xls用read_excel(enginexlrd)读取ValueError: No sheet named Sheet1指定的sheet名称不存在用pd.ExcelFile(f).sheet_names先查看所有sheet名KeyError: 列名文件里没有这个列打印df.columns检查实际列名尤其注意空格和大小写TypeError: unsupported operand type(s) for 尝试对混合类型做加法/合并用pd.to_numeric()统一转为数值类型MemoryError数据量过大内存不足分批读取、只读必要列、先用groupby聚合再合并这里重点说下BadZipFile。Excel文件本质是一个zip压缩包如果文件后缀是.xlsx但内容不是标准zip格式Pandas就没法读。最常见的情况是把一个xls老格式文件直接改了后缀名。遇到这种问题可以试试用xlrd引擎读或者让提供方重新另存为xlsx格式。写在最后做个简单收尾。用Pandas汇总大量Excel核心就三步批量读取、纵向合并、统计导出。听起来简单但实际操作中数据质量永远比代码逻辑更磨人。我经历过最崩溃的一次是汇总完发现总销售额比业务方报表少了2万多排查了一下午最后发现是有个文件里多了一个隐藏工作表默认读取只读了第一个。所以现在我的代码里都会加一个校验环节——算出汇总结果后和业务方给出的总数做一个交叉对比数值对得上才交付对不上就深挖原因。最后再分享一个小习惯汇总脚本写好后我会把源文件名和生成时间写进输出文件的一个备注sheet。这样过了几个月再回来查某个数据还能追溯到是哪一批文件、什么时候跑出来的结果。数据工作不怕慢就怕不知道数据哪来的。用好Pandas这套流程几百个Excel的汇总工作做到十分钟以内真不是梦。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑