夜雨聆风学习资料网

ARTICLE · 1290518

Excel 模板批量填充,保留格式怎么核对

Excel 模板批量填充,保留格式怎么核对

一批检验报告,模板格式定死,明细在另一张表里。脚本五分钟填完八十份,看着特别顺。可交出去之前还得一份份点开:批号是不是这个人的、检验员怎么空着、含量怎么是 98.6 而明细里写的是 99.2。看到第四十份,眼睛就不认字了。

把「填」和「核」写进同一段流程:填的时候每写一格就记一条账(哪份文件、哪个格、什么字段、写的什么值);填完照账本逐份回读比对,只把对不上的和空着的挑出来。用的是 openpyxl 读回、值统一成文本再比,模板自带的公式格排除在账本外。

这篇讲填完之后那一段怎么自查,代码四步走完,示例能直接跑;模板设计和明细行不定的处理,前面几篇讲过了。

一、核的是值,不是格式

格式出错你看得见:边框、列宽打开就知道。看不见的是值。

四类错
靠眼睛怎么发现
靠账本怎么发现
串行:张三的批号填进李四的报告
得回头翻明细,八十份看半天
账上记着「这格该是什么」,一比就出来
被人改过:填完有人动了格
除非你知道他动过,否则发现不了
实际值 ≠ 账上值,直接报
空着没填:明细里本来就没数
容易漏看,也说不清是漏填还是没数
填的时候就没写,标「待补」
前导零被吃:0410 变 410
只有这一列特别长才看出来
写入是文本、回读也是文本,比得出 410

最坑的是第二种:收报告的人改了个数字没告诉你,出了事第一反应是"你脚本有问题"。

二、第一步:把坐标写死,别去猜

报告模板不是数据表:数据表第一行是表头,模板是"标签在这、填入格在那边",中间还夹着合并单元格。

别写通用解析,直接把每个字段落在哪一格列出来。版式定死,坐标就定死;哪天挪了行,改这张表就行。

出处:run.py 顶部的路径与字段常量区

FIELDS = [("报告编号", "B3"), ("批号", "D3"), ("样品名称", "B4"),
          ("检验员", "D4"), ("检验日期", "B5")]
ROW_START = 8                      # 明细第一条落在第 8 行
HEADERS = ["报告编号", "批号", "样品名称", "检验日期", "检验员", "检项", "标准规定", "检验结果"]

HEADERS 把每行按列名变成字典,ROW_START 是明细区起始行。三个常量一改,后面四个函数都不用动。

还有一条:每份都从模板重新 load_workbook。复制上一份再改最省事,但合并单元格、行高、条件格式会跟着上一份的数据走。

三、第二步:读明细,按报告编号归堆

明细一行一个检项,一份报告好几行,按报告编号归堆。

缺报告编号的行不要替它编号——编了号就等于承认它是有效数据,填出来的报告既没人认领也查不到来源。跳过、计数。

出处:run.py 的 read_source()(文件中前部)

    for row in ws.iter_rows(min_row=2, values_only=True):
        rec = dict(zip(HEADERS, row))
        key = norm(rec.get("报告编号"))
        if not key:
            skipped += 1
            continue
        rep = reports.setdefault(key, {"报告编号": key, "批号": rec.get("批号"),
                                       "样品名称": rec.get("样品名称"), "检验员": rec.get("检验员"),
                                       "检验日期": rec.get("检验日期"), "明细": []})
        rep["明细"].append({"检项": rec.get("检项"), "标准规定": rec.get("标准规定"),
                            "检验结果": rec.get("检验结果")})

setdefault 一步做两件事:第一次遇到就建壳子,后面再遇到取出来塞明细。

这里已经在用 norm():判空之前先归一化,否则只有空格的单元格会被当成有效编号。

四、第三步:填的时候顺手记账

整套做法里唯一要多写的几行,也是省下半天的那几行:每写一格就往账本里加一条——文件名、单元格、字段名、值。

出处:run.py 的 fill_one()(文件中前部)

    for field, cell in FIELDS:
        val = rep.get(field)
        if norm(val) == "":
            pending.append((rep["报告编号"], cell, field))   # 空值不写,记一笔待补
            continue
        ws[cell] = val
        ledger.append((rep["报告编号"], cell, field, norm(val)))

空值要单独走一支。别写 ws[cell] = ""——模板里会留个空字符串,回读拿到的就不是"这格没内容"了。空就是没写,另记一列 pending。

明细区循环一样,只是从 ROW_START 往下排,序号自己生成:

