☰
Python批量Excel转CSV实战:多sheet、异常兜底与数据校验
2026/10/28 17:17:57 网站建设 项目流程

简介:这是一款面向Windows用户的批量Excel转CSV小工具,主要解决将指定目录下xls、xlsx文件一次性全部转换为csv格式的重复劳动问题,适合需要批量处理表格数据、又不想逐一手动另存为的办公与数据处理人员。资源包共33个文件,压缩后约94KB,内含7个h头文件、5个cpp源文件以及vcproj、sln、dsw等工程配置,另附2个exe可执行文件、2个xls与1个xlsx示例表格和2个csv转换结果,方便直接对照验证。作者在原源码基础上修复了与Windows SDK重定义Font类导致的编译失败问题,并重新编译,懒得自行编译的同学可直接使用bin目录下的ExcelToCsv.exe,仅限Win系统,作者已实测可用。目前已有2680人学习下载,读者既能直接拿工具完成批量转换,也能通过完整工程与示例文件研究Excel读写与目录遍历的实现思路。

1. 批量Excel转csv工具:从几十个xlsx里把数据捞出来的实战路径

手里压着几十个甚至上百个.xlsx、.xls文件,每个文件结构差不多但表头位置、合并单元格、隐藏 sheet 又各有各的脾气,这种场景做数据清洗的人都不陌生。批量Excel转csv工具要解决的就是这件事:把散落在多个工作簿里的表格,统一转成.csv这种纯文本格式,方便后续导入数据库、喂给 Python 脚本、或者丢进 ArcGIS 做空间数据关联。它适合数据分析、GIS 入库、后端 ETL 前处理这几类人,尤其是那些被“excel加载项被禁用”或者“excel无法粘贴数据”折腾过、不想再手动另存为的从业者。核心诉求就三个:批量、保结构、可复现。

2. 转换前先想清楚:xlsx、xls、csv 到底差在哪

2.1 三种格式的底层差异决定了转换策略

.xlsx本质是个 zip 包,里面装着 XML,能存公式、样式、多 sheet、合并单元格;.xls是老的二进制格式,读取要靠专门的解析库;.csv是纯文本,只有行列和分隔符,没有类型、没有公式、没有多 sheet。这个差异直接决定了转换不是“改个后缀”那么简单。常见做法是用 Python 的pandas配合openpyxl(读 xlsx)和xlrd(读 xls),把每个 sheet 读成 DataFrame 再写出 csv。选pandas的理由是它对表头、缺失值、编码的处理最成熟,遇到“excel无法打开文件,因为文件格式或文件扩展名无效”这类脏文件时,报错信息也比裸解析库清楚。

2.2 环境准备与依赖安装

先确认 Python 版本,建议 3.9 以上,然后装依赖。openpyxl负责 xlsx,xlrd只用来读老 xls(2.0 之后不再支持 xlsx,别装错版本),pandas做数据中转。

# 建议在虚拟环境里操作,避免污染全局 python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install pandas openpyxl xlrd==2.0.1

参数说明:xlrd==2.0.1是最后一个支持 xls 的稳定版本,装 2.0 以上读 xls 会直接报错。openpyxl不需要锁版本,但如果你要处理带宏的.xlsm,得额外装openpyxl并设置keep_vba=True,不过转 csv 用不上宏,忽略即可。

2.3 单文件转换的最小可用脚本

先把单个文件跑通,再谈批量。下面这段读一个 xlsx 的第一个 sheet 并写出 csv,编码用utf-8-sig,这样 Excel 再打开 csv 不会中文乱码。

import pandas as pd def excel_to_csv(src, dst, sheet_name=0): # sheet_name=0 表示读第一个 sheet,也可传具体名字 df = pd.read_excel(src, sheet_name=sheet_name, engine="openpyxl") # index=False 避免把行号写进 csv df.to_csv(dst, index=False, encoding="utf-8-sig") print(f"done: {src} -> {dst}") excel_to_csv("sample.xlsx", "sample.csv")

逻辑说明:read_excel的engine参数在文件后缀和实际格式不一致时特别关键,比如文件名叫.xlsx实际是 xls,指定 engine 能避免“文件格式或扩展名无效”的误判。utf-8-sig比纯utf-8多一个 BOM 头,是给 Excel 看的,如果你后续用 Python 读这个 csv,用encoding="utf-8-sig"也能正常解析。

3. 批量转换的工程化写法:遍历、多sheet、异常兜底

3.1 目录遍历与输出路径规划

批量转换第一件事是把输入输出目录分开,别把 csv 写回原目录,否则下次遍历会把 csv 也当输入。用pathlib比os.path更清爽,也方便跨平台。

from pathlib import Path def collect_excels(root): root = Path(root) # rglob 递归匹配,支持 xlsx 和 xls files = list(root.rglob("*.xlsx")) + list(root.rglob("*.xls")) # 过滤掉 Excel 临时文件 ~$ 开头 return [f for f in files if not f.name.startswith("~$")] files = collect_excels("./input") print(f"found {len(files)} files")

参数说明:~$开头的是 Excel 打开文件时生成的锁文件,体积小但会让脚本报错,必须过滤。rglob会递归子目录,如果你的文件是平铺的,用glob也行,但递归更保险。

3.2 多 sheet 拆分与命名规则

一个工作簿有多个 sheet 时,常见做法是每个 sheet 输出一个 csv,文件名用原文件名_sheet名.csv。sheet 名里可能有空格、斜杠、中文,斜杠在路径里非法,要替换掉。

