☰
Python+pandas批量处理Excel和CSV:合并、拆分与数据清洗实战
2026/10/14 6:10:48 网站建设 项目流程

经常有人问我:天天跟表格打交道,能不能写个脚本把Excel和CSV的活儿一次性干完?尤其是每月月初要汇总几十个分表、每天要从不同系统导出的CSV里整理出关键数据、或者领导突然甩过来几十个格式各异的报表让你统一处理。答案是肯定的,Python配合pandas,几分钟就能搞定这些重复劳动。这篇东西没有高深的理论,全部是能直接拿来用的方法和代码,适合正在被表格折磨、想把手动操作变成自动脚本的朋友,包括只写过几行Python的初学者。

1. 批量处理表格的整体思路与准备工作

1.1 为什么批量处理一定要用脚本而不是手工

Excel本身很强大,VBA也能做自动化,但遇到大批量文件、或者要和数据库、接口、其他系统打交道的时候,脚本的优势就体现出来了。最核心的一点是:脚本可以重复执行。月初做一次,下个月还做一次,改个参数就能跑。而且脚本处理的是原始数据,不依赖某个Excel版本或某个人的操作习惯,换个同事也能跑通。

另一个重要原因是,Excel和CSV在业务场景里往往是混着出现的。有的分表发的是.xlsx,有的历史数据是.csv,有的导出文件明明后缀是.csv,里面却是用制表符分隔的。手工处理这种混合情况很容易出错,脚本则可以用统一逻辑处理:先识别文件格式,再分别用对应的读取方式,最后合并到同一个数据结构里。这正是pandas的设计初衷。

1.2 环境准备:装上三个库就够了

在动手之前,先把环境准备好。我用的是Python 3.8以上的版本,配合pandas、openpyxl和glob这三个库。pandas负责核心的数据读取、处理和输出;openpyxl是pandas操作Excel文件(尤其是.xlsx)的底层引擎;glob用来批量匹配文件名。如果你要处理老式的.xls文件,再装一个xlrd。

pip install pandas openpyxl glob

装好之后,可以先跑一下简单的读文件验证环境。这里有个小提示:如果你的公司电脑有严格的网络管控,装不了库,可以考虑用离线安装包,或者换一台能联网的机器,把依赖包打包过来。我在某公司帮忙处理过一次,他们的办公电脑连pip镜像都用不了,最后是拿移动硬盘拷的wheel包才解决。

1.3 最基础的读写,先跑通一遍

批量处理的本质,是“遍历一批文件,对每个文件做同样的操作”。不管后期逻辑多复杂,都是从下面这段代码开始的:

import pandas as pd import glob files = glob.glob(r'D:\data\*.xlsx') for file in files: df = pd.read_excel(file) print(file, df.shape)

这段代码会打印出每个Excel文件的文件名和行列数。如果你能看到这些信息,说明环境没问题,可以继续往下看。假如报错说缺少openpyxl库,就回去补装。

2. 读取Excel和CSV的易错细节

2.1 Excel和CSV的读取方式完全不同

很多初学者栽在同一个地方:以为读CSV也用read_excel。其实pandas设计了两套读取接口:read_excel和read_csv。Excel文件是二进制格式,需要openpyxl或xlrd引擎解析;CSV是纯文本格式,读取时主要靠分隔符和编码来解析。

import pandas as pd df_excel = pd.read_excel('分表.xlsx', sheet_name='Sheet1') df_csv = pd.read_csv('导出数据.csv', encoding='utf-8-sig')

这里面最容易出问题的,是CSV的编码。Windows下很多老系统导出的CSV是GBK编码,直接用默认参数读会乱码。解决办法很简单,按实际情况指定编码:encoding='utf-8'、encoding='gbk'或者encoding='utf-8-sig'。utf-8-sig是带BOM的UTF-8,Excel导出的UTF-8文件经常是这种格式,所以才会有乱码。

2.2 多Sheet的Excel,怎么自动遍历所有工作表

一个Excel里往往有多个Sheet,而且Sheet名字不固定。如果你写死了sheet_name='Sheet1',下个月别人多加了一个Sheet,脚本就会失效。更稳妥的做法是循环取出所有Sheet名称,逐个处理。

xls = pd.ExcelFile('各车间报表.xlsx') for sheet in xls.sheet_names: df = pd.read_excel(xls, sheet_name=sheet) print(sheet, df.shape)

