Excel 数据读取看起来是入门操作,但一旦数据量到 10 万行,很多问题就会集中冒出来:文件打开慢、单元格逐行读取慢、内存占用高、日期和字符串类型错乱。这次我们不空谈“哪个库最快”,而是围绕“用 Python 常见库读取 10 万行 Excel 到底要多久”这个具体问题,把测试目标、参与库、环境变量、读取脚本和判断方法完整铺开。你会看到 openpyxl、xlrd、pandas 等常见方案的能力边界,也会理解为什么同一个文件在不同读取方式下,得到的时间可能完全不是一个量级。
先把结论形态说清楚:这种评测最怕只给一个秒数。同样的 10 万行数据,列数变多、字符串变长、日期类型增加、文件是否带样式、是否走系统缓存,都会显著改变结果。所以这篇文章不只是给答案,更重要的意义在于提供一套可重复执行的测试流程,包括 10 万行测试数据怎么生成、不同库分别用什么代码读取、怎么统计多次运行的中位数、怎么看内存峰值,最后再补上批量读取多个 Excel 文件的工程写法。你完全可以把这份脚本搬到自己机器上,跑出一组自己环境的结果,再决定生产环境用哪种方案。
适合谁看?日常用 Python 处理 Excel 报表、写数据导入脚本、要把 Excel 解析封装成后台任务的开发者都适用。这个主题不涉及 GPU,也不需要深度学习环境,普通 CPU 机器加常见 Python 包就能跑。真正需要注意的点是:文件格式、读取引擎、是否流式、是否构建 DataFrame,这四件事通常比库本身的“网上跑分”更影响结论。
1. 核心能力速览
| 评测项 | 说明 |
|---|---|
| 评测目标 | 用 Python 常见库读取 10 万行 Excel,比较耗时与稳定度 |
| 数据规模 | 10 万行 X 5 列,包含数值、字符串、日期等常见类型 |
| 文件格式 | 以 .xlsx 为主;.xls、.csv 作为对比说明 |
| 参与库 | pandas、openpyxl、xlrd,可选 xlsxwriter、pyxlsb、polars 等 |
| 运行环境 | 普通 CPU 电脑即可,不依赖 GPU;内存建议按本机实际情况观察 |
| 核心操作 | 生成测试文件、单文件计时、流式读取、批量读取、内存监控 |
| 批量任务 | 支持,可通过目录遍历批量处理;是本地脚本形式,不是网络 API |
| 接口 API | 无现成 Web API;可按需用 FastAPI 等自行封装 |
| 适用读者 | Python 数据处理、报表导入导出、办公自动化开发人员 |
2. 为什么选择“10 万行”作为测试规模
Excel 单表行数上限约是 104 万行,10 万行只是它的十分之一左右。这个规模在办公场景里很典型:一份订单明细、客户名单、设备台账、日志导出,很容易积累到 10 万行。用 Excel 直接打开这种文件,拖动和筛选已经开始有卡顿感,但还没有到完全打不开的程度。对于 Python 脚本来说,10 万行也刚好能区分“纯解析 XM L”和“解析后构建 DataFrame”的不同代价。
另一个原因是文件体积和测试速度的平衡。如果只测几千行,绝大多数库的耗时都在毫秒级,差距可能被随机波动淹没;如果直接上 100 万行,openpyxl 默认模式读取会非常吃力,可能会因为长时间占用内存而让脚本中途崩掉。10 万行更容易跑完整个对比矩阵,也更贴近真实业务中“这份表有点大,但还得用 Python 处理”的状态。
还要提醒一点:10 万行并不是一个固定不变的测试条件。同一批数据保存成不同格式时,内部结构差异很大。.xlsx 本质是 zip 压缩的 XML 文件,除了单元格数据,还有可能包含共享字符串、样式、主题、图表等附加内容。而 .xls 是老的二进制格式,甚至还有单表行数 65536 的上限,10 万行本身就无法放进一个 .xls 工作表。因此做这类评测前,最好先明确你测的到底是哪个扩展名、哪些列、什么类型的数据。
3. Python 常见 Excel 读取库选型
| 库 | 主要支持格式 | 定位 | 注意事项 |
|---|---|---|---|
| pandas | .xlsx、.xls、.xlsb 等 | 数据分析、表结构统一 | 底层依赖 openpyxl、xlrd 等引擎 |
| openpyxl | .xlsx、.xlsm 等 | 读写并保留样式,支持只读流式 | 普通模式不是向量化读取,速度偏慢 |
| xlrd | .xls | 老格式读取 | 新版不再支持 .xlsx,只适合老 .xls |
| xlsxwriter | .xlsx | 写入 Excel | 不是读取库,写入性能有参考价值 |
| pyxlsb | .xlsb | 读取二进制 Excel | 适合 .xlsb 格式,但不是默认首选 |
| 标准库 csv | .csv | 纯文本对照 | 不是 Excel,但常用于“Excel 转出后处理” |
| polars / python-calamine | 多种格式 | 高性能读取 | 是否可用取决于安装版本和引擎支持 |
从材料看,这轮评测至少要覆盖 pandas 和 openpyxl。pandas 的read_excel接口统一,适合后续数据分析;openpyxl 能直接操作工作簿和单元格,更适合需要保留格式或逐行处理的场景。xlrd 则用来验证老 .xls 文件,不能拿它去强行读 .xlsx,否则会直接报“Excel xlsx file; not supported”。
在这类选型里,不要只看库的名字,还要看它背后用的解析引擎。pandas 读 .xlsx 时默认会调 openpyxl;如果你的 pandas 版本支持calamine之类的加速引擎,那读取耗时可能是另一套表现。最稳妥的方式是固定库版本和引擎参数,再做横向对比。
4. 环境准备与测试数据构造
4.1 安装依赖
先创建一个干净的虚拟环境,再安装需要对比的库。项目测试过程中,环境隔离能避免系统 Python 目录里的旧包影响结果。
python -m venv excel_bench_env source excel_bench_env/bin/activate pip install --upgrade pip pip install pandas openpyxl xlrd xlsxwriter pyxlsb如果你的 pandas 版本支持高性能读取引擎,也可以单独安装加速引擎:
pip install python-calamine polars安装完成后,最好确认一下版本。版本不同,read_excel的默认行为可能会有细微差别。
import pandas as pd import openpyxl import xlrd import sys print("Python:", sys.version) print("pandas:", pd.__version__) print("openpyxl:", openpyxl.__version__) print("xlrd:", xlrd.__version__)4.2 生成固定种子数据
做耗时测试时,测试数据必须固定。如果每跑一次都重新随机生成,文件内容不一样,单元格长度和字符串分布可能每次不同,得到的耗时就没有可比性。下面使用固定随机种子,生成 10 万行 X 5 列数据,包含整数、浮点数、分类字符串、长字符串、时间戳五种常见类型。
import numpy as np import pandas as pd from pathlib import Path rows = 100_000 rng = np.random.default_rng(42) df = pd.DataFrame({ "id": np.arange(rows), "amount": rng.uniform(0, 100000, rows), "category": rng.choice(["A", "B", "C", "D"], rows), "note": [f"row-{i}-python-excel-benchmark" for i in range(rows)], "created_at": pd.date_range("2024-01-01", periods=rows, freq="min"), }) xlsx_path = Path("data_100k.xlsx") df.to_excel(xlsx_path, index=False) print("file size MB:", round(xlsx_path.stat().st_size / 1024 / 1024, 2))这里选择 5 列而不是 1 列,目的是让测试更接近真实表格。实际业务中,10 万行往往不止一列,而且字符串列会显著影响文件大小和解析耗时。如果你想测更多列,可以直接在 DataFrame 里增加列字段,保存后重新生成文件。
要注意:如果你非要测 .xls 格式,请先记住 .xls 工作表的行数上限是 65536,10 万行数据放不下。更合理的做法是只对 .xlsx、.xlsb 这类格式做 10 万行对比,或者把 .xls 场景单独降级到 5 万行再说明。
5. 读取耗时测试流程设计
5.1 统一计时口径
耗时测试最怕口径不一致。同样是“读取 Excel”,可以拆成多种不同的完成标志:
- 只打开工作簿,不读任何单元格。
- 把所有单元格遍历一遍,但不保存成表。
- 把单元格内容转成 Python 对象列表。
- 将内容直接加载成 pandas DataFrame。
- 在 DataFrame 基础上做类型推断和日期解析。
这几种口径的耗时差异可能很大。所以测试前要先想清楚业务需要什么。如果后面只是做数据清洗,就要测到 DataFrame 加载完成;如果只是想快速检查表里有没有某些值,用 openpyxl 的只读流式模式可能更合适,但它在“能直接做数据分析”这个能力上是欠缺的。
下面给出一个计时函数,统一使用time.perf_counter,并重复多次后取中位数。
import gc import time import statistics def benchmark(func, repeat=3): times = [] for _ in range(repeat): gc.collect() start = time.perf_counter() result = func() delta = time.perf_counter() - start times.append(delta) del result return statistics.median(times)使用中位数而不是平均值,可以减少系统调度、垃圾回收和 CPU 降频带来的偶发波动。第一次运行和第二次运行往往会因为操作系统文件缓存而出现较大差异,所以不要只跑一次就下结论。
5.2 pandas 读取测试
pandas 是最常见的 Excel 读取方式,代码最简单,后续处理能力也最强。
file_path = "data_100k.xlsx" def read_pandas_openpyxl(): return pd.read_excel(file_path, engine="openpyxl", sheet_name=0) median_time = benchmark(read_pandas_openpyxl) print(f"pandas + openpyxl median: {median_time:.3f} s")如果你的环境安装了 calamine 引擎且 pandas 版本支持,可以把引擎改成"calamine"再测一次。要注意的是,引擎不同,最终得到的 DataFrame 类型不一定完全一致,需要使用同样的校验条件来验证。
def read_pandas_calamine(): return pd.read_excel(file_path, engine="calamine", sheet_name=0)如果你要读的其实是 .xls 老文件,pandas 的引擎应该切换成xlrd。这也能解释为什么不要只记一句话“pandas 慢”或者说“openpyxl 快”,因为你没有说明文件类型和引擎参数。
5.3 openpyxl 只读流式读取测试
openpyxl 的普通模式会把工作表和样式都加载到内存,遇到 10 万行大表会明显变慢。它还有一个read_only=True模式,可以流式读取单元格,不会一次性把所有行都加载成对象。下面测试的是遍历全部单元格并统计行数,不转 DataFrame。
from openpyxl import load_workbook def read_openpyxl_stream(): wb = load_workbook(file_path, read_only=True, data_only=True) ws = wb.active count = 0 for row in ws.iter_rows(values_only=True): if count == 0: # 第一行是表头 count += 1 continue count += 1 wb.close() return count median_stream = benchmark(read_openpyxl_stream) print(f"openpyxl read_only stream median: {median_stream:.3f} s")这段代码里用data_only=True,是为了读取公式计算后的缓存值而不是公式本身。如果在实际业务中你需要的恰恰是公式文本,那就要把data_only设成False,这也可能导致读取结果完全不同。
5.4 校验读取结果
耗时比较不能只看快慢,还要确认读出来的结果是对的。否则最快的方式可能就是“读错数据”。校验点主要有三个:行数是否等于 10 万、列名是否匹配、非空数据量是否正确。
df_check = pd.read_excel(file_path, engine="openpyxl", sheet_name=0) print("shape:", df_check.shape) assert df_check.shape[0] == 100_000 assert list(df_check.columns) == ["id", "amount", "category", "note", "created_at"] print("amount sum:", df_check["amount"].sum())如果你的测试文件是 CSV 读入的,再写入 Excel,日期可能会被转成字符串,读取时就要额外指定parse_dates。这属于数据污染的常见问题,先被校验逻辑拦下来,后面才不会把错误结果当成性能结论。
6. 影响读取耗时的关键变量
同样是“10 万行”,为什么大家在社区里给出的体验差别很大?根本原因在于行数不是唯一变量,真正决定耗时的是解析器需要处理的“单元格数量”和“内容复杂度”。
第一,单元格数量。10 万行 X 5 列是 50 万个单元格,10 万行 X 20 列就是 200 万个单元格。单元格数量翻倍,耗时通常不会只增加一点点。所以报告耗时的时候,最好同时写明列数。
第二,字符串长度和共享字符串机制。xlsx 文本内容通常存在共享字符串表里,字符串越多越长,解析和匹配成本就越高。如果一整列都是“row-0-xxx”这种带前缀的文本,测试结果会更接近真实但偏重的场景。
第三,文件压缩率和底层存储。xlsx 是 zip 压缩包,读取时要解压 XML。如果文件压缩率高,磁盘读取量更少,但 CPU 解压成本更高;如果文件放在机械硬盘、网络盘或 U 盘上,IO 等待会成为主要瓶颈。这提醒我们,测速前应确认文件在本机本地磁盘,避免网络盘干扰。
第四,是否读取样式。openpyxl 普通模式会接触工作簿的更多结构;如果只要数据,设置read_only=True能明显减少样式和绘图对象带来的额外处理。这里并不是说样式信息没有用,而是要看你的业务到底需不需要。
第五,日期和类型推断。pandas 读取后会把日期列解析成时间类型,需要额外判断单元格格式;openpyxl 流式遍历时拿到的则可能是一个 datetime 对象或字符串,这取决于文件内部写法。类型推断看起来很轻,但在几十万单元格上累积起来并不小。
第六,系统文件缓存。第一次读取文件后,操作系统会把文件放进缓存,第二次读取可能大幅提速。所以正确做法是轮流跑多个库,或安排多次重复,取中位数。不要简单地把第二次 fast 归因于某个库更强。
第七,GC 和内存抖动。大 DataFrame 读取和删除过程中,Python 的垃圾回收会产生随机停顿。脚本里每次计时前执行gc.collect(),可以降低前一任务在内存中残留造成的干扰。
第八,文件是否包含公式、条件格式、数据验证、合并单元格等复杂内容。公式要缓存的值,条件格式和合并单元格本身也会增加解析负担。这类文件更适合用 openpyxl 做结构化处理,而不是交给极简的 csv 思路。
7. 批量读取多个 Excel 与本地模块化调用
这个场景没有对外提供“网络接口 API”,但很适合封装成可供其他脚本调用的本地模块。实际工作中,经常需要一次性处理多个 Excel 文件,比如某个目录下每天都有新的报表需要读取。下面给出一个批量读取的思路,逐个文件记录读取耗时、行数和形状。
import time from pathlib import Path import pandas as pd def read_multi_excel(input_dir: str, engine: str = "openpyxl"): results = [] for fp in sorted(Path(input_dir).glob("*.xlsx")): start = time.perf_counter() try: df = pd.read_excel(fp, engine=engine, sheet_name=0) results.append({ "file": fp.name, "ok": True, "shape": df.shape, "seconds": round(time.perf_counter() - start, 4), }) except Exception as exc: results.append({ "file": fp.name, "ok": False, "error": str(exc), "seconds": round(time.perf_counter() - start, 4), }) return results if __name__ == "__main__": for item in read_multi_excel("excel_files"): print(item)批量读取时要考虑失败重试。很多临时性失败并不是逻辑错误,而是文件正在被其他程序占用,或者网络盘临时不可用。可以先把失败文件记录下来,等待一段时间后重试,而不是让整个任务中断。
import time def read_with_retry(path: Path, retries: int = 2, wait_seconds: float = 1.0): last_error = None for attempt in range(1 + retries): try: return pd.read_excel(path, engine="openpyxl", sheet_name=0) except Exception as exc: last_error = exc if attempt < retries: time.sleep(wait_seconds) raise last_error如果只是想给外部程序提供 API,可以用 FastAPI 包一层/read-excel接口。但接口传输的还是文件路径或文件内容,真正的性能瓶颈还是读取引擎本身。不要指望加了 HTTP 层之后,一个原本很慢的 openpyxl 普通读取就会变成毫秒级。
8. 资源占用与性能观察方法
这类测试除了计时,还要关注内存变化。不同库的内存模型差异比很多人想象的更大。pandas 会把数据读入结构化数组,内存开销高,但后续计算方便;openpyxl 的只读流式模式不会一次载入全部单元格,但如果你把遍历出来的数据都保存在 list 里,最终内存一样会上升。
可以用psutil记录当前进程的最大内存占用,把资源数据和耗时一起输出。
import os import psutil process = psutil.Process(os.getpid()) def memory_usage_mb() -> float: return process.memory_info().rss / 1024 / 1024在测试脚本里,读取前调用一次,读取后再调用一次,两者的差值就是这次读取带来的新增内存。注意,Python 进程本身导入 pandas 和 openpyxl 会占用一定基础内存,这部分要在对比时说明,避免误判为“读取 10 万行 Excel 就需要这么多内存”。
观察性能时,重点不是单看峰值。高内存占用配合快速执行,在内存充足的机器上是可以接受的;但如果你的目的是在 8GB 内存的服务器上批量跑任务,那就必须优先选择流式方案,并在每个文件处理完后尽快释放对象。
需要降低内存占用可以参考以下思路:
- 用
usecols只读需要的列,不要全表加载。 - 如果只是做字段提取,先考虑 openpyxl 只读流式。
- pandas 读取大文件后及时删除临时 DataFrame,并调用
gc.collect()。 - 数据量持续增长时,不要继续坚持 xlsx 作为中间格式,转存为 Parquet 或 SQLite 再做高频处理。
- 批量任务中分文件记录日志,方便定位是哪个文件导致的资源尖峰。
9. 常见问题与排查方法
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 运行代码提示缺少 openpyxl | 虚拟环境没有安装依赖 | pip list查看已安装包 | 执行pip install openpyxl |
| 用 xlrd 读 .xlsx 报错 | xlrd 新版只支持 .xls | 查看报错提示是 xlsx not supported | 改用 openpyxl 或 pandas 引擎 |
| 文件打开后报 BadZipFile | 文件损坏或并非真正 xlsx | 用压缩工具打开文件确认结构 | 重新导出,或使用原文件备份 |
| 读取后日期列变成字符串或数字 | Excel 日期内部存储和解析规则不同 | 打印 DataFrame 列 dtype | 用parse_dates或读取后pd.to_datetime |
| 读取结果出现 NaN | 单元格为空或 pandas 类型推断导致 | 检查原始表格空值分布 | 用keep_default_na等参数控制 |
| 内存占用过高 | pandas 全量加载所有列 | 观察任务管理器/psutil | 只读需要的列,或改用流式读取 |
| 连续读取两次耗时差异很大 | 系统文件缓存影响 | 重置缓存或多次取中位数 | 多次运行,按中位数比较 |
| 10 万行 .xls 保存失败 | .xls 行数上限是 65536 | 确认文件格式限制 | 改用 .xlsx 或拆分文件 |
| 读取出来的公式值为空 | 没有读取公式缓存值 | 检查data_only参数 | 需要缓存值设True,需要公式文本设False |
| 批量任务处理到一半中断 | 某个文件损坏或格式特殊 | 给循环加 try except | 记录失败文件并设置重试 |
常见问题排查的顺序也很重要:先看报错是来自语法、依赖,还是文件本身;先读小文件验证脚本,再上 10 万行;先固定文件路径和版本,再谈优化。很多时候问题不是某个库不行,而是环境、文件格式和读取参数之间不匹配。
10. 读取方案选型与最佳实践
不同场景下的推荐方案并不相同。如果你的需求是几十行到几千行的轻量读取,pandas 的read_excel最方便,代码最统一。如果是 10 万行左右、需要清洗和分析,pandas 配合 openpyxl 引擎已经足够,关键是要避免使用 openpyxl 逐单元格手动拼 DataFrame,那样的写法会放大性能差距。
如果是超大 xlsx 文件,而且只想按行扫一遍做简单过滤,openpyxl 的read_only=True流式模式会更合适,但你需要接受一个现实:它返回的是单元格生成器,而不是可以直接做列聚合的 DataFrame。如果你需要同时保留高效读取和 DataFrame 分析能力,常见的工程化选择是先把 xlsx 转成 CSV 或 Parquet,再进入分析流程。这里的“转格式”看起来多了一步,在重复读取多次时往往反而更快。
还有几个工程化建议值得养成习惯。第一,第一次使用新库时要先读小文件验证功能,确认行数、列数和类型正确后再测大文件。第二,保留一套最小可运行配置,把文件生成、计时、校验分开,不要在一个脚本里混入可视化、打印、网络请求等无关操作。第三,模型文件、输入数据、输出结果分目录管理,Excel 类临时文件和正式数据不要混在一个目录里。第四,批量任务必须加日志,至少记录每个文件的成功状态、处理耗时和异常信息。第五,接口服务如果对外开放,要限制访问范围,防止别人通过接口上传超大文件把进程内存打满。
在合法合规方面,处理 Excel 数据时要考虑数据来源和授权边界。如果表格里包含个人姓名、电话、地址、人脸、声音等敏感信息,读取和存储都要遵循最小必要原则,不要为了做性能测试而随意使用他人真实数据。测试阶段尽量用自己生成的数据,例如本文里的随机订单数据,这是最稳妥的方式。
关于“前端和后端如何配合”其实不在这次评测范围里,但有一点值得强调:如果把 Excel 解析放到后台服务,建议对上传文件做格式白名单、大小限制和并发数限制。否则即使某个读取库本身很快,恶意上传或异常文件也会拖垮服务。
11. 总结与下一步
这次评测的核心不是追求一个“谁最快”的单一答案,而是搞清楚 Python 读取 Excel 时,哪些条件会影响最终耗时。最容易踩的坑有三个:第一,只比库名不比引擎,开放 pyxl 和 pandas 在同一个文件上本来就不是同一层操作;第二,只比耗时不管内存,高内存方案在本地能跑,上线后很可能 OOM;第三,只看读取不校验结果,一旦类型解析错误,性能结论就失去了意义。
如果你想在自己机器上复现,建议先跑通数据生成脚本,再执行 pandas 和 openpyxl 两组计时,最后用 psutil 观察内存。拿到结果后,可以进一步扩展的方向包括:不同列数下的耗时变化、字符串占比对解析速度的影响、读取后转 Parquet 再分析的性能、以及用 FastAPI 封装本地读取接口的并发能力。
这篇文章里的代码可以直接复制,替换成你的实际文件路径,就能得到一份属于自己环境的评测结论。建议先收藏备用,之后遇到“Python 读大 Excel 慢”的问题时,可以对着这些变量逐项排查。