Python批量处理1000个Excel文件:从手动崩溃到10秒完成

📅 发布时间:2026/9/10 3:58:49
Python批量处理1000个Excel文件:从手动崩溃到10秒完成
一次处理1000个Excel文件这是很多做数据、做运营、做财务的朋友的真实噩梦。我曾经见过同事为了汇总几十个分公司的报表从早上九点复制粘贴到下午三点中间还因为漏了一行数据返工整个人接近崩溃。而我用Python脚本处理同样规模的文件包括读取、清洗、合并、输出全程跑了不到15秒。这不是什么高深的技术就是很基础的Python文件操作加openpyxl/pandas的组合应用。这篇内容就是要把这套方法彻底讲透。从为什么Excel会慢到环境怎么装到脚本每一行怎么理解再到真实跑批时容易踩的坑我都会按照实际工作里的顺序来写。适合谁看有Excel处理需求但没接触过Python的办公族刚学Python想找练手场景的初学者以及想优化现有数据处理流程的开发者都能在这篇里找到对应的内容。1. 先搞清楚Excel为什么会慢效率瓶颈的真实分布很多人一提到“Excel处理大量文件变慢”第一反应是电脑配置不行或者文件太大了。但根据我的实际经验问题往往不出在电脑上而是出在我们对Excel这个工具的使用方式上。1.1 手动操作是最大的时间黑洞我们来算一笔账。假设你手头有100个Excel文件每个文件里有一个sheetsheet叫“销售数据”你需要把这100个文件里某个特定列的数据全部汇总到一个总表里。手动操作的流程是打开文件、找到sheet、定位列、选中数据、复制、切到总表、粘贴、关闭文件。如果一切顺利整个流程需要30到60秒其中大部分时间花在文件打开、关闭和反复切换窗口上。100个文件就是将近两小时。如果中间还要做数据去重、格式调整、筛选条件变更时间翻倍很正常。这个过程的本质问题是Excel是一个给人“看”和“改”的工具不是一个给程序“批量驱动”的工具。每打开一次文件Excel都要启动完整的图形界面、加载所有样式、计算公式缓存、更新图表引用。哪怕你只是要读一个单元格的数据它也把整个文件的前世今生全部加载出来。这就是为什么明明数据量不大但处理100个文件就像蜗牛爬。1.2 Python快在哪里跳过图形界面直接读写文件结构Python处理Excel的方式和手动操作有本质区别。它不通过Excel软件本身去打开文件而是直接用代码去读取和写入Excel文件包内的XML结构数据。可以这么理解手动方式是你每次都要把整本书从头到尾翻一遍才能找到一句想抄的句子Python方式是你知道那句话在第几页第几行直接找到那一行抄下来就走。这样带来的速度提升是数量级的。同样读取100个Excel文件中的指定单元格Python可能只需要一两秒。如果文件结构规范、内容不大10秒处理1000个文件并不是夸张的说法而是真实可以达到的基准线。1.3 不同Excel文件格式的处理差异这里要提一个很关键的分水岭Excel文件是.xlsx还是.xls。.xlsx是Office 2007之后的标准格式底层是ZIP压缩包里面装着多个XML文件。Python的主流库openpyxl、pandasxlsxwriter等对它的读写支持都非常成熟速度快、功能全。.xls是老版本格式已经是非公开的二进制格式openpyxl不支持。处理.xls需要用xlrd读取和xlwt/xlutils写入或修改而且xlrd在2.0版本之后只支持.xls不再支持.xlsx。如果你手头还有大量.xls文件我强烈建议第一步先批量转成.xlsx后面的处理会省心非常多。转格式也用Python一行pandas就能完成后面我会给出代码。2. 环境准备阶段三个最容易劝退新人的坑写数据处理脚本代码本身其实只占一半工作量另一半在环境。我见过太多人在环境安装阶段就被劝退折腾一下午装不上库最后放弃。其实只要绕开下面几个坑十分钟就能搞定。2.1 Python安装版本的选择新手最常见的问题是装了一个Python 2的老版本教程或者在官网下载时不知道该选哪个文件。这里给出明确的建议去Python官网下载Python 3.10或更高版本的安装包Windows用户记得在安装第一步勾选“Add Python to PATH”。这个选项如果不勾后面在命令行里输入python会直接提示“python不是内部或外部命令”很多人在这里卡住。macOS用户如果系统自带Python 2千万不要直接用系统的建议通过Homebrew安装python3。Linux用户一般自带Python 3但不一定有pip需要用包管理器安装python3-pip。2.2 pip安装过程中的常见报错及处理在命令行输入pip install openpyxl后如果提示“pip不是内部或外部命令”说明Python安装时没加入PATH或者pip没有随Python一起安装。解决方法是在命令行输入python -m pip install openpyxl用模块方式调用pip。这个方法对Windows和Linux都适用。如果提示“Connection error”或者下载速度极慢是网络问题。可以用国内镜像源解决比如清华源pip install openpyxl -i https://pypi.tuna.tsinghua.edu.cn/simple如果提示权限错误在macOS或Linux上需要在命令前加sudo或者加--user参数。2.3 缺少好用的代码编辑器很多零基础用户问“Python代码在哪里写”最直接的答案是装一个Visual Studio Code然后安装Python扩展插件。也可以用PyCharm社区版对新手更友好。但我要提醒的是不要在学习脚本阶段沉迷于搞环境、换编辑器、配主题这些都是本末倒置。哪怕你暂时只用记事本写代码也完全能跑通这个批量处理脚本。等真正上手了再折腾工具。我个人的建议是环境只要满足“能运行、能看到报错信息、能编辑代码”三个条件就可以了不需要追求花哨。3. 从清理一个文件到清理一百个文件脚本能力的三级跳说实话“一次处理1000个文件”这个需求听起来吓人但写脚本的思路是一步步长出来的。你不该一上来就想着写一个大而全的脚本而是先处理一个文件再处理多个文件最后再加上异常处理。这种递进式写法既方便调试也能让你真正理解脚本的每一步在干什么。3.1 第一跳处理单个Excel文件的基础操作假设我们要做一件最常见的事读取一个目录下的某个Excel文件把其中指定sheet指定列里的数据读出来做简单的清洗再保存成新的文件。import openpyxl from openpyxl.utils import get_column_letter # 打开工作簿 workbook openpyxl.load_workbook(example.xlsx) # 选择工作表 sheet workbook[销售数据] # 读取B列所有内容假设第二行开始是数据 data [] for row in range(2, sheet.max_row 1): cell_value sheet.cell(rowrow, column2).value if cell_value is not None: data.append(cell_value) print(f共读取到 {len(data)} 条数据)这段代码用到的基础概念是load_workbook打开整个工作簿然后通过sheet名称选中指定工作表再用for循环遍历每一行的指定列。cell()函数里的row和column参数就是你要读的坐标非常直观。这里有个细节Excel的行列从1开始计数但Python的range函数在结束时是不包含的所以要用max_row1才能把最后一行包括进来。3.2 第二跳用os模块批量获取文件列表拿到单个文件的数据后接下来就是把“处理单个文件”的代码放进一个循环里。但首先你得让Python知道你文件夹里有哪些Excel文件。import os folder_path ./excel_files all_files os.listdir(folder_path) excel_files [f for f in all_files if f.endswith(.xlsx) and not f.startswith(~$)] print(excel_files)这里做了两个过滤条件一个是endswith确定后缀名另一个是排除以~$开头的文件。~$是Excel在打开文件时生成的临时锁文件如果你在脚本运行之前手动打开过某个Excel没关掉这个文件就会存在。不排除的话后面读取时会直接报错。3.3 第三跳循环处理所有文件并汇总输出把第一跳的代码套上第二跳的循环就是完整的批处理脚本了。下面是一个真实可运行的综合示例把多个Excel文件里的数据统一汇总到一个新文件里。import os import openpyxl folder_path ./excel_files output_file ./merged.xlsx # 新建汇总工作簿 result_wb openpyxl.Workbook() result_ws result_wb.active result_ws.title 汇总数据 # 写表头 result_ws.append([文件名, 商品名称, 销量]) # 遍历所有Excel文件 excel_files [f for f in os.listdir(folder_path) if f.endswith(.xlsx) and not f.startswith(~$)] for file_name in excel_files: file_path os.path.join(folder_path, file_name) try: wb openpyxl.load_workbook(file_path, data_onlyTrue) ws wb[销售数据] for row in range(2, ws.max_row 1): product_name ws.cell(rowrow, column1).value sales ws.cell(rowrow, column2).value if product_name is not None and sales is not None: result_ws.append([file_name, product_name, sales]) print(f处理完成: {file_name}) except Exception as e: print(f处理失败: {file_name}, 错误原因: {e}) result_wb.save(output_file) print(f合并完成共 {result_ws.max_row - 1} 条数据已保存到 {output_file})这段代码里的load_workbook(data_onlyTrue)挺关键。data_onlyTrue会读取公式计算后的缓存值而不是公式本身。如果Excel文件里的销量列是公式算出来的不加这个参数你读到的会是公式字符串而不是数字。但有个前提这个Excel文件必须被Excel软件打开并保存过一次缓存才会存在。如果文件是从某个系统导出后从没打开过公式缓存可能为空读出来会是None。这种情况后文会有专项讨论。4. 为什么是这个库而不是那个库openpyxl与pandas的选型逻辑写到这里一定会有人问“为什么不直接用pandas”这是个好问题。pandas确实是数据处理的重量级武器但并非所有场景都适合它。我用一张表格说明两个库在大批量Excel处理场景下的定位差异。对比维度openpyxlpandas读单个文件的性能中等适合中小文件较快底层有优化写复杂样式控制强可精细控制单元格格式弱写入格式能力有限数据清洗与统计能力弱需要自己写逻辑强groupby/merge等现成方法对大文件的处理耗内存容易卡顿相对更省内存但数据类型处理复杂学习门槛低适合零基础中高需要理解DataFrame概念我的选型经验是如果是简单的数据汇总、内容合并、格式调整openpyxl完全够用而且代码逻辑一眼就能看懂如果你要做的是数据分析型的任务比如按月统计销量、多表关联筛选、输出透视表那直接用pandas会更省力。pandas底层在读取Excel时需要额外安装openpyxl作为引擎两者其实不是对立关系而是互补关系。前面那个合并脚本用pandas写起来是这样的import pandas as pd import os folder_path ./excel_files all_data pd.DataFrame() for file_name in os.listdir(folder_path): if file_name.endswith(.xlsx) and not file_name.startswith(~$): df pd.read_excel(os.path.join(folder_path, file_name), sheet_name销售数据) df[文件名] file_name all_data pd.concat([all_data, df], ignore_indexTrue) all_data.to_excel(./merged_pandas.xlsx, indexFalse)代码量少了一半。pandas自动处理了表格结构、列对齐、空值等问题这对数据字段比较规整的情况非常省心。但如果是复杂的多级表头、合并单元格、特殊格式pandas读进来的DataFrame会丢失很多格式信息这时候反而要用openpyxl逐格处理。所以选型的关键在于你是在处理数据内容还是在处理表格样式。前者用pandas后者用openpyxl。另外提一个折中方案用pandas读取、清洗、汇总数据最后直接调用to_excel只做输出。如果还需要在输出的Excel里做格式美化可以把pandas的结果再交给openpyxl二次加工。在工作中的很多复杂需求都是这样组合完成的。5. 真实跑批后的复盘五个常见问题和对应排查链路脚本写好了不等于事情结束了。在实际批处理1000个文件的过程中大概率会碰到各种突发状况。下面这些问题是我自己在多个项目里真实遇到过的每一条都附带完整的排查思路而不是直接丢一句“加个try except就行”。5.1 读取到空数据问题可能出在公式缓存上表现是脚本跑完没报错合并结果里很多行是空的或者有些文件的某些列读出来是None。排查链路先用openpyxl单独打开一个出问题的文件打印单元格的值看看是None还是空字符串。如果是None但打开Excel时明明有数据检查文件是不是包含公式。在Excel里打开文件看看单元格是等于某个值还是以等号开头的公式。如果确认是公式把load_workbook的data_only参数设置为True再试。如果data_onlyTrue读出来仍然是None说明这个文件从生成后从未被Excel软件打开过公式没有计算过Excel文件内部不存在缓存值。对于第4种情况最直接的解决办法是在脚本里引入一个计算引擎强制计算公式。但这比较重我更推荐的做法是如果是自己控制的导出流程在导出前让程序用公式计算好最终值再写入如果是第三方系统导出的文件那就得在系统层面想办法或者考虑用LibreOffice命令行批量转换一次文件强制重新计算公式并保存。5.2 文件名或路径包含中文导致报错Windows系统下如果脚本用os.listdir读出来的文件名是正确的但openpyxl.load_workbook报“No such file or directory”大概率是路径拼接出问题。先检查是否用了os.path.join来组合完整路径不要手动用引号拼接路径字符串。如果路径本身包含中文Python 3在Windows上通常没问题但某些第三方库在特定编码环境下会坑你一次。排查方式是先打印完整路径确认路径字符串是否正确再用os.path.exists检查该路径是否存在。5.3 合并单元格数据只在左上角这是Excel处理里臭名昭著的问题。Excel的合并单元格数据只存在于合并区域的左上角单元格其余单元格值为None。如果你在遍历行时刚好拿到被合并的空白单元格数据就会漏。排查链路打开原始文件肉眼确认是否有合并单元格。读取sheet的merged_cells.ranges属性打印出所有合并区域。如果确定数据都在合并区域里有两种处理方式一种是把合并区域左上角的值填充到区域内所有单元格另一种是只读取合并区域左上角的坐标值。# 填充合并单元格的默认值 for merged_range in ws.merged_cells.ranges: top_left_value ws.cell(rowmerged_range.min_row, columnmerged_range.min_col).value ws.unmerge_cells(str(merged_range)) for row in range(merged_range.min_row, merged_range.max_row 1): for col in range(merged_range.min_col, merged_range.max_col 1): ws.cell(rowrow, columncol).value top_left_value这段代码先把合并区域的值记下来取消合并再把值填回原区域的所有单元格。注意这段代码会修改原始文件的数据结构如果只是读取汇总不建议修改原文件更安全的做法是在读取时自己做映射判断。5.4 读取大文件时内存飙升如果某些Excel文件本身有几万行甚至几十万行用openpyxl的load_workbook把所有数据一次性载入内存内存占用会非常大甚至把机器卡死。有一个针对读取的优化方案把load_workbook的read_only参数设置为True此时openpyxl会采用流式读取方式不加载整个文件到内存而是逐行读取。代价是某些功能不可用比如不能直接获取单元格样式。wb openpyxl.load_workbook(file_path, read_onlyTrue, data_onlyTrue) ws wb[销售数据] for row in ws.iter_rows(min_row2, values_onlyTrue): # row是一个元组按顺序对应每一列的值 if row[0] is not None and row[1] is not None: result_ws.append([file_name, row[0], row[1]]) wb.close()读取模式下处理完文件之后记得调用wb.close()释放资源。如果不关文件句柄会一直被占用在Windows下可能导致后面无法删除或覆盖同名文件甚至拖慢整个批处理的速度。5.5 文件格式伪装成xlsx但实际不是有时候你从某个系统导出的文件后缀是.xlsx但实际内容可能是CSV或者HTML表格。openpyxl去读取时会直接报错或者是乱码。排查方式很简单用文件资源管理器检查文件图标或者用Python读取文件头几个字节。ZIP压缩包的xlsx文件开头固定是PK这两个字符。在Python里可以这样判断with open(file_path, rb) as f: header f.read(2) if header ! bPK: print(f{file_path} 不是合法的xlsx文件)碰到这种情况需要对真实的文件格式做分流转处理比如后缀改成.csv后用csv模块读取或者用pandas.read_csv解析。6. 10秒处理1000个文件的参数解读为什么说这个速度是真实可信的标题里提到的“10秒搞定1000个文件”拿到实战里到底意味着什么这里需要给出一个精确的解释避免读者产生不切实际的预期。6.1 速度的构成拆解10秒处理1000个文件前提是每个文件很小比如只包含一列或几列数据而且我们只读取一个sheet、执行简单的提取逻辑。在这种情况下单个文件的处理时间大约在10毫秒级别。1000个文件总计10秒这样和实测数据是吻合的。但如果文件本身有几MB而且每个文件有多个sheet、大量公式、图表、条件格式处理时间会显著上升。2MB的常规销售报表openpyxl读取一个大约需要1到2秒1000个文件就是20到30分钟。这个差异来自I/O开销和XML解析复杂度不是代码写得不好是物理限制。6.2 时间花在了哪里影响批处理总耗时的关键因素按影响程度从高到低分别是文件的大小和复杂度、文件所在磁盘的读写速度机械硬盘远慢于固态硬盘、解析库的效率、脚本里的业务逻辑复杂度。针对常见情况做一个估算一个100KB左右的简单销售报表openpyxl读取加提取耗时约20到30毫秒如果文件经过压缩优化或者使用pandasopenpyxl引擎读取时间可能降到10毫秒左右。这样1000个文件的总耗时大约在10到30秒之间。6.3 如何真实测量耗时并定位瓶颈测量耗时最简单的办法是在脚本外部用命令行计时python batch_process.py在Windows的CMD或PowerShell里可以这样Measure-Command { python batch_process.py }在Linux或macOS里用time python batch_process.py输出结果里会显示总耗时。如果发现耗时远超预期有几个定位技巧在for循环里对每处理50个文件打印一次累计时间和当前文件编号用time模块手动记录关键步骤的耗时差值比如读取阶段花费多少、写入阶段花费多少。一般来说如果写入阶段占据了大头可以考虑用pandas先暂存数据最后统一写出减少openpyxl逐行append的I/O次数。import time start time.time() for i, file_name in enumerate(excel_files): # 处理逻辑 if (i 1) % 100 0: elapsed time.time() - start print(f已处理 {i 1} 个文件耗时 {elapsed:.2f} 秒)7. 日常工作中的延伸套路从批量合并到自动化流水线跑通了批量合并Excel的脚本之后其实你已经掌握了一个可以复用到很多场景的核心方法论。批量处理Excel远远不止“合并”这一种需求。7.1 批量格式转换与清洗最典型的是把所有.xls文件转成.xlsx。用pandas一行就能做到import pandas as pd for file_name in excel_files: if file_name.endswith(.xls) and not file_name.startswith(~$): df pd.read_excel(os.path.join(folder_path, file_name)) new_name file_name.replace(.xls, .xlsx) df.to_excel(os.path.join(folder_path, new_name), indexFalse)但要注意这种转换会丢失原有样式、宏、图表。如果这些内容必须保留只能用Excel软件本身的“另存为”功能手动处理或者用win32com调用Excel应用来做。win32com适合在Windows上做这种保真转换但它要求目标电脑必须安装Office软件。7.2 按条件拆分总表到多个文件批量合并的反向需求是把一个大表按某个字段拆分成多个文件。比如一个全国门店销售总表要按门店拆成每个门店一个文件。这个逻辑用pandas非常容易实现import pandas as pd df pd.read_excel(总表.xlsx) stores df[门店名称].unique() for store in stores: store_df df[df[门店名称] store] store_df.to_excel(f门店_{store}.xlsx, indexFalse)7.3 定时自动跑批把脚本接入系统计划任务如果这个批量处理需求是每天固定时间都要跑的比如每天早上从系统导出前一天的销售数据然后自动合并报表那可以把脚本挂到系统计划任务上。在Windows上使用“任务计划程序”在Linux/macOS上使用cron。Windows下用计划任务有个小坑计划任务的执行环境通常没有激活的Python相关环境变量所以程序里最好使用绝对路径并且在脚本开头用os.chdir切换到工作目录避免相对路径找不到文件。还有一点如果.py文件里用了print输出直接双击和计划任务运行时是看不到输出信息的。为了排查问题建议在脚本里把日志写到文本文件import sys log_file open(./batch_log.txt, a, encodingutf-8) sys.stdout log_file sys.stderr log_file print(批处理任务开始...)这样即使计划任务跑挂了你也有一条日志链路可以回溯。这个习惯很多专业开发者都有但很多初学者完全没有概念。7.4 与VBA的取舍边界在热搜词里我注意到大量用户在搜索“excel vba shape.method”和“excel插件”这说明很多人在尝试用VBA解决类似问题。VBA在处理复杂Excel内部操作、用户交互型工具上确实有天然优势因为它本身就嵌在Excel里。但VBA处理大量独立文件时有一个显著的劣势它必须通过Excel宿主逐个打开文件本质上还是在走图形界面的流程速度提升有限。另一个劣势是部署困难别人要用你的VBA工具需要让宏可用、信任中心设置、版本兼容这些对普通用户都是看不见的高墙。我的结论是如果你的一次性批处理是文件多、逻辑简单的场景Python是碾压级的选择如果是要做一个长期给团队使用的Excel内嵌工具VBA可以做好但前提是你要愿意处理一堆环境和安全信任问题。两者不是谁取代谁的关系关键是你所在的场景更依赖哪一端。8. 脚本安全与数据校验批处理最容易忽视的环节处理大量数据时最怕的不是脚本报错而是脚本“成功地”输出了一份错误的数据。报错了至少你知道有问题错误结果往往直接送进报表、进决策流程。所以批处理脚本一定要带数据校验环节。8.1 数量校验合并前后都要统计数量。合并前记录所有源文件的有效数据行数总和合并后核对结果文件的行数。两个数字不一致说明有数据在中间的清洗环节丢了或重复了。source_count 0 # 在处理循环里累加 source_count # 合并完成后 print(f源数据总量: {source_count}) print(f结果数据总量: {result_ws.max_row - 1})8.2 值域校验对关键数字列做一次快速的min/max检查在批处理脚本末尾用pandas读取结果文件打印数值列的描述性统计。如果某些列的合计、均值严重偏离预期就要回头检查是不是漏文件了或者多读了脏数据。import pandas as pd result_df pd.read_excel(output_file) print(result_df[销量].describe())8.3 抽样人工确认脚本自动处理完成后不要马上把结果发出去。至少在结果文件里随机抽2到3个文件对应的数据和源文件里的原始数据做一次人工对比。特别是前几次使用新脚本时这个环节不能省。等脚本跑过若干次确认逻辑稳定后才可以把抽查频率降下来。这些校验动作会花一点时间但比起报表发布后被人发现数据不对这点时间成本几乎可以忽略。回到开头那个场景手动粘贴三小时、漏数据、返工。用Python脚本处理同样的活打包成双击即用的批处理工具普通人也能做到。从openpyxl的逐单元格读取到pandas的快速清洗合并再到计划任务的定时触发每一步都不复杂真正复杂的只是迈出第一步时的选择。如果这篇文章帮你跳出了“只会用Excel、只能手动处理”的死循环那这十来分钟就没白花。