周五下午,同事甩给我一份水果进货表,Excel里密密麻麻几百行,让我统计每种水果一共进了多少。我瞄了一眼,数据里还混着“苹果(大)”、“苹果 ”、“apple”这种乱七八糟的写法。如果手动筛选再求和,我估计晚饭前都别想下班。这种活儿,以前我也认,打开Excel,筛选,SUMIF,拖拖拽拽,运气好十分钟搞定,运气不好遇到合并单元格和隐藏行,心态直接崩。
后来我彻底换了个思路:用Python。不管表格里有几百行还是几万行,不管苹果叫“苹果”还是“apple”,不管今天是统计水果还是统计外卖订单,代码一跑,结果几秒钟就出来。今天这篇就把我的做法完整拆给你,从环境准备到处理脏数据,再到把脚本封装成真正能“一键运行”的小工具,全程按小白能跟上的节奏来。你只要会复制粘贴代码、会改文件路径,就能搞定。
1. 为什么非要用Python来汇总Excel——手动操作的真实痛点
先说说我为什么放着现成的Excel不用,非要写Python。
你可能觉得Excel自带的数据透视表、SUMIF函数已经够用了。没错,数据规规矩矩的时候确实够用。但真实世界的表格从来不是按教科书长的。
我那天拿到的水果表,第一列是“品名”,第二列是“数量”,第三列是“单位”。看起来很简单对吧?实际里面全是坑:有的格子写着“苹果”,有的写着“苹果 ”,多了个空格;有的写“苹果(红富士)”;还有的直接把数量写成“10个”、“5斤”这种带单位的文本。用SUMIF一求和,结果全是0,因为Excel根本不会把“10个”当成数字来算。
就算数据是干净的,手动操作也有一堆隐藏问题。
第一,筛选求和的时候容易漏行。几百行的表格,鼠标滚轮滚来滚去,眼神稍微一飘就漏掉一条。第二,领导的需求经常变。你今天按品名汇总,明天可能要求按产地汇总,后天可能要求把两个sheet的数据合在一起再汇总。每次需求一变,你就得重新筛一遍、重新拉一遍公式,时间全搭进去了。第三,这种活是周期性的。如果每个月都要处理类似的表,手动处理一次和写个脚本处理一百次,长期来看效率天差地别。
Python的方案底层逻辑完全不一样:把数据丢给程序,让程序按你的规则跑,不管是10行还是10万行,速度都差不多,而且只要你脚本写得对,永远不会漏。
核心思路就三步:读取Excel、按规则分组汇总、把结果写回Excel。听着简单,但每一步都有细节,下面一个个说。
2. 环境准备与工具选型——pandas和openpyxl的分工
很多小白一听“配置环境”就头疼,觉得这是程序员才需要干的事。其实装Python比装某些国产软件还简单,不需要懂什么底层原理,跟着步骤点就行。
2.1 安装Python的几个关键选择
去Python官网下载安装包,版本选3.10以上的就行,别纠结哪个新用哪个。安装的时候有一个勾选框叫“Add Python to PATH”,一定要勾上,不然后面在命令行里敲python会提示找不到命令。
装完验证一下:打开命令行(Windows按Win+R,输cmd回车),输入:
python --version如果显示类似Python 3.10.x,说明装好了。这一步卡住的人不少,多半是没勾PATH选项,重装一遍,把勾打上就可以。
2.2 为什么选pandas和openpyxl这两个库
处理Excel的Python库有好几个,我直接给结论:你只需要装pandas和openpyxl。
pip install pandas openpyxl你可能听说过头号Excel库openpyxl,也听说过pandas干这个活儿很强大。它俩不是二选一的关系,而是配合关系。
| 库名 | 主要用途 | 擅长的事 |
|---|---|---|
| pandas | 数据处理、分组汇总、清洗 | 按条件分组求和、过滤行、合并多个表,底层基于NumPy,快 |
| openpyxl | Excel文件读写底层引擎 | 让pandas能把数据写进xlsx格式,也支持直接改单元格样式 |
简洁一点说:pandas是干活的,openpyxl是跑腿的。pandas要读取xlsx文件的时候,会叫openpyxl去当翻译;pandas要把结果写回Excel的时候,也靠openpyxl落地。你直接安装pandas的时候,openpyxl不一定会自动装全,所以两个都装一遍最省事。
换一个角度说选型:为什么不选xlrd、xlwt?xlrd读老格式.xls没问题,但新版已经不支持.xlsx的读取了;xlwt只能写.xls,功能太老。这三个库之间的区别,新手不用记住,直接pandas加openpyxl,兼容性最好,资料最多,出了问题随便一搜就有答案。
2.3 一个必须知道的概念:DataFrame长什么样
装好库之后,你接触的第一个pandas核心概念叫DataFrame。你可以把它理解成“一个带行号和列名的Excel表格”。比方说水果表读进来之后,控制台会显示类似这样的结构:
| 品名 | 数量 | 单位 | |
|---|---|---|---|
| 0 | 苹果 | 10 | 斤 |
| 1 | 香蕉 | 5 | 斤 |
| 2 | 苹果 | 8 | 斤 |
最左边那一列0、1、2是索引,相当于Excel的行号,但pandas里叫index。品名、数量、单位是列名。记住这个结构,后面一切操作都好理解。
3. 读入Excel数据——从小白视角看pandas的read_excel
写代码的第一步,是让Python知道你文件放在哪、叫什么名字、要读哪个sheet。
3.1 第一段能跑的代码
假设你的Excel文件名字叫“水果进货表.xlsx”,和你的Python脚本放在同一个文件夹,那么读取数据的代码很简单:
import pandas as pd df = pd.read_excel("水果进货表.xlsx") print(df.head())df.head()的意思是显示前5行,用来快速确认数据读对了没有。
如果文件路径不一样,改成绝对路径,Windows下面要注意反斜杠的问题:
df = pd.read_excel(r"D:\我的文档\水果进货表.xlsx")加粗重点:路径前面加一个r,表示这个字符串里的反斜杠不要当转义字符处理。不然\我这种路径会直接报错或乱掉。这个是新手最容易踩的坑。
3.2 三个高频参数:sheet_name、header、usecols
真实的Excel文件往往有好几个sheet。默认read_excel只读第一个sheet。如果水果数据在第二个表里,只要加一个参数:
df = pd.read_excel("水果进货表.xlsx", sheet_name="Sheet2")如果表格第一行不是表头,而是从第三行才出现“品名”“数量”这些列名,就指定表头行号(0表示第一行):
df = pd.read_excel("水果进货表.xlsx", header=2)如果表里有多余的列,只要品名和数量两列,我习惯在读取的时候直接筛选掉,省得后面清理:
df = pd.read_excel("水果进货表.xlsx", usecols=["品名", "数量"])用usecols还有一个小技巧:如果列名含特殊字符,可以直接用列号,比如“A, C”这种写法:
df = pd.read_excel("水果进货表.xlsx", usecols="A,C")先读进来,看一眼前几行,确认数据没跑偏,再去做汇总。这一步花不了十秒钟,但能给你省掉后面排查问题的半小时。
4. 按水果名称汇总数量——groupby与手动循环两套方案
数据读进来了,接下来是重头戏:按品名分组,把数量加起来。这里给你两套方案,一套是pandas的正规军打法,一套是给完全看不懂正则和分组逻辑的新手准备的备选方案。
4.1 正规军打法:groupby
用一个生活化类比:groupby就像把一堆积木按颜色倒进不同的桶里。品名这一列一样的水果,会被归到同一个桶里,然后再对每个桶执行求和操作。代码就一行:
result = df.groupby("品名")["数量"].sum() print(result)运行之后你会看到类似这样的输出:
品名 苹果 18 香蕉 5 Name: 数量, dtype: int64苹果的10加8等于18,香蕉是5,分毫不差。如果你还想知道每种水果有几条进货记录,也就是统计出现次数,改成count()就行:
df.groupby("品名")["数量"].count()如果想同时求总和和平均值,一行也可以写完:
df.groupby("品名")["数量"].agg(["sum", "mean"])这条代码一次给出总和和平均进货量,应对领导临时加需求特别好使。
有一个细节值得单独说。groupby的结果是一个Series,不是DataFrame。用print打印没问题,但后面如果想把这个结果再加工、再合并,最好把它转回DataFrame:
result = df.groupby("品名")["数量"].sum().reset_index()reset_index()把“品名”变成普通列,索引重新变成0、1、2……这样result的结构就变成:
| 品名 | 数量 | |
|---|---|---|
| 0 | 苹果 | 18 |
| 1 | 香蕉 | 5 |
后面写回Excel、排序、加总计行,都基于这种规整的结构来操作,最顺手。
4.2 备选方案:手动循环统计
如果看到groupby感到头大,完全没关系。Python还有最朴素的写法——用字典手动统计。逻辑特别直白:一个空字典,循环每一行,如果水果名字还没出现过,就新建一个键;如果出现过了,就把数量累加上去。
result_dict = {} for index, row in df.iterrows(): name = row["品名"] num = row["数量"] if name in result_dict: result_dict[name] += num else: result_dict[name] = num print(result_dict)运行完你会得到一个字典:{'苹果': 18, '香蕉': 5}。这个方案虽然没有groupby那么优雅,但每一步都是人眼看得懂的逻辑:遇到苹果就加,遇到香蕉就加,最后字典里存的就是每个水果的总数。对于纯小白,我甚至推荐先写一遍这个循环,体会一下“一行一行处理数据”到底是什么感觉,然后再去拥抱groupby的简洁。
4.3 两种方案怎么选
如果数据量在几千行以内,两种方案速度上几乎没有区别。如果数据量到了十万行以上,groupby的效率会明显优于手写循环。不过对这个场景来说,你只需要记住:groupby是pandas的核心功能,也是日常最常写的代码,熟练它就等于跨过了Excel自动化的第一道门槛。
5. 真实数据里的脏东西——汇总前必须处理的地雷
标题写的是“告别手动统计”,但你拿到手的原始数据通常不会让你跑一遍代码就美美收工。我那天处理的水果表就是个典型,汇总之前必须先洗数据。下面几种情况,你大概率也会遇到。
5.1 数量列是文本,还带着单位
水果表里最常见的坑就是“10个”“5斤”这种写法。表面看是数字,实际上在Excel里是文本,pandas读进来也会当成字符串,一求和直接报错或者全变0。
处理思路是把数量列清洗成纯数字。一行正则替换就能处理:
import pandas as pd df["数量"] = df["数量"].astype(str).str.replace(r"[0-9.]", "", regex=True)先把数量列变成文本,再把数字和点以外的符号(比如“个”“斤”)统统替换为空字符串,最后转成数值。
有一个更简单的思路,如果单位不影响汇总逻辑(你只管数量数字,不管单位是不是斤),那么不管后面跟的是“个”还是“斤”,统一不要。代码如下:
df["数量"] = df["数量"].astype(str).str.extract(r"(\d+\.?\d*)").astype(float)这一行的意思是:从文本里提取第一个数字部分(支持小数),然后转成小数。如果某个单元格写的是“苹果一箱”,提取不出来,会变成NaN,后续可以用fillna(0)补成0。这一步在真实数据里特别常用。
5.2 同一水果有多个名字
我那份表里,苹果至少有三种写法:“苹果”、“苹果 苹果”、“apple”。如果直接按品名分组,这三种会被当成三种水果统计,结果完全不对。
处理办法也简单,定义一张别名映射表,把所有写法映射到标准名:
name_map = { "苹果": "苹果", "苹果 ": "苹果", "apple": "苹果", "红富士": "苹果", } df["品名"] = df["品名"].replace(name_map)replace的意思是:遇到“苹果 苹果”就替换成“苹果”,遇到“apple”也替换成“苹果”,替换完之后再groupby,结果就收敛了。
5.3 单元格前后空格
Excel里手打数据经常带上空格,肉眼看不出来。我的习惯是汇总前先做一个通用清洗:把品名列里所有的首尾空格去掉、把列名里的空格也去掉。
df["品名"] = df["品名"].str.strip()这一步要放在别名映射之前做,因为“苹果 苹果”如果不去掉首尾空格,even替换表也匹配不上。
5.4 空行和无效数据
表格里偶尔会有整行空的,或者数量是负数要剔除的。最简单的做法是:把品名为空的行删掉,数量为正数的才保留。
df = df.dropna(subset=["品名"]) df = df[df["数量"] > 0]dropna是“把缺失行删掉”,subset指定只看品名这一列有没有空值。这两行写完,很多莫名其妙的问题就自动消失了。
5.5 清洗顺序很重要
不要一上来就groupby。我建议的顺序是:
- 读入数据后用
df.info()或df.head()快速看一眼结构; - 处理列名(去空格、统一大小写);
- 处理单位、提取数字;
- 处理品名的别名和空格;
- 删除空行和无效值;
- 最后再groupby汇总。
为什么顺序这么重要?因为如果你先groupby,再清洗,分组的结果已经错掉了,清洗也白清洗。清洗和汇总的关系,就像先洗菜再切菜——你非得先切再洗,最后菜全是湿的,还得返工。
6. 把脚本武装成“一键”小工具——写回Excel与批量处理改造
到这一步,你已经能把一个脏乱的水果表汇总成干净的结果了。但离标题说的“一键”还有一步:让脚本自动把结果写回Excel,并且支持处理多个文件。
6.1 把结果写回Excel:详解to_excel
pandas写Excel的接口和对read_excel是对称的。一行代码就能落盘:
result.to_excel("水果汇总结果.xlsx", index=False)index=False的意思是不要把索引写进Excel。如果不加这个参数,结果表里会多出左边一列0、1、2、3,看着很蠢。
如果你想控制写入到第几个sheet,加sheet_name参数:
with pd.ExcelWriter("水果汇总结果.xlsx") as writer: result.to_excel(writer, sheet_name="汇总", index=False)这种写法还能实现一个需求:把原始明细和汇总结果放到同一个Excel的两个sheet里。
6.2 加一个总计行和排序
领导看汇总表,很多时候还想知道总数是多少,还想让数量最多的水果排在最前面。三行代码就能实现:
result = result.sort_values("数量", ascending=False) total_row = pd.DataFrame([{"品名": "总计", "数量": result["数量"].sum()}]) result = pd.concat([result, total_row], ignore_index=True)sort_values按数量降序排,concat把总计行加在最后,这个表就是按标准汇报逻辑输出的。
6.3 批量处理多个Excel文件
这个需求特别常见。每个月销售发来12张表,分别叫“1月水果表.xlsx”、“2月水果表.xlsx”……你不可能写12遍代码。用Python的glob库遍历文件夹,自动找所有xlsx文件,批量处理:
import pandas as pd import glob all_data = [] for file in glob.glob("水果/*.xlsx"): df = pd.read_excel(file) # 清洗(省略),按你的实际情况写 df["来源文件"] = file all_data.append(df) merged_df = pd.concat(all_data, ignore_index=True) result = merged_df.groupby("品名")["数量"].sum().reset_index() result.to_excel("全年水果汇总.xlsx", index=False)这个脚本的逻辑:先用glob通配符找到“水果”文件夹下所有xlsx文件,逐个读取、清洗,加上“来源文件”这个列标记数据出处,然后合并成一个大表,最后groupby汇总。这样不管你有12个文件还是120个文件,代码一行都不用改。
6.4 真正意义上的“一键”运行
脚本写完之后,怎么做到双击就运行,不用每次打开命令行敲python?Windows下写一个.bat批处理文件,两行内容:
@echo off python 水果汇总.py pause保存成一键汇总.bat放在和脚本同一个文件夹,以后双击这个bat就行了。pause的作用是程序跑完后窗口不会立刻闪退,方便你看到结果。想更丝滑一点,可以把脚本里的路径全部改成相对路径,再把源数据文件都放在同一个文件夹下,配合input()输入日期或者文件名,整个流程就很像一个专用小软件了。比如:
month = input("请输入要汇总的月份,例如202506:") file = f"水果进货表_{month}.xlsx"这么一改,每个月拿到新表,双击bat,输入月份,回车,结果文件就自己生成好了。这套流程我用下来,比在Excel里反复筛选、拉公式、做透视表省太多时间,而且接手同事的表也完全不慌——数据再乱,跑一遍清洗逻辑都能掰回正轨。
后来我又给这个脚本加了个功能,把每种水果从小到大的所有进货记录也拆出来,方便核对。用Python处理Excel这件事,入门真的不难,先照着上面的代码跑通一个小例子,然后试着改一改列名、换一换文件,你很快就会有自己的手感。