Python合并多Excel文件时合并标题、边框、自动列宽格式丢失问题
问题根因
三个异常全是代码逻辑错误+引擎混用导致的,和Excel本身无关:
- 标题合并失效:合并范围计算错误,
end_column=len(df.columns)+1多偏移了一列;且后续用xlsxwriter写格式时直接覆盖了openpyxl写入的合并属性。 - 边框消失:混用openpyxl和xlsxwriter两个互不兼容的引擎操作同一个工作表,xlsxwriter写入条件格式时会清空openpyxl之前写入的所有格式;同时xlsxwriter的范围计算用错了索引起点(xlsxwriter行列从0开始计数),范围本身就没覆盖全表。
- 列宽不自适应:openpyxl的
auto_size属性是未实现功能的占位标记,开启后不会做任何实际列宽计算,网上流传的这个写法本身就是无效的。
修复方案
核心原则:操作同一个工作表时全程只用一个Excel引擎,禁止openpyxl/xlsxwriter混用,两种可选实现如下:
方案1:全流程用xlsxwriter实现(格式兼容性最好,推荐)
xlsxwriter对格式、合并单元格、列宽的支持更稳定,不需要写完文件再二次加载修改,所有格式和数据一次性写入:
import pandas as pd import xlsxwriter # --- 此处是你循环读取4个工作簿、合并为DataFrame的原有逻辑 --- # award 为标题文本变量,excelAutoNamed 为输出文件路径 # 初始化写入器时直接指定xlsxwriter引擎,从第2行第2列开始写df(留标题、留行边距) writer = pd.ExcelWriter(excelAutoNamed, engine='xlsxwriter') df.to_excel(writer, sheet_name='Validation', startrow=1, startcol=1, index=False, header=True) wb = writer.book ws = writer.sheets['Validation'] # 预定义所有需要的格式,不要用条件格式加边框(优先级低易被覆盖) title_fmt = wb.add_format({ 'bold': True, 'font_size': 16, 'font_color': 'white', 'bg_color': '0091ea', 'align': 'center', 'valign': 'vcenter', 'border': 2 }) header_fmt = wb.add_format({ 'bold': True, 'align': 'center', 'valign': 'vcenter', 'border': 2 }) cell_fmt = wb.add_format({ 'align': 'center', 'valign': 'vcenter', 'border': 2 }) # 1. 写入合并标题:xlsxwriter索引从0开始,标题在第1行(索引0),列范围和df列数完全对齐,不多算1列 ws.merge_range(0, 1, 0, len(df.columns), award, title_fmt) # 2. 自动适配列宽:手动遍历每列计算最长内容长度,替代无效的auto_size for col_idx, col_name in enumerate(df.columns): actual_col = col_idx + 1 # 因为df从第2列(索引1)开始写入 # 取表头、列内所有单元格的最长字符数,中文乘1.2留余量,限制最小/最大宽度避免显示异常 max_content_len = max(len(str(col_name)), df[col_name].astype(str).map(len).max()) col_width = min(max(max_content_len * 1.2, 8), 50) ws.set_column(actual_col, actual_col, col_width, cell_fmt) # 3. 给表头单独应用加粗格式 for col_idx, col_name in enumerate(df.columns): ws.write(1, col_idx + 1, col_name, header_fmt) writer.close()
方案2:全流程用openpyxl实现(适合需要二次修改已有Excel的场景)
如果必须用openpyxl(比如需要保留原文件内的公式、宏),删掉xlsxwriter相关的所有代码,全程用openpyxl处理格式:
from openpyxl import load_workbook from openpyxl.styles import PatternFill, Font, Alignment, Border, Side from openpyxl.utils import get_column_letter # --- 此处是你循环读取4个工作簿、合并为DataFrame、写入到excelAutoNamed的原有逻辑 --- wb = load_workbook(excelAutoNamed) ws = wb['Validation'] # 定义2磅粗黑边框样式 thick_border = Border( left=Side(border_style='thick', color='000000'), right=Side(border_style='thick', color='000000'), top=Side(border_style='thick', color='000000'), bottom=Side(border_style='thick', color='000000') ) # 1. 修复合并标题:结束列和df列数对齐,不要额外+1 ws.merge_cells(start_row=1, start_column=1, end_row=1, end_column=len(df.columns)) title_cell = ws.cell(row=1, column=1) title_cell.value = award title_cell.fill = PatternFill("solid", fgColor="0091ea") title_cell.font = Font(bold=True, size=16, color="ffffff") title_cell.alignment = Alignment(horizontal="center", vertical="center") title_cell.border = thick_border col_width_map = {} # 2. 遍历所有单元格设置居中、边框,同时统计每列宽度 for row in ws.iter_rows(min_row=1, max_row=ws.max_row, min_col=1, max_col=len(df.columns)): for cell in row: if cell.row != 1: # 跳过已经设置过格式的标题行 cell.alignment = Alignment(horizontal="center", vertical="center") cell.border = thick_border # 统计列内最长内容 cell_len = len(str(cell.value)) if cell.value is not None else 0 col_letter = get_column_letter(cell.column) if col_letter not in col_width_map or cell_len > col_width_map[col_letter]: col_width_map[col_letter] = cell_len # 3. 手动设置列宽 for col_letter, max_len in col_width_map.items(): ws.column_dimensions[col_letter].width = min(max(max_len * 1.2, 8), 50) wb.save(excelAutoNamed)
避坑说明
- 所有Python Excel写入库的格式对象互不兼容,只要混用引擎操作同一个文件,后写入的内容一定会覆盖之前的格式,不存在例外。
- openpyxl的
auto_size属性从库发布至今都没有实现对应逻辑,所有教你开这个参数实现自动列宽的旧教程全是错的。 - 注意不同库的索引基准:openpyxl行列从1开始计数,xlsxwriter行列从0开始计数,计算合并、边框范围时要先对齐基准,否则会出现偏移、漏覆盖的问题。
内容的提问来源于stack exchange,提问作者Poptropica
相关产品推荐
相关产品推荐