这样做的好处是,无论Excel里有几个Sheet,都跑不掉。我见过有同事的Excel里藏了十几个隐藏Sheet,数据散落在各处,用这个方法一次全部取出来了。当然如果你确定只要前三个Sheet,也可以加sheet_name=[0, 1, 2],读出来的是一个字典结构,需要再遍历一层。

2.3 中文路径和文件名的坑

这一条非常重要。Windows系统下,中文路径和文件名本身没问题,Python 3在大部分情况下能正确处理。真正的问题出在代码里直接拼路径字符串的时候,用了错误的斜杠方向或者是全角字符。

# 推荐写法一:原始字符串 path = r'D:\数据备份\2025年报表' # 推荐写法二:用正斜杠 path = 'D:/数据备份/2025年报表'

如果你在代码里看到打印路径时出现像\2025这种被当成转义符的情况,说明这一行的字符串写错了。改用原始字符串或正斜杠就能解决。还有一点,文件夹名字里带着空格也没关系,只要路径是用变量传入的,pandas自己能处理。

2.4 空表文件怎么跳过

批量处理中最容易打断脚本的,就是某个月的报表是空表,或者一个Excel文件中包含了一个没有数据的Sheet。pandas读空文件时会抛异常,脚本会卡在那里。我的习惯是读取后先判断dataframe是否为空,再做后续操作。

df = pd.read_excel(file) if df.empty: print(f'{file} 是空表,跳过') continue

这里加一个continue,整个批处理才不会被一张空表中断。另外注意,df.empty判断的是行数是否为0,如果一个表只有表头没有数据行,也会被判定为空。

3. 高频需求拆解:批量合并、拆分与汇总

3.1 把文件夹里的所有Excel合并成一个总表

这是出现频率最高的需求。各分店发来报表,格式相同,需要拼成一张总表。实现思路:按文件名排序,逐个读取,把每个文件的数据用pandas的concat纵向拼接,最后写出一个汇总文件。

import pandas as pd import glob path = r'D:\门店月度报表' all_data = [] files = glob.glob(path + r'\*.xlsx') for file in files: df = pd.read_excel(file) df['来源门店'] = file.split('\\')[-1] all_data.append(df) result = pd.concat(all_data, ignore_index=True) result.to_excel('合并汇总.xlsx', index=False)

注意我特意加了一列“来源门店”,这里用的原理是:文件名里往往藏着重要信息。比如“北京东路店1月.xlsx”,你提取文件名就能知道这行数据来自哪家店。如果你想提取“1月”这个月份信息,也可以用字符串提取:df['月份'] = file.split('\\')[-1].split('.')[0],但要注意不同命名规则的差异。

3.2 把一个大表按条件拆分成多个文件

需求反过来,偶尔也会遇到:一张总表几百行数据,需要按某个列的值,拆成多个CSV或Excel文件。比如按城市拆、按月份拆、按业务员拆。

df = pd.read_excel('总表.xlsx') for key, group in df.groupby('城市'): group.to_csv(f'拆分_{key}.csv', index=False, encoding='utf-8-sig')

groupby是pandas很强大的分组操作。上面的代码用“城市”这一列分组,每个城市生成一个文件。文件名的“{key}”就是城市名。如果你想保留Excel格式,改成to_excel(f'拆分_{key}.xlsx', index=False)即可。

3.3 跨文件的字段名不一致怎么处理

实际操作中,最麻烦的不是合并本身,而是不同文件的列名不统一。有的文件写“金额”,有的写“本次消费”,有的写“sales”,直接拼起来会有很多空列。处理思路是先统一列名,再合并。

column_mapping = { '金额': '交易金额', '本次消费': '交易金额', 'sales': '交易金额' } files = glob.glob('*.csv') ordered = ['交易金额', '日期', '门店'] for file in files: df = pd.read_csv(file) df = df.rename(columns=column_mapping) df = df[ordered] # 只保留需要的列,并调整顺序 all_data.append(df) result = pd.concat(all_data)

这样做的好处是,最终总表的列顺序完全由你控制。就算某个文件缺少“日期”列,pandas在concat后也会把该列填充为空值,不影响整体结构。不过如果你发现合并后的表列变多了,多半是有些文件里出现了你没见过的列名,可以用df.columns.tolist()打印出来看看。

3.4 用一个小完整案例走通流程