import re def safe_name(name): # 把路径非法字符替换成下划线 return re.sub(r'[\\/:*?"<>|]', "_", str(name)) def convert_workbook(src, out_dir): src = Path(src) out_dir = Path(out_dir) out_dir.mkdir(parents=True, exist_ok=True) # sheet_name=None 返回所有 sheet 的字典 sheets = pd.read_excel(src, sheet_name=None, engine=None) for name, df in sheets.items(): out = out_dir / f"{src.stem}_{safe_name(name)}.csv" df.to_csv(out, index=False, encoding="utf-8-sig") print(f" sheet [{name}] -> {out.name}")

逻辑说明:sheet_name=None是关键,它让 pandas 返回{sheet名: DataFrame}的字典,一次读全。engine=None让 pandas 自己判断,但遇到后缀和实际格式不符时可能翻车,稳妥做法是根据后缀显式指定:.xls用xlrd,.xlsx用openpyxl。

3.3 异常兜底与日志记录

批量脚本最怕一个文件报错整个中断。用 try/except 包住单个文件,把失败的文件名和原因记下来,最后统一看。

import traceback def batch_convert(in_dir, out_dir): ok, fail = 0, [] for f in collect_excels(in_dir): try: convert_workbook(f, out_dir) ok += 1 except Exception as e: fail.append((str(f), repr(e))) # 打印堆栈方便定位,生产环境可写日志文件 traceback.print_exc() print(f"\n成功 {ok} 个,失败 {len(fail)} 个") for name, err in fail: print(f" FAIL {name}: {err}") batch_convert("./input", "./output")

参数说明:repr(e)比str(e)多带异常类型,排查时更有用。失败清单建议同时写进一个fail.log,文件多了以后翻终端输出很痛苦。这一步做完,一个能扛住几十上百文件的批量转换脚本就成型了。

4. 避坑与排查:那些让转换结果对不上的细节

4.1 现象:csv 打开中文全是乱码

原因:写出时用了默认编码或者纯utf-8,而 Excel 在中文 Windows 下默认按 GBK 解析 csv。解决:写出统一用encoding="utf-8-sig",它带 BOM 头,Excel 能正确识别。如果下游是数据库或 Python,读的时候也用utf-8-sig即可,不影响解析。

4.2 现象:日期列变成 45231 这种数字

原因:Excel 内部把日期存成序列号,pandas 读的时候如果该列混了空值或文本,可能不自动转日期。解决:读入后对日期列显式处理,pd.to_datetime(df["日期列"], errors="coerce"),errors="coerce"让无法解析的变成 NaT 而不是报错。或者读的时候传dtype=str全部当字符串,后续再按需转。

4.3 现象:前几行是标题说明,表头不在第一行

原因:很多业务表格第一行是合并的大标题,真正的列名在第二或第三行。解决:read_excel传header=2(从 0 计数),或者skiprows=2跳过前两行。如果每个文件表头行数不一样,就得先读几行判断,常见做法是读nrows=5看哪一行非空列最多,把它当表头。

4.4 现象:数字前面的 0 丢了,比如工号 00123 变成 123

原因:Excel 里这列是数值类型,前导零本来就不存。解决:读的时候dtype={"工号": str}强制该列为字符串。但注意,如果 Excel 里已经存成数值,前导零在源文件里就没了,pandas 也救不回来,这种情况只能从源头要求录入时设成文本格式。

4.5 现象:脚本报 “Excel file format cannot be determined”

原因:文件后缀和实际格式不符,或者文件损坏、是加密文件。解决:先手动用 Excel 打开确认能正常打开;能打开的话,根据实际格式显式指定 engine。加密文件 pandas 读不了,需要先用msoffcrypto-tool解密,这一步属于额外处理,不在基础脚本范围内。

5. 进阶技巧:把转换接进自动化流程与结果校验

5.1 用命令行参数让脚本可复用

把目录写死在代码里,换个项目就得改代码。用argparse包一层,变成命令行工具,配合定时任务或者批处理调用。

import argparse if __name__ == "__main__": parser = argparse.ArgumentParser(description="批量 Excel 转 CSV") parser.add_argument("-i", "--input", required=True, help="输入目录") parser.add_argument("-o", "--output", required=True, help="输出目录") parser.add_argument("--header", type=int, default=0, help="表头所在行,默认 0") args = parser.parse_args() batch_convert(args.input, args.output)

参数说明:--header暴露出来是因为不同项目表头位置不同,做成参数就不用改代码。required=True保证不传目录时直接报错,而不是默默跑错目录。

5.2 转换后的行数与列数校验

转换完不能只看“成功 N 个”,得验证数据没丢。常见做法是转换时记录每个文件的原始行数和输出 csv 行数,对比是否一致(注意 csv 行数 = 数据行数 + 1 表头)。

校验项原始来源输出 csv判断标准
数据行数df.shape[0]len(open(csv).readlines()) - 1两者相等
列数df.shape[1]首行列数两者相等
空文件df.empty文件大小空 DataFrame 应告警

把这张表落成代码,就是在convert_workbook里加几行断言,失败的文件进 fail 清单。这一步能拦住“转换成功但数据少了一半”这种最坑的情况。

5.3 处理超大文件的内存技巧

单个 xlsx 超过几十万行时,read_excel一次性读进内存可能吃满。常见做法是分块读,pd.read_excel本身不支持 chunksize,但可以先用openpyxl的read_only=True模式逐行迭代,再分批写 csv。或者更简单:把大文件先拆成多个小 xlsx 再走批量流程。我一般会先看文件大小,超过 50MB 的就单独处理,不混在批量脚本里。

从那以后我每次跑批量转换,都强制先拿两三个样本文件跑一遍,人工核对行数、列名和几个关键单元格的值,确认无误再放开全量。这个习惯帮我拦下过好几次表头错位和编码翻车。希望帮到你。

本文还有配套的精品资源,点击获取

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

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

立即咨询