报名名单×签到表核对:谁没来、谁混进来,一个脚本查清

2026-10-07 · 喵窝资源铺

场景:活动收了 10 人报名,当天签到表 9 人。人工拿两张 Excel 对名字,对到眼花还漏一个,事后被问「吴十怎么没来」答不上来。更麻烦的是签到机/手写签到会把名字打错——「周九」变成「周玫」,肉眼核对基本靠缘分。这篇给一个脚本(roster_checkin_diff.py):报名表和签到表一起喂进去,同名匹配 + 手机号兜底 + 形近字模糊匹配三层对齐,输出「报了没来」「没报名却签到」两张名单。代码实跑验证,真实输出见下。

完整脚本

🎁 免费模板:下载报名×签到核对模板(报名表+签到表+说明页,示例行可直接跑通,删掉示例换成你的数据即可)。

先 pip install pandas openpyxl,报名表和签到表各存一份 xlsx(都要有「姓名」列,报名表建议带「手机号」列),运行 python roster_checkin_diff.py 报名表.xlsx 签到表.xlsx 核对结果.xlsx:

# -*- coding: utf-8 -*-
"""报名名单×签到表交叉核对:找出报了名没来/没报名却来了的人。
用法 python roster_checkin_diff.py 报名表.xlsx 签到表.xlsx"""
import sys
import pandas as pd
from difflib import SequenceMatcher

roster_fp, checkin_fp, out_fp = sys.argv[1], sys.argv[2], sys.argv[3]

def norm_name(s):
    return str(s).strip().replace(" ", "")

# 手机号列必须显式 dtype=str,否则 13812345678 会被读成 1.381235e+10
roster = pd.read_excel(roster_fp, dtype={"手机号": str})
checkin = pd.read_excel(checkin_fp, dtype=str)
rmap = {norm_name(n): i for i, n in enumerate(roster["姓名"])}
cmap = {norm_name(n): i for i, n in enumerate(checkin["姓名"])}

# 手机号兜底匹配:名字对不上但手机号对得上,也算同一人
r_phone = {str(p).strip(): n for n, p in zip(roster["姓名"], roster.get("手机号", [""] * len(roster)))}
c_phone = {str(p).strip(): n for n, p in zip(checkin.get("手机号", [""] * len(checkin)), checkin["姓名"])}

matched_pairs = []   # (报名名, 签到名, 方式)
no_show = list(rmap)  # 先假设全没来
used_c = set()
for cn, ci in cmap.items():
    if cn in rmap:
        matched_pairs.append((cn, cn, "同名")); used_c.add(cn); no_show.remove(cn)
for cp, cn in c_phone.items():
    if cn in no_show and cp in r_phone and r_phone[cp] == cn:
        matched_pairs.append((cn, cn, "手机号")); used_c.add(cn); no_show.remove(cn)

# 剩余的做模糊匹配(签到机把名字打错字)
fuzzy = []
for cn in list(cmap):
    if cn in used_c: continue
    best, score = None, 0.0
    for rn in no_show:
        s = SequenceMatcher(None, cn, rn).ratio()
        if s > score: best, score = rn, s
    # 两字名错一个字相似度只有 0.5,阈值必须按名字长度分层,不然形近字全漏
    # 全员到齐 + 有陌生人签到时 no_show 已空、best 为 None,必须先判空再取长度
    thr = 0.5 if best and len(best) <= 2 else 0.75
    if best and score >= thr:
        fuzzy.append((best, cn, f"模糊{score:.2f}")); used_c.add(cn); no_show.remove(best)

walk_in = [cn for cn in cmap if cn not in used_c]  # 没报名却签到的

res = pd.DataFrame(
    [{"姓名": n, "状态": "未签到"} for n in no_show]
  + [{"姓名": n, "状态": "已签到"} for n in cmap if n in used_c]
)
res.to_excel(out_fp, index=False)
print(f"报名 {len(rmap)} 人 / 签到 {len(cmap)} 人 -> {out_fp}")
print(f"对上 {len(matched_pairs)+len(fuzzy)} 人(同名/手机号 {len(matched_pairs)},模糊 {len(fuzzy)})")
for r, c, how in fuzzy: print(f"  模糊匹配: 报名「{r}」≈ 签到「{c}」({how})")
print(f"--- 报了没来 {len(no_show)} 人 ---"); [print(" ", n) for n in no_show]
print(f"--- 没报名却签到 {len(walk_in)} 人 ---"); [print(" ", n) for n in walk_in]

实跑:报名 10 人 / 签到 9 人,1 处错字 + 1 个混进来的全揪出

造了两张表,故意埋了:报名表里「王小明 」带尾随空格、签到表里「周九」被签到机打成「周玫」、2 人报了没来(吴十、钱二)、1 人没报名却签到(刘二狗)。跑一遍的输出:

报名 10 人 / 签到 9 人 -> out_check.xlsx
对上 8 人(同名/手机号 7,模糊 1)
  模糊匹配: 报名「周九」≈ 签到「周玫」(模糊0.50)
--- 报了没来 2 人 ---
  吴十
  钱二
--- 没报名却签到 1 人 ---
  刘二狗

「王小明 」的尾随空格被 norm_name 归一化后正常同名匹配;签到机打错的「周玫」靠模糊匹配抓回来并明确标出(人工确认——周玫和周九是不是同一人要人拍板,脚本只负责把嫌疑对象摆上台面);吴十、钱二精确落在「报了没来」,刘二狗落在「没报名却签到」。核对结果同时写了一份 xlsx(每人一行,标未签到/已签到)。幂等验证:同样的输入重跑,读回来的结果表逐格一致(True)。

两个真坑(都是这轮实跑踩出来的)

模糊匹配的结果只是「嫌疑」不是「结论」:周玫/周九到底是不是同一人,需要拿手机号或现场照片人工确认。脚本负责把可疑名单列出来,别直接拿它当考勤处罚依据。

懒得自己跑?找我们代写
喵窝资源铺提供 Python 脚本代写服务:你说清楚两张表的格式和核对规则,我们写好、用你的真实数据测试通过再交付,24 小时内出活。AI 服务,明码标价,先看效果再付款(虚拟物品不退款,不满意免费重做)。
👉 了解代写服务 | 联系我们

📖 相邻文章:报名表×付款记录对账 · 接龙按选项分组统计票数 · 多轮微信群接龙合并去重 · 单轮接龙统计成Excel · 考勤打卡×排班表交叉核对 · 两张表人名模糊匹配合并 · 博客目录

核对结果仅供参考,最终出勤认定请以现场记录为准。