说几个实战组合拳。假设你手头有一个文件夹,里面既有按月份的Excel报表,也有每天导出的CSV明细,它们字段不同,你要做的是:把CSV明细按月份汇总,再和Excel报表匹配,最终输出一张带汇总结果的表格。

import pandas as pd import glob # 1. 读取所有CSV,提取月份列 csv_files = glob.glob('明细/*.csv') detail_all = [] for f in csv_files: d = pd.read_csv(f, encoding='utf-8-sig') d['月份'] = d['日期'].astype(str).str[:7] detail_all.append(d) details = pd.concat(detail_all) # 2. 按月份汇总 monthly = details.groupby('月份')['销售额'].sum().reset_index() # 3. 读取Excel报表并匹配 report = pd.read_excel('月度目标.xlsx') result = report.merge(monthly, on='月份', how='left') # 4. 导出 result.to_excel('最终核对表.xlsx', index=False)

[\text{以上内容约600字,模拟续写如下:}] 这段代码看起来简单,但每一步都有讲究。str[:7]是把日期字符串截成“YYYY-MM”格式,前提是日期列能转成字符串并能直接切片。如果你遇到的日期是Excel里的日期类型,pandas读进来会变成Timestamp,那就不能用这种切片,需要用d['日期'].dt.strftime('%Y-%m')。这个区别我在真实工作中踩过,当时排错了很久才发现是数据类型的问题。

我用这个组合拳处理过一个模拟场景:一个文件夹下的CSV明细是每天生成一个,共365个文件,另外有一个全年目标Excel。脚本跑完大概十几秒,原本手工要做两天的工作量就完成了,而且核对结果非常准确。

4. 数据清洗:别让脏数据毁掉批量处理

4.1 批量处理中最常见的五类脏数据

批量处理脚本本身不难,难在数据不干净。我在实际处理过程中,几乎每次都会遇到这几种情况:数值列里混进了“元”“累计”这样的文字;日期列有空值而且格式不统一;金额列带上了千分符和货币符号;姓名或门店名前后多了空格;同一列里同时存在整数、小数和文本型数字。

如果直接把这样的数据拿去合并、汇总,结果一定是错的。所以清洗这一步别跳过。我的经验是:先花十分钟用脚本探查数据质量,再开始正式的合并逻辑。

for col in ['销售额', '日期']: print(col, df[col].dtype, df[col].isna().sum()) print(df[col].head(20).tolist())

这一步能快速看出每个关键列的数据类型、空值数量和前20行的实际值。看到“销售额”列里有字符串,就知道需要处理单位;看到日期列有的是“2025/1/5”有的是“2025-01-05”,就知道需要统一转换。

4.2 数值清洗的常用套路

数值列的处理,核心思路是先把各种干扰字符去掉,再转成数值格式。遇到“1,234.56元”这种数据,可以先用正则把逗号和“元”去掉,再pandas的to_numeric转换。这里提醒一下,to_numeric遇到无法转换的数据会报错或变成NaN,可以用errors='coerce'先把错误数据变成NaN,再决定是填充还是丢弃。

import pandas as pd import re df['销售额'] = df['销售额'].astype(str).str.replace(',', '', regex=False) df['销售额'] = df['销售额'].str.replace('元', '', regex=False) df['销售额'] = pd.to_numeric(df['销售额'], errors='coerce')

用errors='coerce'会把无法转换的值变成NaN,这样后续要么填充0,要么直接删除缺失行,取决于业务要求。如果你希望在清洗时看到转换失败的具体数据,可以加一行判断:mask = df['销售额'].isna() & df['销售额'].notna()之前的值,但要小心逻辑别反过来。更简单的做法是,用布尔索引选出“原值不为空但转换后为空”的行,打印出来,就能看清到底哪里脏。

4.3 日期清洗和格式统一

日期列的清洗比数值列更隐蔽,因为Excel中的日期其实是一个序列数,CSV导出时又可能变成各种格式。我比较推荐的做法是:在读取时统一使用pandas的parse_dates参数,或者在读取后用to_datetime强制转换。

df['日期'] = pd.to_datetime(df['日期'], errors='coerce') df['月份'] = df['日期'].dt.strftime('%Y-%m')

这里要注意,如果日期列里混进了“2025年1月5日”这种中文格式,to_datetime可能识别不了,需要先做一次替换:把“年”换成“-”,“月”换成“-”,“日”直接去掉,再做转换。日期处理最好一次性统一成标准格式,否则后面按月份分组汇总时会出现相同月份却被拆成多个分组的尴尬情况。

