正确读取含合并单元格和多级表头的excel需用header指定表头行、fillna前向填充,并手动切片定位数据区,清洗列名后写入时用openpyxl重建格式。

读取含合并单元格和多级表头的Excel
直接用 pd.read_excel() 会把合并单元格读成 NaN 或重复填充,多级表头也常错位。关键不是“跳过”,而是主动告诉 Pandas 表头在哪、怎么解析。
- 用
header=[0, 1]指定前两行为多级列名(注意索引从 0 开始) - 合并单元格内容实际只存于左上角单元格,其余为空;需配合
fillna(method='ffill')向下/向右填充逻辑值 - 若表头跨行且不规则(如第 2 行是大类、第 4 行是小类),先用
skiprows跳过无关行,再用header定位有效表头行 - 避免用
index_col=0直接设索引——中文报表常在 A 列放序号或空行,强行设会导致列名错乱
处理“数据区”与“说明区”混排的Sheet
很多财务/统计报表在同一个 Sheet 里塞了表格主体、底部备注、右侧分析栏。Pandas 不会自动识别“哪块是真数据”,必须手动切片。
- 先用
pd.read_excel(..., header=None)原样读入,转成 DataFrame 查看结构 - 用
df.iloc[start_row:end_row, start_col:end_col]精确截取数据区域(常用技巧:找首列出现“合计”或“总计”的行作为结束标记) - 若右侧有固定宽度的说明列(如“备注”“审核人”),可用
df.iloc[:, :n]保留前 n 列,再丢弃全 NaN 行:df.dropna(how='all') - 警惕“隐藏空行”:看似空,实则含空格或不可见字符,用
df.applymap(str).applymap(str.strip)清洗后再判断
中文列名冲突与特殊字符清洗
报表导出时列名常带换行符 \n、全角空格、括号、冒号,导致 df['销售额(万元)'] 报 KeyError,或 groupby 失败。
- 统一清洗列名:
df.columns = df.columns.str.replace(r'[^\w\u4e00-\u9fa5]', '_', regex=True).str.strip('_') - 特别注意 Excel 自动添加的“Unnamed: 0”类列名,不是真的列,而是读取时因索引错位产生的占位符,应优先用
df.drop(columns=['Unnamed: 0'], errors='ignore')清理 - 若列名含年份+括号(如“2023年(预算)”),建议拆成两列:
df[['year', 'type']] = df['year_type'].str.extract(r'(\d{4})年[((](.*?)[))]') - 不要依赖
inplace=True修改列名——部分旧版 Pandas 对 MultiIndex 列名不支持,显式赋值更稳
写回Excel时保留原始格式与合并单元格
to_excel() 默认只写数据,原报表的合并单元格、字体、边框全丢失。若需交付给业务方,必须分步控制输出。
- 用
openpyxl引擎加载模板文件:with pd.ExcelWriter('out.xlsx', engine='openpyxl', mode='a', if_sheet_exists='replace') as writer: - 写入前先获取原 sheet 的合并区域:
writer.sheets['Sheet1'].merge_cells.ranges,再用worksheet.merge_cells.add('A1:B1')手动重建(适合合并规则固定的报表) - 更稳妥的做法:只用 Pandas 写数据,另用 openpyxl 单独处理样式和合并,二者分离——Pandas 负责逻辑,openpyxl 负责呈现
- 写入含中文的日期列时,确保列 dtype 是
datetime64,否则 openpyxl 可能写成数字序列(Excel 底层日期是浮点数)
真正难的不是读或写,而是确认“这一行到底算不算数据”——业务人员填表习惯千差万别,同一张表里可能混用空字符串、空格、None、0 来表示“无”。得先跟业务方对齐语义,再写清洗逻辑,否则代码越改越像补丁。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











