必须先定位真实数据起始行,再清洗列名、规范时间金额格式,最后按业务主键合并;需用pd.read_excel(header=none)全量读入,遍历前20行找连续非空列确定skiprows,列名通过模糊匹配映射,时间金额需多策略解析,合并时添加source_file和biz_key确保语义准确。

识别并提取每张Excel中真正的数据起始行
很多业务报表的表头不固定,可能有合并单元格、空行、标题说明等干扰,pd.read_excel 默认从第0行读会错位。必须先定位真实数据区——通常靠检测某列(如“产品名称”“日期”)是否出现连续非空值来判断。
实操建议:
- 用
pd.read_excel(file, header=None)全量读入,避免自动跳过空行 - 遍历前20行,检查是否存在某列(比如第1列)连续3行非空且不含“合计”“单位”等关键词,该行索引即为
skiprows - 若报表格式差异大,可预设多个关键词组合(如
["订单号", "SKU", "商品名称"]),用any(col.str.contains(...).sum() > 2)判断
统一列名:用模糊匹配+规则映射替代硬编码重命名
不同报表的列名常写成“客户姓名”“客户名”“客户全称”,直接 df.columns = ["name", "amount"] 会失败。得让程序自己“认出”哪列该叫什么。
实操建议:
- 对原始列名做清洗:转小写、去空格、删括号和单位(如
re.sub(r"(.*?)|\(.*?\)|[^\w]", "", col.lower())) - 建一个映射字典,键是标准字段名,值是可能的别名正则模式,例如:
{"order_id": r"订单[号|ID]|单号", "amount": r"金额|实收|付款"} - 逐列比对,用
re.search(pattern, cleaned_col)找最匹配项;若多列命中同一标准名,保留数据类型更合理的那列(如含数字的优先于全文本的)
处理时间/金额等关键字段的格式混乱
时间列可能是“2024-03-15”“15/03/2024”“2024年3月”甚至“3月15日”,金额列带“¥”“万元”“,”千分位,pd.to_datetime 和 pd.to_numeric 直接调用大概率报 ValueError。
实操建议:
- 时间列:先用
pd.to_datetime(col, errors="coerce")尝试解析,得到NaT的再走备用逻辑——比如用dateutil.parser.parse逐个试,或正则提取年月日数字后拼接"{year}-{month}-{day}" - 金额列:用
col.astype(str).str.replace(r"[^\d.-]", "", regex=True)清洗后再转数值;若含“万”,需额外识别并乘10000(注意区分“1.5万元”和“15000元”) - 所有清洗操作必须加
errors="coerce",宁可留NaN也不让整列失败
按业务主键合并而非简单 pd.concat
直接 pd.concat(dfs, ignore_index=True) 会把不同报表的“客户A在表1的订单”和“客户A在表2的退货”当两条独立记录,后续分析会失真。必须识别并利用业务主键(如 order_id + date 组合)去重或标记来源。
实操建议:
- 合并前给每张表加来源标识列:
df["source_file"] = os.path.basename(file) - 生成唯一业务键:
df["biz_key"] = df["order_id"].astype(str) + "_" + pd.to_datetime(df["date"]).dt.strftime("%Y%m%d") - 若需去重,用
df.drop_duplicates(subset=["biz_key"], keep="first");若需保留明细,后续可用groupby("biz_key").agg(...)聚合
真正难的不是读取,而是理解每张表里哪些单元格承载了语义——比如合并单元格下的空白行其实继承了上方值,这需要手动触发 df.ffill() 或用 openpyxl 读取原始合并状态。这点容易被忽略,但直接影响对齐准确性。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











