Python 拆分 Excel 保留原格式:表头、合并单元格怎么处理

为什么格式会丢

pandas 的读写链路是「值 → DataFrame → 值」,样式信息根本不在它的处理范围内。而 xlsx 的格式信息(字体、填充、边框、合并区域、列宽、条件格式)存在文件内部的样式表和 sheet 定义里,不是单元格值的一部分。

所以只要走「读数据再写数据」的路线,格式必然丢失。要保留,只能逐单元格复制样式。

最小可用实现(openpyxl)

from copy import copy
from pathlib import Path
import openpyxl


def copy_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.value
    if s.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)


def split_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 = {}
    for r in range(header_rows + 1, ws.max_row + 1):
        key = ws.cell(row=r, column=key_col).value
        if key is None:
            continue
        groups.setdefault(str(key), []).append(r)

    # 2. 每组生成一个新工作簿
    for key, rows in groups.items():
        new_wb = openpyxl.Workbook()
        new_ws = new_wb.active

        # 复制表头
        for r in range(1, header_rows + 1):
            for c in range(1, ws.max_column + 1):
                copy_cell(ws, new_ws, r, c, r)

        # 复制数据行
        for i, r in enumerate(rows, start=header_rows + 1):
            for c in range(1, ws.max_column + 1):
                copy_cell(ws, new_ws, r, c, i)

        # 复制列宽
        for col, dim in ws.column_dimensions.items():
            new_ws.column_dimensions[col].width = dim.width

        # 复制表头区域的合并单元格
        for rng in ws.merged_cells.ranges:
            if rng.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 十几行就够了;但只要涉及多层表头、合并单元格、条件格式,逐单元格复制样式的代码量和维护成本会迅速上升,而且条件格式基本无解。

所以这类需求通常有个分界:

总结

  1. xlsx 格式存在于文件内部样式表,不在单元格值里,所以「读数据」路线必然丢格式
  2. openpyxl 逐单元格复制可以保住字体、填充、边框、列宽、表头合并
  3. 合并单元格需按新行号重算;条件格式 / 图表基本无法随行搬运
  4. 先实测目标表的复杂度,再决定自研还是用现成工具

如果表结构比较复杂(多层表头 + 合并单元格 + 条件格式),我后来改用了一个专门做

「保留格式拆分」的开源工具 ExcelRouter:

GitHub:https://github.com/MarsandSea/ExcelRouter

国内镜像:https://gitee.com/Marsandsea/Excelrouter

文中提到的工具:ExcelRouter

把一个或一整批 Excel 按部门、区域、工号等字段拆成多个文件,完整保留复杂表头与格式, 可再按人二级拆分并打包 ZIP。Windows + 银河麒麟 / 统信 UOS,免费开源(MIT), 全程本机处理,不联网、不上传。

下载(国内 · Gitee) GitHub 源码 ⭐