用Python自动化合并Excel:pandas数据清洗与汇总实战指南
这是《学习记录》系列的第二篇。上一篇聊的是本地开发环境怎么搭这篇直接把成果摆出来——我用Python写了一个小脚本把散落在十几个子文件夹里的Excel表格自动合并、清洗、汇总成一张总表顺带产出一份数据质量报告。整个过程没花一分钱买工具只靠Python的pandas和openpyxl两个库。如果你也经常被“月底汇总门店数据”这种活儿折磨这篇文章应该能让你少走不少弯路。我会从需求梳理、代码实现写到踩坑记录适合有Python基础但还没实际写过完整项目的人参考。1. 学习目标与场景拆解1.1 这次想解决什么问题事情起源于我上一个项目积累的数据整理需求。业务方每月会收到十几个门店发来的Excel每张表记录着当月销售明细字段基本一致但又各有各的“小脾气”有的店在表头多留了两行有的日期列是文本有的金额列里混着“元”字还有的干脆把汇总行放在中间。手工操作的话我至少要花半天时间复制粘贴中途还要反复核对有没有串行。这其实是很典型的数据整理场景多源异构表格的汇聚。我当时给自己定的学习目标很明确写一个脚本能够扫描指定文件夹下的所有Excel自动识别每个表的字段过滤无关行标准化日期和金额去重后合并成一张总表。更重要的是它必须能告诉我“处理过程中哪些数据有问题”而不是闷头出结果。1.2 为什么选择Python而不是其他工具在动手之前我认真对比过Power Query、VBA和Python。Power Query适合交互式操作点几下鼠标就能合并但操作步骤保存在文件里换一批文件就得重新设置脚本化程度不够。VBA能实现完全自动化但调试体验实在糟糕而且只能处理Windows环境下的Excel换台电脑就可能跑不起来。Python的优势在于逻辑是显式的代码版本控制一目了然跑完还能顺手生成日志。pandas处理表格数据的生态非常成熟read_excel一句就能把Excel读成DataFrame后续的筛选、去重、聚合都是现成方法。另外它跨平台我在公司Windows上写的代码回家用Mac改一改也能跑。所以我选了Python这条路虽然前期写代码花了两天但之后每个月只需双击运行一次。2. 环境准备与工具选型解析2.1 用到的库和版本这个项目核心依赖并不多我列一下实际用到的版本方便你复现环境库名版本用途pandas2.1.4数据读取、清洗、合并openpyxl3.1.2读写.xlsx格式的Excel文件pathlibPython内置跨平台处理文件路径python-dateutil2.8.2日期解析pandas会自动依赖安装命令就是常规的pip install但我强烈建议先在项目目录下建一个虚拟环境。原因很简单pandas这种库依赖很多底层包如果直接装到全局后续其他项目用到不同版本的pandas会冲突得让人头大。我用的命令是python -m venv .venv .venv\Scripts\activate # Windows source .venv/bin/activate # macOS/Linux pip install pandas openpyxl这里有个细节值得说明openpyxl只是Excel的读写引擎之一pandas读取.xlsx时会自动识别。如果你想处理老的.xls文件通常还要装xlrd但xlrd 2.0以后只支持读取xls不支持xlsx别装错了。我这次的项目里既有xlsx也有xls所以环境里额外装了一份xlrd。2.2 项目目录结构设计代码还没开始写先把目录结构想好。这一步看似多余实际能省掉后期很多麻烦。我的项目目录长这样excel_merge/ ├── .venv/ # 虚拟环境 ├── data/ │ ├── raw/ # 放原始Excel │ │ ├── 门店A.xlsx │ │ ├── 门店B.xls │ │ └── ... │ ├── output/ # 输出汇总表 │ └── reports/ # 数据质量报告 ├── scripts/ │ └── merge_excel.py # 主脚本 └── logs/ └── run_log.txt # 运行日志把原始数据、输出结果、代码分开好处是哪怕脚本跑挂了原始文件不会被破坏。同时日志单独放一个目录出了问题可以追溯最后一次运行的情况。我在项目初期犯过“直接在raw文件夹里写结果”的错误有一次读取循环和输出循环搞混结果把汇总表写进了原始数据文件夹第二次运行脚本时汇总表也被当成输入读了一遍数据立刻翻倍。从此以后输入输出目录严格隔离这是花钱买不来的教训。3. 核心实操从脚本需求到第一版可用程序3.1 读取与合并多个Excel的逻辑第一步是遍历文件夹下的所有Excel文件逐个读取。这里的核心难点不是读取本身而是“每个表的结构不完全一致”。有的表字段名叫“销售日期”有的叫“日期”。为了让后续合并顺利我在代码里维护了一个字段映射字典把同义不同名的列统一起来。import pandas as pd from pathlib import Path raw_dir Path(data/raw) output_dir Path(data/output) reports_dir Path(data/reports) # 确保输出目录存在 output_dir.mkdir(parentsTrue, exist_okTrue) reports_dir.mkdir(parentsTrue, exist_okTrue) # 字段映射把不同门店的列名统一 field_alias { 销售日期: 日期, 交易日期: 日期, 日期时间: 日期, 商品名称: 商品, 产品名称: 商品, 销售数量: 数量, 成交数量: 数量, 销售金额: 金额, 成交金额: 金额, 门店编号: 门店, 店号: 门店, } all_frames [] file_list list(raw_dir.glob(*.xlsx)) list(raw_dir.glob(*.xls)) if not file_list: raise FileNotFoundError(f在 {raw_dir} 下没有找到Excel文件请检查文件是否放对位置。) for file in file_list: df pd.read_excel(file) # 统一列名 df.columns [field_alias.get(str(col), str(col)) for col in df.columns] df[来源文件] file.name all_frames.append(df)这段代码里嵌套了一个常见技巧读取前先明确“按列名映射字段”而不是假设所有表字段完全一样。因为现实中的Excel很少有100%统一的稍微残留一列不同名等到合并后再处理就会非常被动。接下来是合并。这里需要想清楚用concat还是merge。我刚开始总是混淆这两个方法后来总结出一个记忆方式concat是“竖着拼”或“横着拼”把多个表直接叠在一起适合行数变多的情况merge是“按关键列匹配”类似SQL的join适合表之间有关联字段的时候。我这次做的事情是汇总各门店的销售明细每行都是一个独立订单不需要关联其他表所以用concatresult pd.concat(all_frames, ignore_indexTrue)ignore_indexTrue的目的是让最终表的行号重新从0开始编号否则每个分表的原始索引保留下来打印结果时会出现一列突兀的旧索引看着难受后续筛选也容易出错。3.2 数据清洗的核心步骤合并完成后真正的脏活才开始。绝大多数Excel数据表不会干干净净等着你我这次遇到的典型问题有表头下方多出空行、金额列里带单位、日期被识别成文本、商品名称前后有空格、重复行。我用了几条代码逐个处理每一步都有明确目的。先去掉完全空白的行。有些门店的表在中间会留一个空行当作“美观”的间隔但这种空行对数据分析毫无意义result result.dropna(howall)接着处理日期列。如果Excel里日期是以“2024/11/3”这样的文本形式存在pandas默认读成字符串直接排序会出现“2024/11/3”排在“2024/10/15”前面的可笑结果。统一成datetime类型是最稳妥的方案result[日期] pd.to_datetime(result[日期], errorscoerce)errorscoerce表示遇到解析不了的日期就变成NaN而不是让整个程序崩溃。之后检查这些NaN要么手动修正原表要么在报告里标记出来这一步我会放到后面的校验报告中处理。金额列的处理则需要小心。有的门店填的是“1,280元”有的直接填“1280”还有的填“1280.00”。如果直接做类型转换带“元”的字符串会让转换报错。我的处理方式是先用字符串替换把非数字字符清掉再转成数值类型# 先去掉逗号和“元”再转成float result[金额] ( result[金额] .astype(str) .str.replace(,, ) .str.replace(元, ) .astype(float) )这里为什么用astype(str)先转字符串因为金额列里可能既有数字又有字符串直接调用str.replace在非字符串列上会报错。先统一转字符串再用字符串方法处理保证不会因为某一行是数字而卡住。去重也是个高频操作。同一个门店可能因为重复提交导致同一条记录出现两次。我的去重逻辑是基于所有业务字段判断而不是单单看“来源文件”列result result.drop_duplicates(subset[日期, 门店, 商品, 数量, 金额])如果连“金额”都一样那基本可以断定是重复数据了。但有的时候两个订单金额恰好相同且数量也相同这种误删情况很难完全避免所以我不会直接覆盖原始数据而是把去重结果输出到新表并且在报告里记录每个门店删除了多少行方便业务方人工抽查。3.3 输出汇总表与校验报告数据清洗完最终目标是把结果写回Excel。这里我不建议直接把DataFrame用to_excel导出因为实际业务中往往需要“一个工作簿里包含多个Sheet”比如汇总表一张、门店明细一张、数据质量报告一张。pandas的ExcelWriter可以一次性写入多个Sheet但要注意openpyxl引擎会覆盖整个文件如果多次打开同一个路径写入容易把前一个Sheet冲掉。我一般这样写from openpyxl import load_workbook output_path output_dir / 汇总销售明细.xlsx with pd.ExcelWriter(output_path, engineopenpyxl) as writer: result.to_excel(writer, sheet_name汇总数据, indexFalse) result[result[金额].isna()].to_excel( writer, sheet_name金额缺失记录, indexFalse )关键点是with上下文管理器。它会保证writer正确关闭文件落盘。我之前图省事写了两行单独的to_excel第一行写完文件已经存在第二行再打开时openpyxl默认会认为“追加模式”结果第二个Sheet莫名其妙写到别的文件里了。校验报告我选择用最简单直观的方式——直接统计关键指标写成文本文件方便打开即看report_lines [] report_lines.append(f原始文件数量{len(file_list)}) report_lines.append(f合并后总行数{len(result)}) report_lines.append(f缺失日期行数{result[日期].isna().sum()}) report_lines.append(f缺失金额行数{result[金额].isna().sum()}) report_lines.append(f重复行数去重前{len(result) - len(result.drop_duplicates(subset[日期, 门店, 商品, 数量, 金额])}) report_path reports_dir / check_report.txt report_path.write_text(\n.join(report_lines), encodingutf-8)这份报告不需要多复杂但非常有用。它让我不用打开Excel就能判断脚本运行后数据质量大概是什么水平比如缺失金额的行是否有异常增长如果某个月突然多出几百条缺失记录很可能说明门店的新模板又改了字段名这时就该去查原表了。4. 实战中的坑与排查方法4.1 路径与编码新手最容易翻车的地方第一个坑来自Windows系统。因为门店同事发来的文件名里带着中文比如“华东区-11月-新.xlsx”Python字符串本身没问题但我在某些老旧代码里习惯性地用data\\raw\\ filename手动拼接路径一旦脚本在Mac上运行反斜杠路径就会直接报错。后来我全改成pathlib的Path对象写法统一为raw_dir / file_name跨平台瞬间省心。编码问题则更隐蔽。我在写校验报告时直接把中文字符串写入txtWindows下用记事本打开是乱码原因在于Python写入时默认用的是UTF-8而Windows记事本默认以GBK读取。解决办法是写文本时显式声明编码report_path.write_text(\n.join(report_lines), encodingutf-8-sig)utf-8-sig会在文件开头加一个BOM头记事本就能正确识别UTF-8编码。这个小细节如果不处理报告生成之后同事反馈“全是乱码”排查半天也找不到原因。4.2 pandas版本与Excel引擎兼容pandas不同版本的行为差异真是让人防不胜防。我最早写这个脚本时用的是pandas 1.5当时pd.read_excel读取xlsx默认用的是xlrd引擎xls和xlsx都读。后来升级到pandas 2.xxlrd不再支持xlsx必须显式指定openpyxl。如果你遇到报错ValueError: Excel file format cannot be determined, you must specify an engine manually多半就是引擎匹配问题。我的处理方式是在read_excel里显式传引擎if file.suffix .xlsx: df pd.read_excel(file, engineopenpyxl) else: df pd.read_excel(file, enginexlrd)这样做虽然多写了两个分支但避免了pandas自动猜测时出错的可能。另外openpyxl读大文件几万行数据时内存占用很高如果你发现脚本运行到一半机器卡死可以先试试只读不加载公式df pd.read_excel(file, engineopenpyxl, data_onlyTrue)data_onlyTrue会跳过Excel里的公式计算直接读取缓存值速度能快不少。但它有个副作用如果单元格里只有公式而没保存过结果读出来会是空值所以要在报告里留意缺失情况。4.3 数据本身带来的暗坑数据问题是最让人头疼的最典型的是Excel表格里有合并单元格。pandas读取合并单元格时通常会把合并区域里除左上角外的所有单元格读成NaN。比如一个门店表里“门店名称”一列一行合并了十行那么后面九行都是空值。这种问题在报表类Excel里极其常见。我的应对思路是不要试图在pandas里自动填充分合并单元格那太绕了。最实际的办法是在数据读取后做一次前向填充result[门店] result[门店].fillna(methodffill)ffill的意思是“用上一个非空值填充当前空值”前提是空值所在的列按顺序排列且前面的确存在可以填充的值。这个操作必须在其他清洗步骤之前做否则一旦你先把空值行删掉了合并单元格的信息也就全丢了。另一个暗坑是日期列以数字形式存储。Excel里如果单元格格式不是“日期”而是“常规”你看到的可能是45000这样的数字实际它是1900年以来的天数。pandas会把看起来像日期的数值自动识别成datetime类型但也有识别不了的时候。遇到这种问题我会先用result.dtypes打印所有列的类型再用pd.to_datetime(result[日期], unitD, origin1899-12-30)做显式转换Excel与pandas的日期起点差异就在这里用错了日期会莫名其妙差几十年。最后列名里藏着空格的问题也值得单独记录。某个门店的表标题叫“商品名称”但后面多了一个空格成了“商品名称 ”肉眼看不出来。字段映射匹配时因为多了空格没对齐这一列就会变成独立的未映射字段后续合并结果里就会出现“商品”和“商品名称”两列并存的情况。我排查了一小时才发现是多个空格在捣乱。后来我在读取后统一对列名做strip处理df.columns [str(col).strip() for col in df.columns]这个防御性操作建议放在任何真实项目的read_excel之后成本极低收益却很实在。5. 学习复盘与后续计划这第二篇学习记录写到这里最想强调的其实不是语法或函数本身而是一种处理表格数据的思维习惯先看清楚数据结构再动代码。我在这个项目里最大的收获是把日常重复的“打开Excel、查找、复制、粘贴”操作抽象成了“读取—清洗—校验—输出”四个环节。这个流程不仅适用于Excel任何表格类数据都通用掌握之后你会发现很多手工活儿都有了自动化的可能性。具体到工具层面pandas的DataFrame就像一个工作台你不需要太长时间就能把十几张表摆在一起处理。openpyxl则更像“翻译官”负责把Excel格式的细节处理好。我可以负责任地说花两天时间学这两个工具的常用操作远比每个月月底花半天手工汇总来得划算。基于这次经验我接下来的学习计划很明确一是给脚本加上简单的GUI让不熟悉命令行的同事也能双击选择文件夹使用二是把运行日志从纯文本改成结构化格式比如JSON方便后续统计脚本执行情况三是研究一下怎么把这份逻辑部署成定时任务每月自动运行并发送结果邮件。到时候应该会有新的坑可以记录等第三篇学习记录出来再和大家聊。

