2026-10-06 · 喵窝资源铺
场景:组织完聚餐/团建,要在群里收 AA 款。等收完一看聊天记录——「张伟 转账¥25.50」「已付25」「发错了重转」「红包¥8.88」混成一团,人对着屏幕数谁没交,数三遍数出三个结果。这篇给一个纯标准库的 Python 脚本(aa_collect.py):微信聊天记录整段复制进去存成 txt,自动按人对账,--expect 25.5 传入每人应付金额,直接输出「正常 / 少付 / 多付」名单。文中代码实跑验证,真实输出贴在下面。
| 输入形态 | 脚本的处理 |
|---|---|
| 「张伟 转账给你 ¥25.50」 | 姓名 + 金额,正常付款 |
| 「已付 25」「付了 25.5」「30元」 | 金额写法全兼容(¥/元/块/裸数字) |
| 「发错了重转 ¥25.50」 | 覆盖之前那笔,只认重转的(不是累加!) |
| 「补了0.5 转账¥0.50」 | 同人多笔累加,补转也算数 |
| 红包 ¥8.88 | 不计入 AA,单独列出提醒 |
| 系统行(收款到账/退款/收款码/订单号) | 整行剔除,绝不当成某人付款 |
| 「该收每人25.5元」 | 有金额但没付款动词,不算付款 |
| 同一个人转两笔 | 金额累加,最后对账看总数 |
| 输出 | 付款人数 + 合计 + 按 --expect 算差额点名 |
仿微信群记录 14 行:8 人付款 + 1 个红包 + 1 笔系统收款 + 1 笔退款 + 若干干扰:
付款 8 人,合计 ¥233.50 红包(不计入AA): 陈晨 ¥8.88 应付 ¥25.50/人 对账: 正常 5 人: 张伟、李娜、赵敏、孙磊、吴刚 少付 王强: 差 ¥0.50 多付 刘洋: 多 ¥4.50(多退少补或记入下次) 多付 郑雪: 多 ¥25.50(多退少补或记入下次)
逐行核对:王强「已付 25」少 5 毛是对的;刘洋备注「多退少补」转了 30 也对;郑雪替两个人转了 51 对——该进名单的一个不漏,一个不错。系统行、红包、退款全部剔除。
第一版先跑金额正则再取姓名。群昵称「2B青年 转账 ¥25.5」——金额正则从行首扫,先把名字里的 2 当成了金额 ¥2.00,真正的 25.5 反而丢了。
实测抓到后修复:先取姓名(必须含至少一个汉字或字母),金额只在姓名之后的剩余文本里找:
nm = re.match(r"([0-9]*[一-龥A-Za-z][一-龥A-Za-z0-9·'’_]*|...", line) rest = line[nm.end():] # 金额只在姓名之后找 m = MONEY.search(rest)
修复后「2B青年 转账 ¥25.5」正确输出 2B青年: ¥25.50。这个顺序问题在快递单号提取那篇也出现过(EMS 前缀被通用数字正则咬掉)——「先抓结构、再在结构内抓数字」是文本提取的通用铁律。
通知类消息「该收每人25.5元」里也有金额,第一版把它算成了一笔 ¥25.50 的付款,总额虚高。
正解:金额之外还要有付款动词(转账/已付/付了/已转账),没有动词的行不算:
PAY = re.compile(r"转账|已付|付了|已转账|已发")
if kind == "pay" and not PAY.search(rest):
return None # 有金额但不是付款语气 → 不算
张伟先转了 ¥10 发错,重转 ¥25.50。第一版同人累加算成 ¥35.50——多收人 10 块钱,群里直接社死。
这个坑和「补转」是反的:补转(¥25 + ¥0.5)要累加,重发(10 作废 + 25.5)要覆盖。靠文案特征区分:
RESEND = re.compile(r"发错|重转|重发|转错|作废|撤回")
elif RESEND.search(raw):
paid[name] = amount # 发错重发:新值覆盖旧值
else:
paid[name] = paid.get(name, 0.0) + amount # 补转:累加
实测:重发场景输出 ¥25.50(不是 35.50),补转场景「¥25 + ¥0.5」输出 ¥25.50 全对。
「收款到账 ¥25.50」「转账退款 ¥25.50 原路退回」「收款码收款 ¥25.50」这些系统消息都带金额,没有姓名也照算,账目直接乱掉。
正解:系统特征词出现整行剔除,优先级最高:
SYS = re.compile(r"收款到账|收款码|微信支付|订单号|退款|已存入|零钱明细")
实测 4 行系统消息 + 1 行真人付款,只出 1 人 ¥25.50。
「陈晨 抢了你的红包 ¥8.88」被当成了陈晨付的 AA 款——但他明明只是抢了个红包。
正解:红包行单独归类,不计入付款,输出里单独提醒让你自己判断(有时候群主会发红包抵一部分,这是你的决定不是脚本的)。
「张伟 转账给你 ¥25.50 备注:3号桌302」——如果金额正则匹配范围不限制,「302」有机会被卷进来(尤其是 ¥ 符号缺失时)。
修复方式和坑一同源:金额搜索限定在姓名之后的文本、优先匹配带 ¥/元/块特征的数字。实测备注带桌号、房号都正确取到 ¥25.50。
| 输入片段 | 结果 | 说明 |
|---|---|---|
| 2B青年 转账 ¥25.5 | 2B青年 ¥25.50 | 坑一修复后名字数字不干扰 |
| O'Neil 转账给你 ¥25.50 | O'Neil ¥25.50 | 英文名/撇号兼容 |
| 该收每人25.5元 | (排除) | 非付款行 |
| 张伟 发错了重转 ¥25.50 | 覆盖为 ¥25.50 | 坑三:不累加 |
| 周婷 补了0.5 转账¥0.50 | 累加 ¥25.50 | 补转算数 |
| 收款到账 ¥25.50 / 退款 / 收款码 | (排除) | 坑四:系统行 |
| 陈晨 抢了你的红包 ¥8.88 | 红包单独列出 | 坑五 |
| 转账给你 ¥25.50 备注:3号桌302 | ¥25.50 | 坑六:备注数字不干扰 |
first pass: 付款 8 人 合计 ¥233.50 second pass(回灌): 付款 8 人 合计 ¥233.50 identical: True
把脚本认出的 9 行付款消息原样回灌再跑一遍,人数、合计、差额名单完全一致——对账结果可复现,不是碰运气。
python aa_collect.py 聊天记录.txt --expect 25.5 # 不传 --expect 则只汇总每人付款金额,自己心算也行
纯标准库,Python 3.8+ 直接跑。金额口径注意:脚本认最后一次到账的实付金额,收了红包抵扣、线下现金这种聊天记录里看不出来的,还是要人脑补一刀——脚本点名,你来判断。
#!/usr/bin/env python3
"""aa_collect.py — 从微信群聊天记录里批量核对 AA 收款
用法: python aa_collect.py 聊天记录.txt [--expect 25.5]
输出: 终端对账报告(未付/多付/少付名单 + 合计)
规则:
- 只认「人名 + 金额」的付款行;系统行(到账/退款/收款码/订单号)剔除
- 红包不计入 AA 付款,单独列出提醒
- 金额口径:支持 ¥25.50 / 25.5 / 25 / 30元 多种写法
- --expect 传入每人应付金额,自动算差额;不给则只汇总
"""
import re
import sys
from collections import OrderedDict
MONEY = re.compile(r"([0-9]+(?:\.[0-9]{1,2})?)\s*(?:元|块)?")
# 系统行特征:出现即整行剔除,不参与付款判断
SYS = re.compile(r"收款到账|收款码|微信支付|订单号|退款|已存入|零钱明细|转账给\s*\S+\s*¥?0\.01")
HONGBAO = re.compile(r"红包")
# 「发错重发」特征:出现则同人金额取新值覆盖,而不是累加
RESEND = re.compile(r"发错|重转|重发|转错|作废|撤回")
# 付款动词:这行像「某人付了钱」
PAY = re.compile(r"转账|已付|付了|已转账|已发")
def parse_line(line):
"""返回 (姓名, 金额, 类型) 或 None。类型: pay / hongbao"""
line = line.strip()
if not line or SYS.search(line):
return None
# 姓名:必须含至少一个汉字/字母(纯数字开头后面也要落到汉字),防止把「2B青年」的 2 当金额
nm = re.match(r"([0-9]*[\u4e00-\u9fa5A-Za-z][\u4e00-\u9fa5A-Za-z0-9·'’_]*|[A-Za-z][A-Za-z0-9'’ _]{0,14}[A-Za-z])", line)
if not nm:
return None
name = nm.group(1).strip()
rest = line[nm.end():] # 金额只在姓名之后的剩余文本里找,备注里的数字、名字里的数字都咬不到
m = MONEY.search(rest)
if not m:
return None
amount = float(m.group(1))
if amount <= 0:
return None
kind = "hongbao" if HONGBAO.search(line) else "pay"
if kind == "pay" and not PAY.search(line):
return None # 有金额但不是付款语气(比如「该收 25.5」)→ 不算
return (name, amount, kind)
def main():
args = sys.argv[1:]
expect = None
if "--expect" in args:
i = args.index("--expect")
expect = float(args[i + 1])
del args[i:i + 2]
if not args:
sys.exit("用法: python aa_collect.py 聊天记录.txt [--expect 25.5]")
paid = OrderedDict() # name -> amount(同一人多次转账取最后一次,聊天里常见「发错了重发」)
hongbao = {}
for raw in open(args[0], encoding="utf-8"):
got = parse_line(raw)
if not got:
continue
name, amount, kind = got
if kind == "hongbao":
hongbao[name] = amount
elif RESEND.search(raw):
paid[name] = amount # 发错重发:新值覆盖旧值,10块作废只认重转的25.5
else:
paid[name] = paid.get(name, 0.0) + amount # 补转/分两笔:累加
total = sum(paid.values())
print(f"付款 {len(paid)} 人,合计 ¥{total:.2f}")
if hongbao:
print(f"红包(不计入AA): " + ", ".join(f"{n} ¥{a:.2f}" for n, a in hongbao.items()))
if expect is None:
for n, a in paid.items():
print(f" {n}: ¥{a:.2f}")
return
ok, less, more = [], [], []
for n, a in paid.items():
d = round(a - expect, 2)
if abs(d) < 0.01:
ok.append(n)
elif d < 0:
less.append((n, -d))
else:
more.append((n, d))
print(f"\n应付 ¥{expect:.2f}/人 对账:")
print(f" 正常 {len(ok)} 人: {'、'.join(ok) if ok else '—'}")
for n, d in less:
print(f" 少付 {n}: 差 ¥{d:.2f}")
for n, d in more:
print(f" 多付 {n}: 多 ¥{d:.2f}(多退少补或记入下次)")
if __name__ == "__main__":
main()
喵窝资源铺提供代跑脚本服务:你把聊天记录发过来,跑完把对账名单发回你,¥5 起,虚拟物品不退款(跑不通不收费)。脚本本身免费自取。
📖 相邻文章:微信群接龙/报名消息一键变Excel · 聊天记录批量提取快递单号 · Excel 批量提取手机号 · 博客目录
对账结果仅供参考,涉及金额分歧请以微信/支付宝官方账单为准。