尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Python读取10万行Excel耗时对比:openpyxl、pandas等库路径选型指南

Python读取10万行Excel耗时对比:openpyxl、pandas等库路径选型指南 你是不是也遇到过这种场景业务方发来一个 10 万行的 Excel你习惯性打开 Python敲下import openpyxl然后脚本跑了十几秒还没结束内存倒是涨得让人心慌。很多人第一反应是“文件太大”但实际上 10 万行对 Excel 来说远没到上限真正的瓶颈往往不是你选错了功能而是选错了读取路径。这篇文章就围绕一个非常具体的基准问题展开测试 Python 中常见库读取 10 万行 Excel 的耗时差异。我会用同一份样本文件、同一组硬件条件、同一个计时口径把 openpyxl、pandas、xlrd、pyxlsb 这些常见库的读取流程拆开对比并且把代码脚本完整给你方便你在自己电脑上重新跑一遍。先说核心判断读取 10 万行 Excel没有“哪个库绝对最快”只有“哪条路径最适合你当前的业务”。如果你只需要把单元格值拿去做统计pandas 配合合适引擎通常更顺如果你要处理的是带格式、带公式的复杂报表openpyxl 的只读模式能帮你节省大量时间和内存如果文件本身是 .xls 或 .xlsbxlrd 和 pyxlsb 又是另一种选择。真正要理解的是每个库在读取文件时做了什么为什么不同路径会产生数量级的耗时差距。1. 为什么这个测试值得关注很多 Python 开发者选 Excel 库时只看“能不能读”很少看“读起来代价多大”。真正到了生产环境代价会以两种形式出现一种是耗时卡在接口调用上一种是内存8GB 的笔记本被一个 60MB 的 xlsx 文件直接撑满。Excel 的 xlsx 格式从底层设计上就不是为“大数据量程序化读取”准备的高效格式。它本质是一个 ZIP 压缩包里面装着多个 XML 文件行列数据、字符串表、样式、公式都散落其中。程序要读一个 10 万行文件需要经历磁盘读取、ZIP 解压、XML 解析、对象模型构建这些阶段。不同库在这些阶段的重叠和取舍各不相同因此性能表现差异极大。10 万行也不是小数目。业务上很常见从 OA 导出的明细表、电商后台的订单流水、数据仓库临时下发的业务清单基本都会卡在这个量级。如果脚本写得不够好10 万行可能就从“几秒能完成”变成“几分钟还在转”整个数据任务的体验直接被拖垮。这个测试对你来说至少有三个层面的价值如果你还没开始写代码可以避免“一上来就 openpyxl 默认模式读到底”的低效选择。如果你已经在用某个库可以通过同口径测试判断当前方案的优化空间。如果你想在团队内定一个读取规范这份基准脚本能成为可复现的参考依据。2. 先弄清几个 Excel 库的底层差异拿 Python 读 Excel很多人分不清一堆库各自是什么角色甚至以为xlrd、xlwt、openpyxl是新旧版本的关系。实际上它们面对的文件格式和运行方式完全不同。先看 xlsx 本身也就是 Excel 2007 之后默认的 .xlsx 文件。它内部是一个 ZIP 包而 openpyxl 是一个纯 Python 实现的解析库会主动把 ZIP 中的 XML 内容读出来并按 Excel 的对象模型重新组织。pandas 并不是一个直接解析 Excel 的库它内部需要调用引擎比较常见的是openpyxl或xlrd也就是说你用 pandas 读 xlsx 时底层有一层工作仍然是 openpyxl 做的。再看 xls这是 Excel 97-2003 的旧格式本身是一个二进制 BIFF 结构。老版本的 xlrd 既能读 xls 也能读 xlsx但从 xlrd 2.0 开始官方只保留 xls 支持不再处理 xlsx。这是一个非常容易踩的坑很多人从旧教程里复制代码安装最新版 xlrd然后读 xlsx 文件得到一个XLRDError其实不是代码问题是库不再支持了。还有 xlsb 格式它把数据以二进制方式存储解析速度通常比 XML 文本快。pyxlsb 只支持 xlsb不支持常见的 xlsx。所以最稳妥的理解方式是库 / 工具可读格式典型角色主要特点openpyxlxlsx / xlsm读、写 xlsx纯 Python 解析默认构建单元格对象支持修改样式和公式pandas openpyxlxlsx / xlsm数据分析入口底层走 openpyxl读取后转换 DataFrame代码简洁xlrdxls读取旧版 xls2.0 后不支持 xlsx是历史包袱较重的选择pyxlsbxlsb读取二进制格式针对 xlsb 专用速度在特定场景下有优势xlsxwriter仅写入 xlsx生成样本文件本测试用它构造 10 万行文件避免写入环节拖累整体时间xlwings依赖本机 Office桥接 Excel 应用适合自动化已有 Excel不适合无界面服务端做大规模数据解析从整体设计上一次标准读取通常包含几个开销非常大的阶段读取 ZIP 并解压 XML。解析sheet1.xml中的行列单元格。读取sharedStrings.xml并做共享字符串去重。将文本数据转换为 Python 对象或 pandas 的二维结构。网上流传的“谁比谁快”结论很多时候没说明文件格式和读取模式这会造成误导。比如 openpyxl 普通模式加载一个文件是直接在工作簿层面创建大量单元格对象而 openpyxl 的只读模式也就是read_onlyTrue会避开不必要的对象构建按行流式读取文件。同样是 openpyxl两种模式在 10 万行级别上的耗时会明显不同。所以做性能对比前先锁定文件格式和读取模式否则结论没有意义。3. 测试环境与前置准备本次测试目标是横向比较而不是生产环境调优因此更关注“同一个环境下的相对差距”。我建议你用 Python 3.10 及以上版本系统不限Windows、Linux、macOS 皆可。为了保证结果可复现先把依赖固定安装pip install pandas openpyxl xlsxwriter # 如果需要测试 xls、xlsb、calamine 引擎再补装 pip install xlrd pyxlsb python-calamine关于python-calamine需要提醒一句它是新版 pandas 中calamine引擎的底层依赖不是所有 pandas 版本都能直接使用安装后还要确认当前 pandas 版本支持enginecalamine。如果你的 pandas 版本比较旧可以去掉这个引擎选项不必因此影响测试主流程。测试设计遵循下面几个原则第一样本文件统一。用同一个生成脚本生成一份固定大小的 xlsx 文件避免磁盘碎片、字符串长短差异、单元格样式数量不同造成干扰。第二多次运行取中位数。读取文件会受操作系统磁盘缓存影响。第一次运行可能是冷读后面几次可能命中缓存。建议每个测试脚本至少顺序执行 5 次去掉最大值和最小值取中间值或中位数比较。第三区分 CPU 开销和内存开销。耗时只反映了时间成本。如果某个库读取很快却把内存吃到 2GB在多任务场景中未必是更好的选择。因此测试同时记录耗时和内存峰值。第四不要用time.time()测极短任务。在 Python 3.8 以上版本建议使用time.perf_counter()它能提供更高精度的单调时钟不容易受系统时间调整的影响。4. 生成一份标准的 10 万行测试样本如果手动在 Excel 里拖拽填充 10 万行既不现实也容易混入额外样式。更可靠的方式是用 xlsxwriter 生成一个只有纯数据的 xlsx 文件。xlsxwriter 的优势是写 xlsx 效率较高不会像 openpyxl 普通模式那样在写文件阶段就消耗大量时间。如果样本本身的生成就要半分钟后面的读取基准会受到不必要的影响。下面是完整的样本生成脚本# 文件路径generate_test_xlsx.py import xlsxwriter FILE_NAME mock_100k.xlsx ROW_COUNT 100_000 COL_HEADERS [订单号, 客户名称, 订单金额, 下单日期, 是否发货] workbook xlsxwriter.Workbook(FILE_NAME) worksheet workbook.add_worksheet(订单) for col, header in enumerate(COL_HEADERS): worksheet.write(0, col, header) for row in range(1, ROW_COUNT 1): order_id fORD{row:08d} customer f客户{(row % 5000) 1} amount round((row % 100000) / 3, 2) date_text 2025-06-18 shipped 是 if row % 2 0 else 否 worksheet.write(row, 0, order_id) worksheet.write(row, 1, customer) worksheet.write(row, 2, amount) worksheet.write(row, 3, date_text) worksheet.write(row, 4, shipped) workbook.close() print(f{FILE_NAME} 生成完成共 {ROW_COUNT} 行数据)运行方式python generate_test_xlsx.py运行后工作目录下会出现一个mock_100k.xlsx文件。这份文件不包含公式、不包含特殊样式、不含合并单元格是一份相对“干净”的数据文件。这里有一个细节值得注意真实业务 Excel 往往带表头样式、冻结窗格、批注、数据验证等额外元素。只要这些元素存在解析库构建对象模型时就要多消耗资源。如果生产环境里的文件特别复杂建议单独用真实文件的脱敏副本再测一轮。干净样本只能说明库本身的基础性能不能代表“任何 10 万行 Excel 都这么快”。XLSX 格式的单表行数上限是 1,048,576 行10 万行距离上限还有不少空间。如果文件真的逼近上限普通 Excel 文件本身也会因为公式重算、自动筛选、条件格式等问题变得极其卡顿这种场景已经不适合继续用 Excel 作为数据交换格式了转向 CSV、Parquet、数据库会更合理。5. 完整基准脚本把常见读取路径放在同一口径下对比下面这个脚本是整篇文章的核心。它分别测试openpyxl 默认加载模式。openpyxl 只读模式read_only。pandas 调用 openpyxl 引擎的读取模式。我不建议在这个阶段直接引入 xlsxwriter 参与测试因为 xlsxwriter 只负责写入。我们也暂时不引入 xlrd 和 pyxlsb因为生成的样本是 xlsxxlrd 新版不支持pyxlsb 又只支持 xlsb缺少同格式可比性。后面我会单独补充这两个库的测试思路。# 文件路径bench_read_libs.py import time import resource import openpyxl import pandas as pd FILE_NAME mock_100k.xlsx def get_max_rss_mb(): 获取当前进程的峰值内存单位转换为 MB。Linux/macOS 下可用。 rss_bytes resource.getrusage(resource.RUSAGE_SELF).ru_maxrss # macOS 单位为 bytesLinux 单位为 KB if rss_bytes 2**40: return rss_bytes / 1024 / 1024 return rss_bytes / 1024 def bench_openpyxl_normal(): start time.perf_counter() wb openpyxl.load_workbook(FILE_NAME) ws wb.active row_count 0 for row in ws.iter_rows(values_onlyTrue): row_count 1 elapsed time.perf_counter() - start wb.close() return elapsed, row_count def bench_openpyxl_readonly(): start time.perf_counter() wb openpyxl.load_workbook(FILE_NAME, read_onlyTrue) ws wb.active row_count 0 for row in ws.iter_rows(values_onlyTrue): row_count 1 elapsed time.perf_counter() - start wb.close() return elapsed, row_count def bench_pandas_openpyxl(): start time.perf_counter() df pd.read_excel(FILE_NAME, engineopenpyxl) elapsed time.perf_counter() - start return elapsed, len(df) if __name__ __main__: # 第一次运行前先打印基线内存 print(f初始峰值内存: {get_max_rss_mb():.2f} MB) for func in [bench_openpyxl_normal, bench_openpyxl_readonly, bench_pandas_openpyxl]: # 每个函数先执行一次作为预热避免第一轮加载库的时间干扰结果 func() run_times {func.__name__: [] for func in [bench_openpyxl_normal, bench_openpyxl_readonly, bench_pandas_openpyxl]} for func in run_times: print(f开始测试: {func}) for _ in range(5): elapsed, rows globals()[func]() run_times[func].append(elapsed) print(f {elapsed:.4f} 秒, 行数: {rows}) run_times[func].sort() median_time run_times[func][2] print(f {func} 中位数耗时: {median_time:.4f} 秒) print(f 当前峰值内存: {get_max_rss_mb():.2f} MB) print()这段脚本有几个关键点需要说明。为什么要设计“预热”环节Python 进程第一次导入 pandas 或 openpyxl 时模块加载本身就要消耗不少时间。如果直接第一轮计时read_excel 的耗时里会混入“import pandas import openpyxl”的启动成本这显然不是我们想要的业务读取耗时。为什么每轮测 5 次取中位数磁盘缓存会让连续读取文件的速度越来越快第一轮冷读取可能比后续循环慢很多。使用中位数可以把偶然的冷启动波动剔除掉。进程中get_max_rss_mb()统计的是“截止到当前为止进程出现过的最大内存峰值”而不是每个函数的实时内存。如果想要精细到每个函数执行期间的内存变化更专业的方式是用 psutil 轮询采集但对于一次基础对比观察整体峰值已经足够发现哪个库最“吃内存”。如果运行环境是 Linux还可以用系统级命令对比内存结果/usr/bin/time -v python bench_read_libs.py命令输出中的Maximum resident set size能反映进程整体峰值内存。Windows 下没有这个命令用任务管理器观察 Python 进程的内存变化即可。6. 结果怎么看耗时差异背后暴露的问题运行完脚本后你大概率会看到 openpyxl 默认模式明显比只读模式耗时更多pandas 配合 openpyxl 引擎的耗时通常与 openpyxl 默认模式接近或略低。不同机器结果有差异很正常的但相对顺序能反映出各库处理机制上的区别。耗时最高的通常是默认加载模式。原因是 openpyxl 在load_workbook()这一步就会把工作簿里的单元格对象构建起来。10 万行如果再乘上 5 列就是 50 万个单元格。Python 为每个单元格创建独立对象无论是时间还是内存都是很大的开销。更麻烦的是如果你访问单元格的坐标字符串比如ws[A1]还要经历字母坐标到行列索引的转换虽然现代版本已经尽量优化但这种模式本质上就不是为大数据量遍历设计的。只读模式则不同。read_onlyTrue时openpyxl 采用流式方式逐行读取 XML不提前构建无关对象也不维护完整的单元格对象集合。它的代价是你不能自由随机访问某个单元格只能一行一行往下读。对于“只需要把所有行取出来做处理”的批量任务这个限制可以接受换来的是性能和内存的大幅改善。pandas 的情况比较复杂。直接用pd.read_excel()最符合数据分析习惯代码短、返回的 DataFrame 可以直接做分组聚合、透视表、可视化但它的底层仍然要依赖 openpyxl 解析 xlsx。如果你只做数据读取不做后续处理pandas 相对 openpyxl 没有绝对优势。它真正的优势是读取后那一步pandas 可以继续使用向量化操作做统计而 openpyxl 读出的一堆元组还需要自己用 Python 循环去汇总。更合理的判断是如果目的只是提取 Excel 内容并转存到数据库用 openpyxl 只读模式更好。如果目的是做业务分析、数据报表pandas 更合适因为省去了“读完再转 DataFrame”的二次成本。如果文件里还有多级表头、复杂公式或需要保留格式思路要重新回到 openpyxl 的普通模式。这里需要正视一个可能让人意外的结论很多教程喜欢说“数据量大用 pandas”但 pandas 并不是一个比 openpyxl 更快的 Excel 解析器它只是把解析后的数据封装得更方便。当它的底层引擎是 openpyxl 时解析 xlsx 的这层瓶颈是绕不开的。如果追求极致性能可以尝试新版 pandas 的calamine引擎它在部分场景下比 openpyxl 引擎快不少但由于版本差异较大我建议把它作为“进阶优化点”而不是默认方案。另外你可能还需要注意耗时单位是否包含文件读取缓存的影响。第一次读到的是磁盘上的真实文件后续读取时操作系统可能把文件内容放进了内存缓存程序拿到的数据其实来自内存。这也是为什么我会建议读者连续运行多次并取中位数的原因。7. 用 xls、xlsb 文件做扩展对比刚才的基准只覆盖了 xlsx 文件。如果你的工作场景里还有 xls 或 xlsb 格式可以按下面的思路做扩展。注意这几种格式不能直接放在同一个目录里用同一份代码测试因为对应库的读取接口完全不同。测试 xls 格式时步骤如下import time import xlrd FILE_NAME_XLS mock_100k.xls start time.perf_counter() book xlrd.open_workbook(FILE_NAME_XLS) sheet book.sheet_by_index(0) row_count sheet.nrows elapsed time.perf_counter() - start print(fxlrd 读取 xls 耗时: {elapsed:.4f} 秒, 行数: {row_count})需要注意的是xlrd 2.0 及以上版本只支持 xls不再支持 xlsx。拿到一份 xlsx 文件却发现用 xlrd 打开报错不需要怀疑代码逻辑先检查安装的版本pip show xlrd测试 xlsb 格式时pyxlsb 的接口与 xlsx 解析库完全不同import time from pyxlsb import open_workbook FILE_NAME_XLSB mock_100k.xlsb start time.perf_counter() row_count 0 with open_workbook(FILE_NAME_XLSB) as wb: with wb.get_sheet(1) as sheet: for row in sheet.rows(): row_count 1 elapsed time.perf_counter() - start print(fpyxlsb 读取 xlsb 耗时: {elapsed:.4f} 秒, 行数: {row_count})这里先解释一下pyxlsb 返回的每一行是单元格对象列表如果要获取值需要访问item.v属性否则直接拿到的是包装对象。xlsb 文件的读取通常比同等内容的 XML 格式更快这也是为什么一些报表导出台会专门生成 xlsb 给下游使用。在做这个扩展测试前你需要提前把样本文件另存为对应格式。比如在 Excel 中打开mock_100k.xlsx再“另存为”选择.xls或.xlsb。注意另存为 xls 格式时如果文件里存在不兼容的单元格格式或数据类型Excel 会弹窗提示并尝试转换这本身也可能改变文件内容。不要直接拿 xlsx 文件改后缀名。比如把.xlsx改成.xlsb文件内容仍是 XML 包pyxlsb 打开时依然会报格式错误。文件格式由文件内部结构和魔数决定不是由后缀决定。8. 常见问题与排查方法在跑基准或实际读文件时你会碰到各种报错和离奇现象。下面是我认为出现频率较高的几个问题问题现象可能原因排查方式解决方案启动失败提示缺少 xlrd 或 openpyxl新环境未安装对应依赖执行pip list查看包列表按需执行pip install安装xlrd 读取 xlsx 时报 XLRDErrorxlrd 2.0 不再支持 xlsxpip show xlrd查看版本xlsx 改用 openpyxl/pandas或保留 xls 格式给 xlrd 读pandas 读取时内存占用过高read_excel 默认读取全部列并进行类型推断用任务管理器观察 Python 进程 Rss用usecols指定列、用dtype指定类型减少类型推断压力openpyxl 普通模式读取太慢默认加载构建大量 Cell 对象观察耗时和内存同时升高使用read_onlyTrue流式遍历连续测试结果波动明显磁盘缓存或后台任务干扰分别做冷启动测试和热启动测试多次运行取中位数关闭无关程序读取出的日期变成数字或时间戳Excel 日期序列值被解析为数字检查数据类型确认是否设置data_only或指定 dtype在业务侧统一转换或用 pandas 解析时间列打开文件时提示 zipfile.BadZipFile文件不是真正的 xlsx或文件已损坏用解压工具尝试打开文件从源头重新导出不要手工改后缀只读模式读公式单元格为空read_only 模式下 openpyxl 不计算公式确认文件是否包含公式而非静态值在 Excel 中重算并另存或使用 data_only 读取缓存结果其中有两个坑值得展开讲。第一openpyxl 的data_only和只读模式经常被混在一起。load_workbook(..., data_onlyTrue)的作用是读取公式单元格的缓存结果而不是公式本身。如果文件是用 xlsxwriter 直接写入的公式没有经过 Excel 打开并计算可能不存在缓存值此时读取到的内容是None。处理业务文件时先判断文件里是公式还是有值才不会在数据对接阶段出现大面积空值。第二pandas 的read_excel没有像read_csv那样的chunksize参数。很多人以为可以像读 CSV 一样分块读取 Excel实际上并不是这样。Excel 的 xlsx 解析逻辑决定了一次性把某个 Sheet 的数据读入并转换。如果文件已经大到内存吃紧建议先转换成 CSV 或 Parquet 再做流式读取不要指望用“分块读 Excel”来绕过内存瓶颈。如果确实需要分块可以自己用 openpyxl 只读模式读一定行数后交给 pandas 处理但这种方案要自己维护行号边界。9. 读取 10 万行 Excel 的最佳实践建议结合前面的对比测试可以整理出一套比较实用的选型和工作流建议。第一先区分“要不要保留格式”。很多需求文档会说“用 Python 读 Excel”但真实场景差异很大。如果只是从 Excel 里抽数openpyxl 只读模式是性价比很高的选择如果还要把处理后的数据写回一个新的 xlsx 并保留原文件的样式或公式默认加载模式基本无法避免你需要接受它在性能上的付出或改用模板复制等方式减小代价。不要妄想一边不创建单元格对象一边精确修改样式读取优化和随机写回在 openpyxl 设计上是冲突的。第二如果业务上允许优先考虑 CSV 而不是 Excel。CSV 本质是纯文本读取时不需要做 ZIP 解压和 XML 解析pandas 的read_csv对同样数据量的读取性能会明显优于read_excel。很多团队从 Excel 转向 CSV 后数据任务整体耗时能改善不少。代价是 CSV 不保留格式和公式所以它更适合“数据交换和计算”不适合“报表文件制作”。第三用usecols和dtype控制 pandas 的读取成本。pandas 在read_excel后要推断每一列的数据类型列越多、类型越杂开销越大。如果只需要其中三列尽量不把所有列读进内存import pandas as pd df pd.read_excel( mock_100k.xlsx, engineopenpyxl, usecols[订单号, 客户名称, 订单金额], dtype{订单号: string, 客户名称: string, 订单金额: float64}, )此时 pandas 不会去推断每一列的类型内存和执行时间都会更可控。这个写法对大数据文件的性能改善非常直接。第四留意 Excel 工作表的“空行尾部”和“整列格式残留”。有些业务表只是从某系统导出外表看起来 10 万行实际因为重复删除数据、设置过条件格式Excel 的usedrange可能异常扩大。openpyxl 的ws.max_row看到的行数可能远超实际数据行。遇到这种文件最好的办法是在 Excel 中清除格式或重新整理数据源而不是让程序去承受这些无效解析。第五凡是能一次遍历完成的处理不要在循环里反复访问ws.cell(row, col)或ws[A1]。尤其是默认模式每次坐标访问都有字符串坐标解析和单元格对象查找的成本。正确做法是直接用ws.iter_rows(values_onlyTrue)或ws.values拿到迭代器一次性顺序读完。第六多进程不一定能加速单个 Excel 文件读取。原因是解析 xlsx 的瓶颈通常集中在 CPU 和内存分配上如果文件在内存层面已经吃紧再开多进程只会让内存雪上加霜。更合理的并行方式是一个进程读一个文件文件数量多时才是真正的并行场景。对于单文件读取优化库和引擎比盲目并发更有效。10. 总结与后续可以继续做的事回到标题用 Python 测试常见库读取 10 万行 Excel 的耗时这个实验最有价值的不是得到一个精确的秒数而是帮你看清一件事——Excel 读取性能问题通常不是“数据量的问题”而是“解析路径的问题”。openpyxl 默认模式要承担大量对象构建成本只读模式避开了它pandas 的优势不体现在解析层而体现在读取后的数据处理层xls、xlsb、CSV 又是完全不同的赛道需要先锁定文件格式再做选型。你可以按这篇文章的脚本生成自己的基准数据跑一遍得到当前机器环境下可复现的对比结果。不要照搬网上的“某某库速度慢”结论真实项目里的文件结构、列数、样式复杂度都会影响结果。跑完测试后下一步还值得深入的方向有三个一是测试写入场景也就是 10 万行数据的写入和追加xlsxwriter 与 openpyxl 的差异同样明显二是调查 calamine 引擎在你当前 pandas 版本上的可用性它可能会成为读 xlsx 的新选择三是把真实业务中的复杂文件脱敏后放进基准看带着样式、公式、合并单元格的文件会不会改变当前结论。把这些实验跑完你对 Python 处理 Excel 的性能边界才算建立起了自己的判断体系。
返回列表