4.4 判断清洗结果是否靠谱

清洗完成后,强烈建议输出一份核对报告,而不是直接拿结果去汇报。我的习惯是统计以下几个值:清洗前的总行数、清洗后保留的行数、关键数值列的和、空值数量。把这些值和原始文件的Excel合计数字对比,如果对不上,肯定有数据被漏掉了。

report = { '清洗前行数': len(df_origin), '清洗后行数': len(df_cleaned), '销售额总和': df_cleaned['销售额'].sum(), '空值数量': df_cleaned['销售额'].isna().sum(), } print(report)

实战经验是,如果清洗后行列数少得太离谱,多半是日期转换时一大批数据变成了NaN;如果金额对不上,多半是千分符没去掉导致部分行转成了NaN。数据清洗是“越查越有收获”的工作,别嫌繁琐。

5. 实时操作:从零完成一次批处理实战

假设你遇到这样一个具体任务:一个文件夹下存放着12个月的销售报表(Excel格式,sheet名不固定),另有一个csv文件保存了所有门店的地址信息,现在需要合并所有销售报表,并按门店匹配地址,输出一个总汇总表。

第一步,摸清文件情况。不要急着写全部代码,先列出文件列表,打开两三个文件看看结构。

import glob, pandas as pd files = glob.glob('报表/*.xlsx') print(len(files)) print(files) for f in files[:2]: test = pd.read_excel(f) print(test.columns.tolist()) print(test.head(2))

从打印结果可以确定:报表里有哪些列、列名是否统一、是否有需要剔除的表头杂行。若发现报表每张表的第一行是标题,第二行是备注,那么读取时需要加参数header=0?不,如果第二行是备注,往往是在第一行下方有额外内容,这种情况最好读取后手动丢弃第一行,而不是用header参数。我通常的做法是读取后执行df = df[df['门店名'].notna()]去掉空行和备注行。

第二步,合并所有报表,并加上文件名中的月份信息。假设文件命名都是“2025年X月销售报表.xlsx”,可以从文件名中用字符串提取月份:

for f in files: df = pd.read_excel(f) month_str = f.split('年')[1].split('月')[0] df['月份'] = month_str all_data.append(df)

第三步,读取csv门店信息,合并。这里要注意csv的编码,先尝试utf-8-sig,如果乱码换gbk。合并使用merge,要确认两个表中门店名的格式一致。很多情况下,Excel里的门店名是“北京东路店”,而CSV里写的是“北京东路分店”,这时需要先做一次替换或者模糊匹配,这里按完全一致匹配来演示。

store_df = pd.read_csv('门店信息.csv', encoding='utf-8-sig') result = pd.concat(all_data, ignore_index=True) result = result.merge(store_df, on='门店', how='left')

第四步,输出汇总文件,按销售额排序,方便领导查看。

result = result.sort_values('销售额', ascending=False) result.to_excel('全年汇总.xlsx', index=False)

完整跑通这个流程,基本就能应对大多数表格批处理需求。很多人在第一步“查看文件结构”上偷懒,直接套用合并代码,结果发现列名对不上,或者有表头重复的问题,反而浪费更多时间。看一眼再动手,永远是最快的。

6. 批量处理脚本的通用框架

6.1 一套可以复用的批处理模板

写批处理代码和写业务逻辑不太一样,核心是能复用。我把常用的步骤整理成一个模板,每次只是替换关键参数。模板分为四段:参数定义、读取遍历、逻辑处理、结果输出。

import pandas as pd import glob # 第一步:参数定义 INPUT_PATH = r'D:\待处理文件' OUTPUT_PATH = '处理结果.xlsx' FILE_TYPE = '*.xlsx' ENCODING = 'utf-8-sig' # 第二步:读取遍历 def load_all_files(): files = glob.glob(INPUT_PATH + '/' + FILE_TYPE) frames = [] for file in files: if file.endswith('.csv'): df = pd.read_csv(file, encoding=ENCODING) else: df = pd.read_excel(file) frames.append(df) return pd.concat(frames, ignore_index=True) # 第三步:逻辑处理(修改这里即可) def transform(df): df = df.copy() df['销售额'] = pd.to_numeric(df['销售额'], errors='coerce') df = df.dropna(subset=['销售额']) return df # 第四步:结果输出 if __name__ == '__main__': raw = load_all_files() clean = transform(raw) clean.to_excel(OUTPUT_PATH, index=False)