出处:run.py 的 fill_one()(同一函数,明细循环段)

    for i, item in enumerate(rep["明细"], start=1):
        r = ROW_START + i - 1
        for col, field in zip("ABCD", ["序号", "检项", "标准规定", "检验结果"]):
            val = i if field == "序号" else item[field]
            ws[f"{col}{r}"] = val
            ledger.append((rep["报告编号"], f"{col}{r}", field, norm(val)))

第 5 列「判定」故意不在循环里。它是模板自带的公式,账本里没有它,回读时就不会把公式报成不一致。

五、第四步:照账本回读,只列异常

核对分两半:一致的只计数,对不上的才进清单——67 条全列出来,等于又让人看一遍。

出处:run.py 的 verify_all()(文件中后部)

        if no not in books:
            books[no] = load_workbook(OUT / "报告" / f"{no}.xlsx")["检验报告"]
        actual = norm(books[no][cell].value)
        if actual == expect:
            stat["一致"] += 1
        else:
            stat["不一致"] += 1
            rows.append([no, cell, field, expect, actual, "不一致"])

books 是个小优化:同一份文件只开一次,否则六十七次读取要开六十几次文件。

清单就三列有用:报告编号、单元格、期望值和实际值。前两列定位,后两列判断。

比对前必须先把值归一

99.2 和 99.20、2026-10-28 和 2026-10-28 00:00:00、410 和 0410——人看着一样,程序看着全不一样。

出处:run.py 的 norm()(文件中前部)

def norm(v):
    """比对前先把值统一成文本:日期、整数、None 各走各的路,否则永远对不上。"""
    if v is None:
        return ""
    if isinstance(v, datetime):
        return v.strftime("%Y-%m-%d")
    if isinstance(v, float) and v == int(v):
        return str(int(v))
    return str(v).strip()

少一条,核对就冒一堆假警报。假警报比不核对更糟——发现清单里一半是误报,后面整张就不看了。

六、四个实测踩到的坑

坑
现象
怎么改的
空值写成空字符串
回读拿到的不是 None,语义变了
空就跳过,另记待补
公式格读成 None
判定列整列报不一致
账本只记自己写的格,公式一概不核
存盘不重算
openpyxl 写完再读,公式格没缓存值
同上,别指望它帮你算
重复跑累加
上一轮的旧报告混进本轮核对
造数据脚本里顺手清空输出目录

第二条多说一句:openpyxl 存盘不会重算公式,data_only=True 读的是文件里上次的缓存值,刚生成的文件压根没有缓存。真要算,得让 Excel 打开一次,或者在 Python 里把判定先算完再写值。

总结

批量填,代码不难;难的是填完怎么敢交出去。把"我填了什么"记成账,核对就从"再读八十份"变成"拿账本比一遍"。

示例里埋了四种边界:前导零批号、空检验员、<0.05 文本结果,还有一个填完被人改掉的格。跑一遍能看到核对把那一个改动挑出来,其余六十六条只报个数。

完整源码

示例(模板、明细、脚本、清单)在这里,可整体下载直接跑:

把下面这一行复制到浏览器地址栏打开(公众号正文不支持直接点外链):

https://gitee.com/huang-jianhua-gitee/examples/tree/main/20261101

关于我

在实验室一线待了 13 年(9 年制药 + 4 年第三方检测),做的一直是实验室信息化。做过某商业 LIMS 产品的甲方 PM——一期、二期两轮上线都由我主导(招标到 3Q 验证到验收全流程);也在那套系统上自己做过二次开发——把纸质的账号申请流程搬到线上跑;在那之前还有 6 年多 CS 架构 LIMS 的使用与运维经验(其中一段经远程桌面接入)。现在专做实验室里那些重复劳动:报表自动生成、仪器数据对接、合规文档批量处理。

本科物理化学、硕士计算机化学,既听得懂 QA 说的变更控制,也看得懂仪器导出的原始数据长什么样。SOP、偏差、OOS、样本流转这些词,不用你解释。

现在主要做这几类:

• 检验报告与台账批量生成:模板不动,数据自动填,格式一步不错

• 仪器数据对接:色谱、光谱、酶标仪导出的原始文件,解析、清洗、入库、转成报表

• 合规文档自动化:SOP、验证方案、批记录这类重复文档的批量生成与核对

• 数据完整性核查:按 ALCOA+ 逐条核对原始数据与记录是否对得上

手里有这类活儿卡着,或者只是想问问能不能自动化,都欢迎评论区聊,先把问题说清楚再谈怎么做。

回首页,搜索你想要的文档

在首页搜索框输入关键词,快速定位资料

文库首页搜索指引 立即搜索

相关学习资料