一、前言:Excel合并单元格的痛点
在日常办公、数据爬虫、数据分析、报表自动化场景中,Excel合并单元格是最让人头疼的格式问题之一。
常见痛点:
-
合并单元格拆分后数据丢失:openpyxl原生拆分单元格后,只有左上角单元格保留数值,其余单元格为空,导致数据断层、无法用于筛选、透视、匹配数据。
-
样式错乱:拆分/合并后字体、颜色、边框、对齐方式丢失,报表格式崩坏。
-
无法可逆操作:拆分后想重新规整合并,没有通用自动化方案,只能手动操作。
-
批量处理困难:多区域、多行多列合并单元格,人工修改效率极低。
为此,我基于 openpyxl 封装了一套无损、可逆、全覆盖的Excel合并单元格工具:
-
拆分合并单元格:自动填充全部空缺单元格,保留完整样式
-
智能合并单元格:按行/按列自动合并相同值、支持指定区域合并
-
全程保留:字体、填充色、边框、对齐、数字格式
-
拆分+合并双向联动,完美闭环
二、功能核心优势
-
真·无损拆分:拆分后原合并区域所有单元格自动填充左上角原值,解决数据空白问题
-
全样式复刻:逐单元格复制字体、背景填充、边框、对齐、数字格式
-
三种合并模式:按列纵向合并、按行横向合并、自定义区域合并
-
可逆闭环逻辑:拆分全填充 → 合并清冗余,和Excel原生逻辑一致
-
安全不毁源文件:支持覆盖原文件/另存新文件双模式
三、完整工具源码(可直接运行)
依赖安装:
pip install openpyxl
完整工具代码:
from openpyxl import load_workbook
from copy import copy
# ===================== 拆分合并单元格(全区域填充值+保留样式) =====================
def unmerge_merged_cells_fill_all(file_path, sheet_name="Sheet1", output_path=None):
"""
拆分所有合并单元格,拆分后原合并区域全部填充原左上角值,完整保留样式
:param file_path: 输入Excel路径
:param sheet_name: 工作表名称
:param output_path: 输出路径,默认覆盖原文件
:return: 处理后文件路径
"""
# 加载工作簿,保留原格式
wb = load_workbook(file_path)
ws = wb[sheet_name]
# 提前读取所有合并区域(拆分后会丢失合并信息,必须前置读取)
merged_ranges = list(ws.merged_cells.ranges)
if not merged_ranges:
print("未检测到合并单元格,无需处理")
return file_path
# 遍历所有合并区域逐一处理
for m_range in merged_ranges:
min_row = m_range.min_row
max_row = m_range.max_row
min_col = m_range.min_col
max_col = m_range.max_col
# 获取左上角值与样式
top_left_cell = ws.cell(row=min_row, column=min_col)
top_left_value = top_left_cell.value
# 拆分单元格
ws.unmerge_cells(range_string=str(m_range))
# 全区域填充值 + 复刻样式
for row in range(min_row, max_row + 1):
for col in range(min_col, max_col + 1):
current_cell = ws.cell(row=row, column=col)
current_cell.value = top_left_value
# 完整复刻样式
current_cell.font = copy(top_left_cell.font)
current_cell.fill = copy(top_left_cell.fill)
current_cell.border = copy(top_left_cell.border)
current_cell.alignment = copy(top_left_cell.alignment)
current_cell.number_format = top_left_cell.number_format
# 保存文件
output_path = output_path if output_path else file_path
wb.save(output_path)
print(f"✅ 拆分完成!共处理 {len(merged_ranges)} 个合并区域,文件保存至: {output_path}")
return output_path
# ===================== 按列纵向自动合并相同值 =====================
def merge_cells_by_column(file_path, sheet_name="Sheet1", start_row=1, end_row=None, col=None, output_path=None):
"""
单列纵向合并:同一列连续相同值自动合并
"""
wb = load_workbook(file_path)
ws = wb[sheet_name]
max_row = ws.max_row if end_row is None else end_row
merge_blocks = []
current_start = start_row
prev_val = ws.cell(row=start_row, column=col).value
# 扫描连续相同值区块
for r in range(start_row + 1, max_row + 1):
curr_val = ws.cell(row=r, column=col).value
if curr_val != prev_val:
if current_start != r - 1:
merge_blocks.append((current_start, col, r - 1, col))
current_start = r
prev_val = curr_val
# 收尾最后一段
if current_start != max_row:
merge_blocks.append((current_start, col, max_row, col))
# 执行合并+样式同步
for min_r, min_c, max_r, max_c in merge_blocks:
ws.merge_cells(min_row=min_r, min_col=min_c, max_row=max_r, max_col=max_c)
top_cell = ws.cell(min_r, min_c)
for r in range(min_r, max_r + 1):
for c in range(min_c, max_c + 1):
if not (r == min_r and c == min_c):
cell = ws.cell(r, c)
cell.value = None
cell.font = copy(top_cell.font)
cell.fill = copy(top_cell.fill)
cell.border = copy(top_cell.border)
cell.alignment = copy(top_cell.alignment)
cell.number_format = top_cell.number_format
save_path = output_path if output_path else file_path
wb.save(save_path)
print(f"✅ 列合并完成,共 {len(merge_blocks)} 处合并,保存至:{save_path}")
return save_path
# ===================== 按行横向自动合并相同值 =====================
def merge_cells_by_row(file_path, sheet_name="Sheet1", start_col=1, end_col=None, row=None, output_path=None):
"""
单行横向合并:同一行连续相同值自动合并
"""
wb = load_workbook(file_path)
ws = wb[sheet_name]
max_col = ws.max_column if end_col is None else end_col
merge_blocks = []
current_start = start_col
prev_val = ws.cell(row=row, column=start_col).value
for c in range(start_col + 1, max_col + 1):
curr_val = ws.cell(row=row, column=c).value
if curr_val != prev_val:
if current_start != c - 1:
merge_blocks.append((row, current_start, row, c - 1))
current_start = c
prev_val = curr_val
if current_start != max_col:
merge_blocks.append((row, current_start, row, max_col))
for min_r, min_c, max_r, max_c in merge_blocks:
ws.merge_cells(min_row=min_r, min_col=min_c, max_row=max_r, max_col=max_c)
top_cell = ws.cell(min_r, min_c)
for r in range(min_r, max_r + 1):
for c in range(min_c, max_c + 1):
if not (r == min_r and c == min_c):
cell = ws.cell(r, c)
cell.value = None
cell.font = copy(top_cell.font)
cell.fill = copy(top_cell.fill)
cell.border = copy(top_cell.border)
cell.alignment = copy(top_cell.alignment)
cell.number_format = top_cell.number_format
save_path = output_path if output_path else file_path
wb.save(save_path)
print(f"✅ 行合并完成,共 {len(merge_blocks)} 处合并,保存至:{save_path}")
return save_path
# ===================== 自定义区域合并 =====================
def manual_merge_range(file_path, sheet_name="Sheet1", min_r=1, min_c=1, max_r=1, max_c=1, output_path=None):
"""手动指定行列区域合并单元格"""
wb = load_workbook(file_path)
ws = wb[sheet_name]
ws.merge_cells(min_row=min_r, min_col=min_c, max_row=max_r, max_col=max_c)
top_cell = ws.cell(min_r, min_c)
for r in range(min_r, max_r + 1):
for c in range(min_c, max_c + 1):
if not (r == min_r and c == min_c):
cell = ws.cell(r, c)
cell.value = None
cell.font = copy(top_cell.font)
cell.fill = copy(top_cell.fill)
cell.border = copy(top_cell.border)
cell.alignment = copy(top_cell.alignment)
cell.number_format = top_cell.number_format
save_path = output_path if output_path else file_path
wb.save(save_path)
print(f"✅ 区域合并完成!({min_r},{min_c})~({max_r},{max_c})")
return save_path
# ===================== 一键联动:拆分填充 + 重新合并 =====================
if __name__ == "__main__":
# 配置文件路径
input_file = r"C:\Users\Administrator\Desktop\cs11.xlsx"
temp_file = r"C:\Users\Administrator\Desktop\temp_拆分后.xlsx"
final_file = r"C:\Users\Administrator\Desktop\最终规整表格.xlsx"
# 1. 先拆分所有合并单元格,填充空白、保留样式
unmerge_merged_cells_fill_all(input_file, output_path=temp_file)
# 2. 按需重新合并(示例:对第1列纵向合并相同内容)
merge_cells_by_column(temp_file, col=1, start_row=1, output_path=final_file)
# 其他用法可单独调用
# merge_cells_by_row(input_file, row=2)
# manual_merge_range(input_file, min_r=2, min_c=1, max_r=5, max_c=3)
四、核心逻辑解析
1. 拆分单元格核心逻辑
很多人直接使用 ws.unmerge_cells() 会出现数据丢失,原因是:openpyxl 拆分后仅左上角单元格保留值。
本工具解决方案:
-
先批量读取所有合并区域(必须拆分前读取,否则丢失记录)
-
记录每块区域左上角单元格的 值 + 全套样式
-
执行拆分操作
-
遍历区域内所有单元格,批量填充值、复刻样式
最终实现:拆分后数据完整、格式不变,可直接用于数据分析。
2. 合并单元格核心逻辑
模拟人工规整表格逻辑:
-
扫描行/列中连续相同的值,自动识别合并区块
-
执行合并后,仅保留左上角值,其余单元格置空(符合Excel原生合并规则)
-
同步复刻左上角样式,保证表格美观统一
五、常用调用示例
1. 仅拆分填充所有合并单元格
unmerge_merged_cells_fill_all("test.xlsx", output_path="拆分后.xlsx")
2. 按列自动合并相同内容
# 第2列,从第2行开始纵向合并 merge_cells_by_column("拆分后.xlsx", col=2, start_row=2)
3. 按行自动合并相同内容
# 第3行横向合并 merge_cells_by_row("test.xlsx", row=3)
4. 手动指定区域合并
# 2-5行,1-3列区域合并 manual_merge_range("test.xlsx", min_r=2, min_c=1, max_r=5, max_c=3)
六、适用场景总结
-
数据清洗:解决合并单元格导致的pandas读取数据错位、空值问题
-
报表自动化:自动规整Excel表格格式,替代人工合并拆分
-
批量处理文件:海量Excel文件统一标准化格式
-
数据可逆处理:拆分填充数据 → 规整合并,实现表格标准化闭环