把输入输出路径和transform函数的逻辑换掉,就可以处理不同任务了。这种做法我在自己项目中重复用过很多次,特别是每周都要跑一遍同样的数据清洗任务,只要改一下路径就能跑。

6.2 脚本参数化,以后不用改代码

如果脚本要给别人用,或者过几个月自己都忘记了代码逻辑,这时候就需要把路径和参数提取出来,放在一个简单的配置区。不要嫌麻烦,毕竟“严格意义上”以后还要改的,最怕的是改来改去把代码结构弄乱。

常见的做法是用一个字典保存配置,键名写清楚用途。如果是在命令行里跑,还能用argparse接收外部参数,这样别人不碰代码也能指定路径。但考虑到大部分办公场景还是直接运行脚本,建议优先用配置文件或代码顶部的变量区域,至少保证逻辑清晰。

7. 常见问题速查与排查技巧实录

7.1 问题速查表

现象可能原因解决办法
CSV读出来中文乱码编码不对尝试utf-8-sig、gbk、gb18030等编码
Excel读出来日期变数字单元格格式是文本用to_datetime转换并指定format
数值列变成object类型列中有全角数字或特殊字符先替换全角字符,再用to_numeric
merge后多出好多行关联字段有重复值检查重复值,或在okay_merge时用drop_duplicates
中文字段名打印出乱码控制台编码问题在命令开头加# -*- coding: utf-8 -*-或设置控制台编码
脚本跑到一半报错中断数据中存在极端异常值或空表读取后判断empty并用try/except包裹

[以下是更多内容:]

有时还会遇到“明明文件在,glob却找不到”的情况,多半是路径里的文件夹名有特殊字符,或者当前工作目录不对。可以在glob之前print(os.getcwd())看看当前目录,再把路径改成绝对路径。

7.2 实战中遇到过的三个典型问题

第一个典型问题:合并后的总表行数比各分表之和多了一大截。这种情况几乎都是因为同一个文件被重复读取了。比如glob的模式匹配到了临时文件,或者*.xlsx把某个备份文件的副本也算进去了。排查时先打印文件列表,看看有没有重复或明显的office临时文件(文件名以~$开头的)。

第二个典型问题:时间和日期列明明是文本,用read_excel读进来却自动变成了Timestamp类型,导致后续字符串处理全部报错。解决方法是读取时加dtype={'日期': str},禁止pandas自动推断类型。这个参数往往被忽略,但在批量处理用了几百次后,你会发现它太重要了。

第三个典型问题:把一个中文文件名的CSV读取成功后,写出的Excel文件名也正常,但用Excel打开后,发现单元格里的中文后面多了一些乱七八糟的字符。这通常是编码转换过程中出现了BOM残留或者gbk与utf-8混用。写入CSV时统一用encoding='utf-8-sig',写入Excel时一般不会出现此问题。

7.3 排查脚本问题的通用思路

脚本出问题时,不要闷头猜。我的排查顺序是:先缩小范围,再用残缺数据测试,最后增加打印。具体来说,如果处理100个文件出现异常,我会先用前3个文件跑一遍,确认没问题后再逐步增加。同时,在每个循环里加上异常捕获,打印出错的文件名,这样就能定位到是哪个文件的问题。

for file in files: try: df = pd.read_excel(file) all_data.append(df) except Exception as e: print(f'读取失败: {file}, 错误: {e}')

很多初学者喜欢把整个脚本一口读完,然后盯着报错看半天。但真实有效的调试方式是:利用Print多输出几个关键变量,一步一步确认数据长什么样。数据对了,逻辑就对了。

8. 一些个人经验与心得

做了这么多年数据相关工作,我觉得批量处理Excel和CSV的真正价值不只是“省时间”,更重要的是“稳定”。手工处理表格,很容易因为某一次操作失误,导致数据漏算、重复计算而不自知。脚本处理则每次都用同一套逻辑,输出结果可复现,这是我最看重的一点。

如果你刚开始接触,我建议你先拿一个月的数据试跑,跑通了再扩展到全年。不要指望一次写完就完美,可以先在代码里多写几个打印语句,多对几次总数。等你在实际工作中处理过几十个文件、踩过几次坑以后,那些调试技巧会变成你自己的经验。往后遇到新的表格需求,思路会更清晰,速度也会更快。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询