为什么格式会丢
pandas 的读写链路是「值 → DataFrame → 值」,样式信息根本不在它的处理范围内。而 xlsx 的格式信息(字体、填充、边框、合并区域、列宽、条件格式)存在文件内部的样式表和 sheet 定义里,不是单元格值的一部分。
所以只要走「读数据再写数据」的路线,格式必然丢失。要保留,只能逐单元格复制样式。
最小可用实现(openpyxl)
fromcopyimportcopyfrompathlibimportPathimportopenpyxldefcopy_cell(src,dst,src_r,src_c,dst_r):s=src.cell(row=src_r,column=src_c)d=dst.cell(row=dst_r,column=src_c)d.value=s.valueifs.has_style:d.font=copy(s.font)d.border=copy(s.border)d.fill=copy(s.fill)d.number_format=s.number_format d.protection=copy(s.protection)d.alignment=copy(s.alignment)defsplit_by_column(src_path,key_col,header_rows,out_dir):wb=openpyxl.load_workbook(src_path)ws=wb.active out_dir=Path(out_dir)out_dir.mkdir(parents=True,exist_ok=True)# 1. 按拆分列分组,记录行号groups={}forrinrange(header_rows+1,ws.max_row+1):key=ws.cell(row=r,column=key_col).valueifkeyisNone:continuegroups.setdefault(str(key),[]).append(r)# 2. 每组生成一个新工作簿forkey,rowsingroups.items():new_wb=openpyxl.Workbook()new_ws=new_wb.active# 复制表头forrinrange(1,header_rows+1):forcinrange(1,ws.max_column+1):copy_cell(ws,new_ws,r,c,r)# 复制数据行fori,rinenumerate(rows,start=header_rows+1):forcinrange(1,ws.max_column+1):copy_cell(ws,new_ws,r,c,i)# 复制列宽forcol,diminws.column_dimensions.items():new_ws.column_dimensions[col].width=dim.width# 复制表头区域的合并单元格forrnginws.merged_cells.ranges:ifrng.max_row<=header_rows:new_ws.merge_cells(str(rng))new_wb.save(out_dir/f"{key}.xlsx")几个必须注意的点
1. 合并单元格要做行号偏移
表头里的合并区域可以直接照搬;一旦有跨表头和数据行的合并,重算区间会很麻烦,需要在拆分前就确认表结构。数据行内部如果也有合并,必须按新行号重新计算min_row / max_row。
2. 列宽 / 行高要单独复制
它们不属于单元格,column_dimensions和row_dimensions各自维护。
3. 条件格式、图表、数据验证基本带不走
openpyxl 对这几类的复制支持很有限,实际项目里通常会丢。如果这些是刚需,逐单元格复制这条路会非常难走。
4. 大文件性能
逐单元格复制样式的开销远大于只写值。几万行 × 十几列的表建议先实测一遍耗时。
结论
如果只是「把值拆开」,pandas 十几行就够了;但只要涉及多层表头、合并单元格、条件格式,逐单元格复制样式的代码量和维护成本会迅速上升,而且条件格式基本无解。
所以这类需求现在有三条路,按「表结构复杂度 × 重复频率」选:
| 情况 | 建议 |
|---|---|
| 格式简单、偶尔做一次 | 照着上面的代码自己写,十几行的事 |
| 格式复杂、要长期反复做 | 用专门做「保留格式」的现成工具,别自己维护样式复制逻辑 |
| 格式复杂、且你已经在用 AI 助手 | 装一个拆分技能,直接说「按部门拆开」,脚本在本机跑 |
第三条是这两年才成立的选项,顺带说清它和「让 AI 写段代码我粘过去跑」的区别:技能是装一次长期可用的,AI 知道什么时候该调、参数怎么填,不用每次重新描述需求、重新 review 一遍新生成的代码。对「每个月都要拆一次」这种事,差别很明显。
数据也不经过模型——读表写盘都是本机脚本干的,AI 只看到你的指令和「拆了 29 组」这样的回报。
总结
- xlsx 格式存在于文件内部样式表,不在单元格值里,所以「读数据」路线必然丢格式
- openpyxl 逐单元格复制可以保住字体、填充、边框、列宽、表头合并
- 合并单元格需按新行号重算;条件格式 / 图表基本无法随行搬运
- 先实测目标表的复杂度,再决定自研还是用现成工具
- 除了自研和现成工具,还有第三条路:把拆分封装成 AI 技能,说人话调用,脚本仍在本机跑
表结构复杂(多层表头 + 合并单元格 + 条件格式)的话,可以直接用现成的
我后来改用了一个专门做「保留格式拆分」的开源工具 ExcelRouter(MIT,数据全程本地):
- 桌面版免安装包,点开即下(Windows,约 52MB):ExcelRouter-Windows.zip
- AI 技能版(上文第三条路):skillhub.cn/skills/excelrouter
- 样式复制那部分的源码在
core/splitter.py,可以直接抄:GitHub · Gitee 国内镜像