相关新闻

Oracle 21c Windows客户端安装包拆解与安装避坑指南

Oracle 21c Windows客户端安装包拆解与安装避坑指南

简介:Oracle Database 21c 客户端安装包(WINDOWS.X64_213000_client.zip)面向需要在 Windows 64 位环境下连接和开发 Oracle 21c 数据库的开发者与运维人员,用于解决本机缺少客户端工具、无法使用 SQL*Plus、数据泵及各类开发接口…

2026/10/13 0:10:32 阅读更多 →
MATLAB小波高通与低通滤波实战:从原理到代码实现

MATLAB小波高通与低通滤波实战:从原理到代码实现

1. 从一个实际信号处理需求说起如果你手头有一段采样率不低的振动信号、一段音频或者某类传感器采集的时序数据,想把它拆成"快变部分"和"慢变部分"分别研究,那小波分析里的高通滤波器和低通滤波器基本是绕不开的一对工具。我最早接触…

2026/10/13 0:09:31 阅读更多 →
边双连通分量缩点实战:从Tarjan求桥到树上加边最少边数

边双连通分量缩点实战:从Tarjan求桥到树上加边最少边数

1. 从一道经典图论题说起:为什么“冗余路径”值得反复琢磨第一次看到“Redundant Paths”这个标题,很多人会误以为是一道关于网络冗余备份的工程题。实际上,这是一道非常经典的图论题目,核心考察的是边双连通分量(e-DC…

2026/10/13 0:09:31 阅读更多 →

最新新闻

从零搭建ELK日志分析平台:架构设计、组件选型与实战调优

从零搭建ELK日志分析平台:架构设计、组件选型与实战调优

说实话,我在接手公司日志平台之前,排查故障靠的是“三件套”组合拳:登录服务器、grep关键字、tail -f盯屏幕。最开始那几年业务量不大,这招勉强够用,可一旦服务器数量超过两位数、日志格式五花八门,这套土办…

2026/10/13 12:58:10 阅读更多 →
数论基础实战:从紫书第10章到Kuangbin专题14的题型拆解

数论基础实战:从紫书第10章到Kuangbin专题14的题型拆解

如果你正卡在紫薯第10章数学,准备转战Kuangbin专题14数论基础,我给你一句忠告——书上的公式全会了,不代表你看到题目能动手。我当年啃完紫书第10章,带着一堆“看起来都能推导”的公式点开专题14,第一题就把我按在地上…

2026/10/13 12:58:10 阅读更多 →
Conda报错排查指南:从repodata失败到依赖冲突的完整解决

Conda报错排查指南:从repodata失败到依赖冲突的完整解决

两行红字一贴,小白当场懵,老手也得先深吸一口气。Collecting package metadata (current_repodata.json): failed和Solving environment: failed这对组合,几乎每个用过 Anaconda 的人都撞见过:刚装完环境准备conda install pandas…

2026/10/13 12:58:10 阅读更多 →
基于Halton序列的图像加密解密算法详解与Matlab实现

基于Halton序列的图像加密解密算法详解与Matlab实现

做图像加密解密这个方向的,八成都有过这种感受:教材里的经典算法讲得头头是道,真到自己动手复现,却总在“看起来像那么回事”和“实际效果经不起推敲”之间反复横跳。我之前在做基于Halton序列的图像加密解密项目时,最…

2026/10/13 12:58:10 阅读更多 →
Kettle 9.3 源码编译实战:从 GitHub 构建可部署的 PDI 生产环境

Kettle 9.3 源码编译实战:从 GitHub 构建可部署的 PDI 生产环境

简介:本资源为Kettle(Pentaho Data Integration)最新9.3版本的官方安装包百度网盘直链,面向数据工程师、ETL开发人员及大数据初学者,解决国内用户因网络限制难以获取国际开源ETL工具的问题。资源以1个14KB的docx文档形…

2026/10/13 12:58:10 阅读更多 →
Agent上线最后一公里:评测门禁、可观测性埋点与AgentOps闭环落地指南

Agent上线最后一公里:评测门禁、可观测性埋点与AgentOps闭环落地指南

1. 从"能跑"到"敢上线":Agent 交付的最后一公里到底卡在哪做过 Agent 项目的人大概都有过这种体验:本地 Demo 跑得飞起,工具调用、多轮对话、RAG 检索全都正常,演示给同事看的时候掌声一片。可真到了要往生产…

2026/10/13 12:57:09 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

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

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

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

2026/10/12 14:48:49 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

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

2026/10/12 18:11:17 阅读更多 →