pandas.read_excel() 统一读取多种Excel格式需按后缀选引擎:.xlsx/.xlsm用openpyxl,.xls须指定engine='xlrd'

用 pandas.read_excel() 统一读取多种格式(.xls、.xlsx、.xlsm)
不同后缀的 Excel 文件本质都是 OLE 或 OOXML 容器,pandas.read_excel() 底层靠 openpyxl(.xlsx/.xlsm)或 xlrd(旧版 .xls)解析,但注意:xlrd 从 2.0 版起**不再支持 .xlsx**,只读 .xls。所以必须按格式选引擎:
- `.xlsx` / `.xlsm` → 默认用
openpyxl(确保已装:pip install openpyxl) - `.xls` → 必须显式指定
engine='xlrd',且只能装xlrd(<code>pip install "xlrd) - 混合目录下最稳写法是先判断后缀,再传
engine参数,避免报错Unsupported format
示例逻辑:
import pandas as pd
import os
<p>def safe_read_excel(path):
ext = os.path.splitext(path)[1].lower()
if ext in ['.xlsx', '.xlsm']:
return pd.read_excel(path, engine='openpyxl')
elif ext == '.xls':
return pd.read_excel(path, engine='xlrd')
else:
raise ValueError(f"Unsupported extension: {ext}")
处理表头不一致、空行、多级标题等脏数据
真实业务 Excel 常有前几行说明、合并单元格标题、空行分隔,直接 pd.read_excel() 会把说明文字当列名或数据。关键不是“跳过几行”,而是动态定位有效表头:
- 用
header=None全部读为字符串,再遍历df.iloc[:, 0]找到第一个含“编号”“姓名”“日期”等业务关键词的行,设为header - 遇到合并单元格导致的
NaN列名,用df.columns = df.columns.fillna(method='ffill')向前填充 - 删除全空行:
df.dropna(how='all', inplace=True);但别急着reset_index(),留着原始索引方便后续 debug - 若多个文件字段顺序不一(如 A 表是
['name', 'age'],B 表是['age', 'name']),统一用df.reindex(columns=standard_cols, fill_value=None)对齐
用 pd.concat() 合并时保留来源文件信息
合并后不知道某行来自哪个文件,排查问题就抓瞎。必须在 concat 前给每份数据加标识列:
立即学习“Python免费学习笔记(深入)”;
- 不要用循环里反复
df['source'] = filename(低效),改用pd.concat(..., keys=filenames)自动加层级索引,或更直白的: -
df.assign(source=os.path.basename(path))—— 简洁、可链式调用、不修改原df - 注意
ignore_index=True会丢弃原始行号,调试时建议先设False,确认无误再开 - 如果某文件列数极少(比如只有 1 列),
concat可能报ValueError: All objects passed were None,需提前过滤空df
写入结果时控制 sheet 名称和格式兼容性
pd.ExcelWriter 默认用 openpyxl 引擎,但写入大文件(>10 万行)易内存溢出。折中方案:
- 用
engine='xlsxwriter'(pip install xlsxwriter),支持流式写入,但不支持读、不支持 .xlsm - sheet 名不能超 31 字符、不能含
/ ? * [ ],用re.sub(r'[\/?*[]]', '_', name)清洗 - 如果要保留原始数值格式(如带千分位的数字、日期),
read_excel时加dtype=str保真,合并后再用pd.to_numeric()或pd.to_datetime()转——别依赖自动 infer,容易把“2023-01”转成1905-08-01
真正麻烦的是跨平台时间格式:Windows Excel 写的日期在 Linux 上读可能变整数(Excel 序列号),务必检查 df.dtypes,对疑似列强制 pd.to_datetime(df[col], unit='D', origin='1899-12-30')


















