浏览 AI使用教程
Python 批量处理 Excel:保留原文件并生成差异报告
用 Python 批量清洗 Excel 时,原文件、公式、日期和异常行都要保留证据。输出一份新文件和差异报告,比直接覆盖“整理干净”更重要。
按实际操作顺序推进
每完成一段就检查当前输出,确认结果符合预期后再继续。
表格看起来整齐了,公式和异常日期却一起丢了
日期 01/09/2026 存在日月歧义,订单号 00123 又必须保留前导零。清洗时新增规范列,原始字段和公式不覆盖。
此处给出预期与断言;当前编辑环境未运行 Python/openpyxl。公式保留不等于公式重算,复杂真实工作簿需单独验证支持范围。
- 原文件哈希不变
- 新增规范列而非覆盖原值
- 歧义与未知映射有行号和原因
动手前,把材料和不能改的部分定下来
| 需要确认 | 本例内容 |
|---|---|
| 现有材料 | Python 3、openpyxl 3.1 系列;下方生成包含 orders 与 notes 的练习工作簿,3 行订单、3 个公式,不包含宏、图片、复杂对象。 |
| 不能越过的边界 | 原文件只读,输出新文件;公式与样式变化单列;异常行保留原值、原因和行号。 |
| 要交付的结果 | 新文件、差异报告与异常行 |
Python 批量处理 Excel,从“在新目录建环境”开始做
在新目录建环境
安装 openpyxl,保存并运行 make_fixture.py;不要使用唯一的真实工作簿练习。
确认规则
只接受明确的年/月/日字符串;渠道只映射 wechat→微信,未知值保留。
运行清洗
保存 clean.py,生成新工作簿和逐行 CSV;已有输出时脚本停止。
核对输出
手工打开两个工作表,比较原列、规范列、公式与异常报告。
迁移到真实文件前复核
宏、图形、外链、合并单元格与复杂样式并未被本例覆盖,不能仅凭公式数宣称完整保真。
先生成能对账的输入
终端
在新的练习目录执行
py -m venv .venv .\.venv\Scripts\python.exe -m pip install "openpyxl>=3.1,<3.2" .\.venv\Scripts\python.exe make_fixture.py
make_fixture.py
完整输入工作簿
from openpyxl import Workbook
from pathlib import Path
if Path("orders.xlsx").exists(): raise FileExistsError("不要覆盖已有文件")
wb = Workbook(); ws = wb.active; ws.title = "orders"
ws.append(["order_id","date","channel","amount","double"])
ws.append(["00123","2026/9/1","wechat",100,"=D2*2"])
ws.append(["00124","01/09/2026","微信",200,"=D3*2"])
ws.append(["00125","2026/9/3","unknown",50,"=D4*2"])
wb.create_sheet("notes")["A1"] = "教学数据;不包含宏、图形或外部链接"
wb.save("orders.xlsx")完整清洗程序
clean.py
新增规范列、写差异与检查不变项
from pathlib import Path
from datetime import datetime
import csv, hashlib
from openpyxl import load_workbook
source = Path("orders.xlsx")
before = hashlib.sha256(source.read_bytes()).hexdigest()
wb = load_workbook(source, data_only=False)
ws = wb["orders"]
if [ws.cell(1, c).value for c in range(1, 6)] != ["order_id","date","channel","amount","double"]:
raise ValueError("表头不符合本练习,不猜列")
formula_before = sum(c.data_type == "f" for sheet in wb for row in sheet for c in row)
ws.cell(1,6,"date_normalized"); ws.cell(1,7,"channel_normalized")
diff = []
channels = {"wechat":"微信", "微信":"微信"}
for row in range(2, ws.max_row + 1):
raw_date, raw_channel = ws.cell(row,2).value, ws.cell(row,3).value
normalized, reason = raw_date, ""
try:
if not isinstance(raw_date, str) or raw_date.count("/") != 2 or len(raw_date.split("/")[0]) != 4:
raise ValueError()
normalized = datetime.strptime(raw_date, "%Y/%m/%d").strftime("%Y-%m-%d")
except ValueError:
reason = "日期不是明确的年/月/日,保留原值"
channel = channels.get(raw_channel, raw_channel)
if raw_channel not in channels:
reason += (";" if reason else "") + "渠道未在映射表中,保留原值"
ws.cell(row,6,normalized); ws.cell(row,7,channel)
diff.append([row, ws.cell(row,1).value, raw_date, normalized, raw_channel, channel, reason])
target = Path("orders_cleaned.xlsx")
if target.exists() or Path("orders_diff.csv").exists():
raise FileExistsError("输出已存在;先检查或换一个练习目录")
wb.save(target)
with open("orders_diff.csv","w",encoding="utf-8-sig",newline="") as f:
writer = csv.writer(f)
writer.writerow(["row","order_id","date_old","date_new","channel_old","channel_new","reason"])
writer.writerows(diff)
check = load_workbook(target, data_only=False)
formula_after = sum(c.data_type == "f" for sheet in check for row in sheet for c in row)
assert hashlib.sha256(source.read_bytes()).hexdigest() == before
assert formula_after == formula_before == 3
assert check["orders"]["A2"].value == "00123"
print("原文件哈希未变;公式数量3;差异记录", len(diff))
终端
执行
.\.venv\Scripts\python.exe clean.py
openpyxl 不计算 Excel 公式。本例 data_only=False 保留公式字符串;读取公式缓存与重新计算是另一个问题。支持范围参见 [openpyxl 教程](https://openpyxl.readthedocs.io/en/stable/tutorial.html)。
完成后的新文件、差异报告与异常行
预期输出 orders_cleaned.xlsx 和 orders_diff.csv,3 条逐行记录;第二行日期保留原值并标歧义,第三行渠道 unknown 保留并标异常,订单号仍为字符串00123,公式数量仍为3。
为什么“数据行数一致,工作簿仍可能损坏”还不能交付
数据行数一致,工作簿仍可能损坏
- 原因
- 只比较单元格值,遗漏公式、工作表和格式
- 怎么改
- 检查 sheet、行列、公式计数、关键样式与重开结果
