1. 需求场景拆解:什么情况下非要把 Excel 切开
1.1 四种高频切分场景,看看你中了几个
Excel 按行切分这件事听着像小需求,但只要你在数据岗待过一段时间,就会发现它几乎是每个月底的固定动作。我把过去几年遇到过的情况归了归类,大致跑不出下面四种。
第一种是导入限制触发的被动切分。不少业务系统的导入模板对单文件行数有硬性限制,超过就直接报错或者静默截断一半数据,那种"导进去了但少了一半"最要命,因为不看回执根本发现不了。这时就必须把一张几十万行的明细拆成若干个小文件,一个个喂进去。
第二种是分发需求导致的切分。比如一张全量客户对账单,需要按区域或者按负责的销售拆成多个文件,分别发给不同的人。全量发出去既没意义也不安全,每个人只看自己那一块就行。这种切分的依据不是行数,而是某个字段的取值。
第三种是性能问题倒逼的切分。一个两三百万行的表,打开要等半分钟,随便筛一下又卡住,公式一多直接假死。这时候与其硬扛,不如按时间或者按业务编号切成若干张,单表控制在十万行以内,操作体验立刻回来。
第四种是送审送检的格式要求。某些流程要求提交的文件每份不得超过固定大小,或者每份只能包含固定条数的记录,一条不多一条不少。这种对"切得准"的要求最高,多一行少一行都要返工。
这四种场景看起来都是"把大文件变小文件",但切分的依据、对格式的要求、出错后的代价完全不同。想清楚自己属于哪一类,后面的方案选型才不会走弯路。
1.2 手工切分的成本账:为什么必须工具化
有人会想,不就是复制粘贴吗,插几行空行分一分不就完了。我算过一次账,结论是这条路基本走不通。
假设一个 12 万行的表,要按每 5000 行切一份,共 24 份。手工流程是:定位到第 5001 行、选中剩余所有内容、剪切、新建工作簿、粘贴、另存为、命名、关掉。熟练的人一份大概 40 秒,24 份接近 20 分钟。这还不是最糟的,最糟的是中途被打断,回来忘了切到第几行了,只能从头核对。
更要命的是准确性。手工操作里最容易出的事故是剪切时的选区偏移,导致某一行重复出现在两个文件里,或者某一行直接消失。12 万行的数据,少一行多一行肉眼根本看不出来,等到下游对不上账才发现,回溯成本远高于当初写个脚本的时间。
还有一次性的问题:这个月切完了,下个月数据一更新,全部重来。所以我的判断很简单——只要这个动作发生第二次,就值得写成工具。第一次可以手工验证逻辑对不对,第二次开始一律跑脚本。
写脚本的投入大概是一两个小时,换来的是每次几十秒出结果,而且结果稳定可复现。这笔账怎么算都划算。
1.3 切分粒度怎么定:行数、大小还是业务键
定了要工具化,下一个问题就是按什么标准切。这个决策直接决定了工具的设计,选错了后面怎么调都别扭。
按固定行数切是最通用的一种,每 N 行一个文件,不管内容是什么。它适合纯导入场景,因为下游只关心"每个文件不超过多少行"。N 的取值一般看下游限制,留 10% 到 20% 的余量比较稳妥,比如限制 5000 行就按 4500 切。
按文件大小切更适合走传输渠道的场景。行数和大小不是线性关系,一行全是长文本和一行全是数字,体积能差十几倍。如果你的约束是"每个文件不超过 5MB",那就得按字节算,边写边累加体积,超过阈值就换下一个文件。
按业务键分组切是另一套逻辑,常见的是按日期、按区域、按部门。这种切法的特点是每份文件的大小完全不可控——热门区域的记录可能是冷门区域的几十倍。所以实践中通常是两级策略:先按业务键分组,组内再按行数二次切分,组太大就继续拆,组太小可以考虑合并。
我个人的默认配置是:数据类文件按行数切,分发类文件按业务键切,交付类文件两者结合。这个默认值覆盖了大概八成的情况,剩下的两成再单独调。
2. 方案选型:三条路线各自的适用边界
2.1 路线一:办公软件内置功能与 Power Query
先说不用写代码的路子,因为很多人被"要装 Python"这一步劝退了。
主流办公套件里,部分版本确实内置了"拆分工作表"的功能,一般藏在数据选项卡或者工具箱里。它的优点是零门槛,点几下就出结果;缺点是参数非常死,通常只支持"按固定行数"或者"按某列的不同取值",没法做复杂逻辑,比如"跳过前三行标题、从第四行开始算、每份保留表头"。而且不同版本的功能位置和命名还不一样,换个环境就找不到了。
更靠谱的无代码方案是Power Query。它的思路是把切分变成一次数据变换:先给数据加一个索引列,然后用索引除以每份行数取整,得到一个"分组号"列,再按这个分组号分组,最后把每组导成一个独立的表或者文件。整个过程有记录、可复用,数据更新了只要刷新一下。
Power Query 的优势在于它是办公软件原生能力,不需要额外环境,同事接手也能看懂步骤。它的短板也很明显:处理几十万行以上的数据时刷新速度会明显变慢,而且导出成多个独立文件这一步,原生界面支持得并不好,通常还得配合一段简短的宏代码来实现"批量另存"。
所以我的判断是:数据量在十万行以内、逻辑简单、需要交付给非技术同事维护的,优先 Power Query。超过这个量级,或者逻辑里带条件判断的,直接上脚本,别在图形界面里绕。
2.2 路线二:脚本化处理(openpyxl / pandas)
脚本路线是main力方案,也是我实际工作中用得最多的。Python 生态里处理 Excel 最常用的两个库,定位完全不同,得分开说。
pandas强在数据处理,读进来就是一个二维表结构,切片、筛选、分组都是一行代码的事。用它做切分,核心代码可能就七八行。但它有个致命短板:读写过程中格式基本全丢。字体、颜色、边框、列宽、条件格式、合并单元格,统统保不住,写出去的就是一张干干净净的裸表。如果你的表只是纯数据,这反而正合适——快、简单、体积小。
openpyxl强在文件结构层面,能读到工作簿的每一个细节,样式、公式、批注、超链接都能拿到。它支持流式读写模式,处理大文件时内存占用控制得很好。代价是 API 相对啰嗦,写个切分脚本大概要五六十行,而且逐单元格复制样式会明显拖慢速度。
我的实际组合是:纯数据切分用 pandas,需要保留格式的用 openpyxl。两条路都准备着,遇到什么活用什么工具,不用强行统一。
2.3 三条路线的横向对比表
把几种方案摆在一起对比,选型的时候能少纠结。
| 维度 | 办公软件内置功能 | Power Query | pandas | openpyxl |
|---|---|---|---|---|
| 上手门槛 | 极低 | 低 | 中 | 中 |
| 是否需要额外环境 | 否 | 否 | 是 | 是 |
| 十万行以内速度 | 快 | 较快 | 很快 | 快 |
| 百万行级别 | 基本不可用 | 很慢 | 内存吃紧 | 流式模式可扛 |
| 格式保留 | 部分保留 | 基本不保留 | 不保留 | 可完整保留 |
| 复杂逻辑支持 | 无 | 中等 | 强 | 强 |
| 可维护性 | 差 | 好 | 好 | 好 |
| 批量输出多文件 | 一般 | 需配合宏 | 好 | 好 |
表格里最值得注意的一行是"百万行级别"。很多人踩的坑是拿 pandas 硬读一个两百万行的表,机器内存不够直接崩掉,还以为是代码写错了。实际上 pandas 读 Excel 是先把整个文件解开成对象再转换,峰值内存大概是文件体积的十几倍,一个 50MB 的表就可能吃掉 800MB 以上内存。这种情况必须换 openpyxl 的只读流式模式,它是一行一行吐数据,内存占用基本恒定。
3. 核心实现:按行切分脚本的关键细节
3.1 环境准备与库版本
环境这块没什么玄机,但版本对不上会浪费很多时间排查。
python -m venv venv source venv/bin/activate # Windows 下用 venv\Scripts\activate pip install openpyxl pandas我一般会顺手把版本固定下来,写进一个依赖文件里。原因是 openpyxl 在 3.x 的几个小版本之间,只读模式对某些特殊文件的行为有过变化,尤其是带大量合并单元格的表格,老版本会直接抛异常。固定版本能保证今天能跑的脚本,半年后同事拿去也能跑。
pip freeze > requirements.txt还有一个容易被忽略的点:处理 Excel 的机器上不一定要装办公软件。openpyxl 是纯 Python 实现,不依赖任何本地组件,扔到服务器上照样跑。这点在做定时任务的时候特别有用——不用为了跑个切分脚本,在服务器上装一整套办公套件。
3.2 表头定位:别只写 header=1
这一段是真实踩过的坑,值得单独拎出来说。
几乎所有教程里的切分示例,都默认第一行是表头,第 1 行开始是数据。但真实业务表格里,第一行很可能是标题("2024 年某季度销售明细表"),第二行是制表人和日期,第三行才是真正的字段名,第四行开始才是数据。你按"第一行表头"去切,切出来的每个文件都带着一行大标题和一行日期,导入下游系统百分百报错。
所以我的做法是:表头行数和数据起始行,一律做成显式参数,不猜。
HEADER_ROWS = 3 # 前 3 行作为表头,每份文件都重复写入 DATA_START_ROW = 4 # 从第 4 行开始算数据判断表头到哪一行为止,有个很土但很有效的办法:看第一列从哪一行开始"往下都是同类型的东西"。标题行第一列通常只有一个格子有内容,后面全是空;字段名那一行第一列是个短词;数据行第一列是具体的编号或者名称。人工扫一眼就能定,定完写死,别在代码里做启发式猜测,那玩意儿在边界情况下比人不可靠。
还有一类表更阴险:中间夹着小计行或者空行。这种表在切分前必须先清洗,把所有非数据行剔掉,否则切出来的分片里会混进小计行,下游一算总和就翻倍。我的处理习惯是先跑一遍清洗,把数据规整成"首行字段名 + 纯数据"的标准形态,再进入切分流程。多一步,但省心。
3.3 主流程代码与逐段解释
下面这份是按固定行数切分的完整实现,用的是 openpyxl 的流式读写模式,几十万行也不至于把内存吃爆。
import os from openpyxl import load_workbook, Workbook def split_by_rows(src_path, out_dir, rows_per_file=5000, header_rows=1, sheet_name=None, prefix=None): """ src_path 源文件路径 out_dir 输出目录 rows_per_file 每个分片包含的数据行数(不含表头) header_rows 表头占用的行数,每份分片都重复写入 sheet_name 指定工作表,None 表示取第一个 prefix 输出文件名前缀,None 表示用源文件名 """ os.makedirs(out_dir, exist_ok=True) base = prefix or os.path.splitext(os.path.basename(src_path))[0] wb_in = load_workbook(src_path, read_only=True, data_only=True) ws_in = wb_in[sheet_name] if sheet_name else wb_in.worksheets[0] row_iter = ws_in.iter_rows(values_only=True) # 先把表头抓出来存着,后面每份都要重写 headers = [] for _ in range(header_rows): headers.append(list(next(row_iter))) part_no = 1 buffer = [] def flush(buf, idx): wb_out = Workbook(write_only=True) ws_out = wb_out.create_sheet(title=(ws_in.title or "Sheet1")[:31]) for h in headers: ws_out.append(h) for r in buf: ws_out.append(list(r)) name = f"{base}_part{idx:03d}.xlsx" path = os.path.join(out_dir, name) wb_out.save(path) print(f"[OK] {name} 数据行数 {len(buf)}") return path for row in row_iter: if row is None: continue # 全空行直接丢弃,避免污染分片 if all(v is None or str(v).strip() == "" for v in row): continue buffer.append(row) if len(buffer) >= rows_per_file: flush(buffer, part_no) part_no += 1 buffer = [] if buffer: flush(buffer, part_no) else: print("[WARN] 没有剩余数据,最后一片未生成") wb_in.close()逐段说一下为什么这么写。
read_only=True是性能关键。开着这个模式,openpyxl 不会把整个工作表解析成对象树,而是按行流式吐数据,内存占用基本恒定。代价是这时候不能改单元格、不能读样式,只能读值。切分场景恰好只需要值,所以这个取舍完全划算。
data_only=True处理的是公式问题。很��表里的关键列是公式算出来的,不加这个参数,读到的就是=VLOOKUP(...)这串字符串,写出去下游全是错的。加了之后读的是缓存值,但有个前提条件:这个文件必须曾经被办公软件打开并保存过。如果文件是程序直接生成的、从没被打开过,缓存值压根不存在,读出来是None。这个坑我栽过一次,排查了大半天,最后发现是上游系统导出时没计算公式。
iter_rows(values_only=True)省掉了单元格对象,直接返回值的元组,速度快不少。
全空行过滤那几行别小看。真实表格里经常出现"看着没数据但实际有格式"的行,或者中间的一段间隔行,不过滤的话会被当成数据行数进去,导致每份分片的实际有效行数少于预期。
write_only=True同样是流式写入,配合ws.append()一行一行推。注意这个模式下不能读回刚写的内容,也不能合并单元格,所以样式相关的操作要提前想清楚。
3.4 保留格式的进阶写法
有些场景必须保格式,比如带底色标记的异常记录、带边框的报表模板、带条件格式的看板。这时候write_only和values_only都得关掉,改成逐单元格搬运。
from openpyxl import load_workbook def copy_row_with_style(ws_in, ws_out, row_idx): for col_idx, cell in enumerate(ws_in[row_idx], start=1): new_cell = ws_out.cell(row=row_idx, column=col_idx, value=cell.value) if cell.has_style: new_cell.font = cell.font.copy() new_cell.fill = cell.fill.copy() new_cell.border = cell.border.copy() new_cell.alignment = cell.alignment.copy() new_cell.number_format = cell.number_format这段逻辑本身不复杂,但性能代价非常大。样式复制要反复创建对象,实测下来比纯值写入慢五到十倍。一个 20 万行、30 列的表,纯值写入大概十几秒,带样式搬运要两三分钟,如果还有大量条件格式,时间还得往上翻。
我的做法是分级处理:表头行一定要搬样式(因为它是每份文件的脸面,格式错了很显眼),数据行只搬数字格式和填充色(这两个下游最常校验),字体和边框一律放弃。这样能省掉一大半时间,效果上也能应付绝大多数场景。
合并单元格要单独提醒一句。如果你的表头里有合并单元格(比如"基本信息"横跨三列),直接按原样逐格搬运会报错,必须先读一次合并区域列表,在新工作簿里重建同样的合并区域。这块代码稍微绕,我一般建议:能用取消合并 + 居中对齐伪装的就别用真合并,省事很多。
3.5 参数计算:文件大小估算与分片数
rows_per_file这个参数别拍脑袋定,估一下更稳。
Excel 单工作表的上限是 1048576 行、16384 列,超过这个数保存都会失败。所以如果你的源表接近这个量级,切分不是可选项而是必须项。
文件体积的粗估可以用这个经验公式:每 1 万行 × 每行 20 列的数据量,xlsx 格式大约在 1.5MB 到 3MB 之间。变量主要来自单元格内容的平均长度和一列里长文本的比例。如果一列是备注字段、单格动辄上百字,那体积会明显偏大。
分片数就是总行数除以每片行数向上取整:
import math total_rows = 123456 rows_per_file = 5000 part_count = math.ceil(total_rows / rows_per_file) print(part_count) # 25留余量的习惯我一直保持着。下游说限制 5000 行,我就按 4500 切,因为下游的计数口径可能和我不一样——它可能把表头也数成一行,也可能把空行算进去。留 10% 的缓冲,比自己反复调试省事得多。
4. 实操记录:一次20万行数据的完整切分过程
4.1 前置检查清单
正式跑之前,我有一套固定的检查动作,五分钟能做完,但能挡掉大部分事故。
第一,确认总行数和有效行数。用办公软件打开拉到最底下看看,再配合脚本读一遍计数,两个数对不上就说明有隐藏行或者中间的空行干扰,得先搞清楚。
第二,确认表头行数和字段数。数一下列数,看看有没有隐藏列。隐藏列这事特别隐蔽,肉眼看不见但在文件里是存在的,切完之后突然多出来一列,追查起来很费劲。
第三,检查有没有公式依赖跨表引用。如果某一列的值是靠引用另一个工作表算出来的,切分时那个被引用的表没被带过去,值就变成None或者#REF!。解决办法是切分前先把公式列转成值。
第四,确认输出目录有写权限,而且要留足空间。20 万行的表切出 40 多个文件,加起来体积可能比源文件还大(每个文件都有自己的结构开销)。
第五,定好命名规则并写下来。源文件名_part001.xlsx这种带三位序号的格式是我一直在用的,好处是文件管理器里排序天然正确,不会出现 part10 排在 part2 前面那种尴尬。
这套检查我最初是凭记忆做的,漏过两次之后老老实实写成了一张清单贴在显示器边上。清单化的好处是换台机器、换个人,流程一样。
4.2 执行与耗时实测
那次的源文件是 203,847 行、26 列的订单明细,体积 89MB,包含日期、金额、编号、备注四类字段,备注列平均长度在 40 字左右。
参数定为rows_per_file=5000、header_rows=1,理论上会切出 41 个文件,最后一片只有 3847 行。
实测耗时分布大概是这样:
| 阶段 | 耗时 | 说明 |
|---|---|---|
| 打开文件并进入只读模式 | 约 2 秒 | 只解析结构,不加载数据 |
| 逐行读取并缓冲 | 约 25 秒 | 大头是解析 XML |
| 写入 41 个文件 | 约 18 秒 | 每片约 0.4 秒 |
| 合计 | 约 45 秒 | 内存峰值约 180MB |
45 秒对比手工操作的两三个小时,量级差异不用多说。内存峰值 180MB 也在可控范围内,随便一台普通办公机都能扛。
这里有个细节值得说:整体耗时的大头在读不在写。因为是流式读取,每读一行都要走一遍 XML 解析,26 列就是 26 次取值。如果列数更多或者单元格内容更长,这个时间还会涨。想提速的话,最有效的办法是先把源文件转成 CSV 再处理,CSV 的解析速度比 xlsx 快一个数量级,处理完再转回 xlsx。当然,这么做的前提是格式不重要。
4.3 结果校验方法
切完不校验等于没做,这是我最坚持的一条。
最简单的校验是行数求和。把 41 个分片的有效数据行数加起来,必须等于源文件的有效数据行数。
import os, glob from openpyxl import load_workbook total = 0 for f in sorted(glob.glob("output/*.xlsx")): wb = load_workbook(f, read_only=True, data_only=True) ws = wb.worksheets[0] cnt = sum(1 for _ in ws.iter_rows(min_row=2, values_only=True)) print(os.path.basename(f), cnt) total += cnt wb.close() print("合计:", total)第二个校验是关键字段去重计数。挑一个理论上唯一的字段(订单号、流水号之类),把所有分片里的这个字段汇总起来去重,去重后的数量应该和总行数一致。不一致就说明有重复行,多半是切分时的边界处理有问题。
第三个校验是抽样比对首尾。每个分片的第一行和最后一行,拿回源文件核对一下,看看是不是连续。这一步能抓出"漏行"和"错位"两类问题,而且人工看几眼就能确认,比写脚本快。
三个校验都过,我才认为这次切分是成功的。多花五分钟,换来的是不用在下游报错时手忙脚乱。
5. 踩坑手册:数据不失真的十个细节
5.1 数据失真类问题
这一类最恶心,因为文件能打开、能导入、不报错,但数据本身已经悄悄变形了。
长数字变科学计数法。订单号、银行卡号、身份证号这类 15 位以上的数字,写出来经常变成1.23457E+18。根子在类型判断上——写入时如果值是个数字类型,Excel 会按数值处理并按列宽显示。解决办法是在写入前统一转成字符串,并且在写完后把该列的数字格式设成文本@。这个坑我在一个银行卡号字段上栽过,几十万条数据全部报废,只能重跑。
前导零丢失。001234写进去变成1234,看着是小问题,但如果下游拿这个当主键,匹配全崩。同样是文本格式的问题,处理方式和上一条一样。
日期变成一串数字。Excel 内部把日期存成序列号,2024-01-01 实际是 45292。如果读的时候拿到的不是 datetime 对象而是个整数,写出去就是 45292 这种鬼东西。openpyxl 一般能正确识别,但如果单元格被设成了"常规"格式,就识别不出来。稳妥的做法是读的时候判一下列名或者值范围,必要时手动转换。
超长文本被截断。Excel 单元格的字符上限是 32767 个,超过就直接丢。注释类字段偶尔会撞上这个限制。切分前扫一遍最长的那几格,心里有个数。
5.2 结构丢失类问题
合并单元格直接报错。前面提过,write_only模式完全不支持合并,普通模式下也得手动重建。撞上这个错误的典型表现是脚本跑到一半抛异常,然后你发现源文件的表头长得像千层饼。
条件格式全部失效。用 pandas 处理的话,条件格式、数据验证、批注、超链接这四样东西一定全丢,因为这个库压根不看这些信息。需要保留就得走 openpyxl 逐格搬运,而且条件格式还得单独用formatting相关的 API 重建,代码量不小。
列宽变成默认值。这是个"不影响功能但很影响观感"的问题。切出来的文件每列都是标准宽度,文字挤在一起或者留出大片空白。补救办法很简单,写完之后遍历一遍所有列,按内容长度的粗略估计设一下:
for col in ws_out.columns: max_len = 0 letter = col[0].column_letter for cell in col[:200]: # 只看前 200 行,够用了 if cell.value is not None: max_len = max(max_len, len(str(cell.value))) ws_out.column_dimensions[letter].width = min(max(max_len * 1.2, 8), 50)打印区域和页眉页脚丢失。这个只有做正式报表交付时才会在意,普通数据处理忽略即可。
5.3 性能与稳定性问题
内存爆掉。前面算过,pandas 读 Excel 的峰值内存大概是文件体积的十几倍。一个 60MB 的表在 8GB 内存的机器上跑,很容易触发换页,慢得像死机。换 openpyxl 的流式模式能解决,代价是代码长一点。
max_row返回 None。只读模式下这个属性并不总是可靠,如果文件生成时没写 dimension 信息,或者被某些工具改过,max_row可能返回一个错误的数甚至直接报错。所以分片数别用max_row算,老老实实边读边计数。
写入时文件被占用。如果输出目录里已经有一个同名文件被打开了(哪怕只是预览窗格开着),保存会抛权限错误。脚本里加个 try 捕获,提示"请关闭已打开的输出文件",比抛一堆堆栈信息友好得多。
磁盘空间不够。切分是"一变多",总体积通常会膨胀 10% 到 30%,因为每个文件都有自己的结构头和共享字符串表。源文件 1GB,输出目录至少留 1.5GB。
5.4 常见问题速查表
把上面这些整理成一张表,出问题的时候按症状查,比翻代码快。
| 症状 | 最可能的原因 | 处理方向 |
|---|---|---|
| 长数字变成科学计数法 | 按数值写入 | 转字符串 + 设文本格式 |
| 前导零消失 | 同上 | 同上 |
| 日期显示为整数 | 单元格格式为常规 | 显式转 datetime 或设日期格式 |
| 分片里出现无关的小计行 | 源表未清洗 | 切分前先过滤非数据行 |
| 公式列全是 None | 文件从未被办公软件打开保存 | 先手工打开保存一次,或让上游改导出方式 |
| 脚本跑到一半报错 | 表头有合并单元格 | 关闭只读写入模式并重建合并区域 |
| 处理速度极慢 | 逐格读样式 | 只保留必要样式,其余放弃 |
| 内存占用持续上涨 | 用了普通读取模式 | 改read_only=True |
| 保存报权限错误 | 输出文件被占用 | 关闭预览窗格或已打开的文件 |
| 每份分片少几行 | 空行被当成数据计入 | 增加全空行过滤 |
6. 交付给别人用:把它做成傻瓜工具
6.1 参数化与配置文件
脚本自己用怎么都好说,一旦要交给同事,参数就得从代码里搬出来。
我一般把配置写成一个独立的文本文件或者命令行参数,最少包含这几项:源文件路径、输出目录、每片行数、表头行数、工作表名称。这样改需求的时候不用碰代码,改一行配置就行。
import argparse parser = argparse.ArgumentParser(description="按行切分 Excel") parser.add_argument("-i", "--input", required=True, help="源文件路径") parser.add_argument("-o", "--output", default="output", help="输出目录") parser.add_argument("-n", "--rows", type=int, default=5000, help="每片数据行数") parser.add_argument("-h-row", "--header-rows", type=int, default=1, help="表头行数") parser.add_argument("-s", "--sheet", default=None, help="工作表名称") args = parser.parse_args()用起来就是一句命令:
python split_excel.py -i 源数据.xlsx -o 输出 -n 4500 -h-row 3比让同事去代码里找ROWS_PER_FILE = 5000这一行改数字,体验好太多了。
再往上一步是做成可视化界面。用 Python 自带的图形库搭个简单的窗口,选文件、填数字、点按钮,二十分钟能写完。这一步的价值不在于技术含量,而在于让不会用命令行的人也能自助操作。我做过一次之后,同组同事再也不来问"能不能帮我切一下"了。
6.2 日志与异常提示怎么写
工具交付出去之后,最花时间的不是写代码,是回答"为什么我这边报错了"。所以日志和错误提示必须写得像人话。
原则一:打进度,别打细节。每一片写完打一行"已生成 xxx_part005.xlsx,数据行数 4500",跑一万行能出几行,不多不少。别打每一行的日志,几十万行输出刷屏,真出问题反而找不到。
原则二:错误信息要带位置。与其打一个KeyError,不如打"读取失败:指定工作表 '明细' 在文件中不存在,当前文件包含的工作表有:明细表、汇总、参数"。第二句能直接让人自己解决,第一句只能来找你。
try: ws = wb[sheet_name] except KeyError: names = "、".join(wb.sheetnames) raise SystemExit(f"未找到工作表 [{sheet_name}],当前文件包含:{names}")原则三:结束给个总结。跑完了打一行"共生成 41 个文件,合计 203847 行,输出目录:xxx"。这一行是用户唯一的确认信号,没有它,用户永远不确定是不是跑完了。
最后是个经验:把日志同时写进文件。窗口一关日志就没了,出问题时用户根本说不清发生了什么。留一份run.log在输出目录里,事后能复盘。
6.3 使用说明文档的模板
给非技术同事的工具,一份一页纸的说明基本是必需品。我通常按这个结构写,不用长,但要能照着做。
第一段写"这个工具干什么":把一个大 Excel 按指定行数拆成多个小文件,每份都保留表头。一句话讲清楚,不讲原理。
第二段写"怎么用",用截图配三步:双击启动、选文件、填数字点开始。每一步配一张图,不写文字也能看懂。
第三段写"结果在哪",明确说明输出文件在源文件同目录下的output文件夹里,命名规则是什么样。
第四段写"常见情况",列三到五条最可能遇到的:文件正被打开会失败、表头行数填错了怎么办、处理大文件需要等一会儿别重复点击。
第五段写"找谁"。留个联系方式,别留一堆技术细节,用户不需要。
这份说明我自己看过好几遍,越写越短。最初写了三页,后来发现没人看,压到一页之后反而天天有人翻。
最后分享两个我一直在用的小技巧。一是切分前先做个"试切",把每片行数设成很小的值(比如 20 行),快速跑一遍看看表头对不对、字段有没有跑偏、格式有没有保留,确认没问题再调回正常参数跑全量。这一步花十几秒,能挡住大部分低级错误。二是给输出文件加一个总览表,把所有分片的文件名、数据行数、首行关键字段值汇总成一张小表放在输出目录里,交付的时候连同这张表一起发出去,对方一眼就能知道每一份装的是什么,不用逐个点开翻。
这两个习惯都是踩坑踩出来的。试切那一条,源于一次参数填错导致 40 个文件全部重跑;总览表那一条,源于被追问了十几次"哪个文件里有某区域的记录"。工具好写,习惯难养,但这两样确实省下了后面大量的沟通成本。