微信和支付宝账单怎么合并成一本账(附 Python 脚本)

去年年底我想算清楚全年花了多少。微信账单导出来、支付宝账单导出来、银行卡流水导出来,三张表分别求和,加起来一看,数字大得离谱——比我全年的收入还高。

查了两个多小时才找到原因:12 月我用微信还了 3000 块信用卡。这笔钱,在微信账单里记一次「支出」,在银行流水里又记一次「支出」,在信用卡账单里再记一次「消费」。同一笔钱,算了三遍。

这不是我一个人会遇到的问题。账单记录的是资金流动,不是消费。 你想知道的是「我花了多少」,而账单回答的是「钱经过了哪些地方」。这两件事在只有一个账户时几乎相等,一旦有了三份账单,差额能翻倍。

这篇讲怎么把它们合成一本账,并且把重复的那部分剔干净。

一、先搞清楚三份账单的区别

在动手之前,得知道它们的字段结构不是一回事。这不是细节问题——「收支方向」在这三家用了三种不同的表达,不统一就没法合并。

微信支付账单支付宝账单银行流水
获取方式App 内申请,发到邮箱App 内申请,发到邮箱(zip 有密码)网银/手机银行直接导出
文件格式CSV(UTF-8 带 BOM)CSV(GBK / GB18030)CSV 或 Excel,编码各家不同
收支方向单独一列「收/支」= 收入 / 支出 / 中性单独一列「收/支」= 收入 / 支出 / 不计收支没有方向列,靠金额正负
金额写法¥123.45(带货币符号)123.45带引号和逗号,如 "1,234.00"
表头前十几行说明(昵称、时间范围、笔数)几行说明 + 分隔线账号信息若干行
分类交易类型(商户消费、转账、红包…)交易分类(餐饮美食、交通出行…)无

最大的坑在第三行:微信和支付宝用「列」表示方向,银行用「正负号」表示方向。 直接 concat 会把银行的支出当成收入,或者把微信的支出当成字符串无法求和。

所以合并的第一步不是清洗,是统一语言。

二、导出:两家 App 的具体路径

微信:我 → 服务 → 钱包 → 账单 → 右上角「常见问题」→ 下载账单 → 用于个人对账 → 选时间范围 → 填邮箱 → 收到 zip(解压密码在微信里单独发给你)。

支付宝:我的 → 账单 → 右上角「···」→ 开具交易流水证明 → 用于个人对账 → 选时间范围 → 填邮箱 → 收到 zip(密码通过短信发送)。

两家的导出都有延迟(几分钟到一天),且一次最多一年。想算三年,就分三次导。

拿到文件后不要急着写代码,先解压、用 Excel 打开看一眼。每个版本的列名都可能有微调,本文的代码基于常见版本,如果你的表头对不上,照着改列名即可。

三、核心:先剔掉「不是消费的钱」

这是整篇文章最重要的一节。四种情况会让你的支出虚高,而且都是真实发生过的:

图1:一笔 3000 元还款如何被计算三遍

1. 信用卡还款。 用微信或支付宝还信用卡:微信账单记「支出」,信用卡账单记「消费」。同一笔钱两处都在。银行流水如果也导了,还有第三处。

2. 零钱 / 余额提现到银行卡。 微信提现到卡:微信记「支出」,银行流水记「收入」。这一进一出都不该算消费,但会同时抬高你的支出和收入。

3. 余额宝 / 零钱通的转入转出。 从银行卡转进余额宝:支付宝记「不计收支」(新版)或「支出」(旧版),银行记「支出」。这笔钱只是换了个地方放。

4. 退款。 买完退掉的订单,原交易记一笔支出,退款记一笔收入。如果只统计支出,这笔钱就白算了。

判断标准其实只有一句话:这笔钱有没有离开「你自己」这个范围? 没有离开(在卡、零钱、余额宝、信用卡之间移动),就是内部流转,剔除。

# 内部流转的关键词:命中即剔除,不计入消费
INTERNAL = [
    "信用卡还款", "还信用卡", "还款",
    "提现", "充值", "转入", "转出",
    "余额宝", "零钱通", "余利宝",
    "退款", "退货",
]

四、统一成一张表

思路是:给三份账单各自写一个「归一化」函数,输出的表长得一模一样,再拼起来。

目标字段只有五个:date(日期)、amount(金额,正数)、direction(in / out)、desc(交易对方+商品)、src(来源)。

图2:三份账单归一的数据流
import pandas as pd
import re

def read_wechat(path):
    """微信:收/支列是「收入」「支出」或「/」"""
    df = pd.read_csv(path, encoding="utf-8-sig", skiprows=16)
    df = df.rename(columns={
        "交易时间": "date", "交易对方": "party", "商品": "item",
        "收/支": "dir", "金额(元)": "amt", "当前状态": "status",
    })
    return df

def read_alipay(path):
    """支付宝:编码多是 GBK;表头前说明行数不固定"""
    df = pd.read_csv(path, encoding="gbk", skiprows=4)
    df = df.rename(columns={
        "交易时间": "date", "交易对方": "party", "商品说明": "item",
        "收/支": "dir", "金额": "amt", "交易状态": "status",
    })
    return df

def read_bank(path):
    """银行:没有方向列,靠金额正负"""
    df = pd.read_csv(path, encoding="gbk", skiprows=2)
    df = df.rename(columns={"交易日期": "date", "摘要": "party","金额": "amt"})
    df["dir"] = df["amt"].apply(lambda v: "收入" if v > 0 else "支出")
    df["item"] = ""
    return df

注意 skiprows 这三处:说明行数会随 App 版本变化。如果读出来的表头是乱码或变成 Unnamed: 0,把 skiprows 加减一两行再试,这是最高频的报错来源。

然后是统一的清洗与归一:

def normalize(df, src):
    df = df.copy()

    # 1) 日期
    df["date"] = pd.to_datetime(df["date"], errors="coerce")
    df = df.dropna(subset=["date"])

    # 2) 金额:去掉 ¥、逗号、引号、空格,转成数字并取绝对值
    df["amt"] = (df["amt"].astype(str)
                 .str.replace(r"[¥,\"\s]", "", regex=True))
    df["amt"] = pd.to_numeric(df["amt"], errors="coerce").abs()
    df = df.dropna(subset=["amt"])

    # 3) 方向:中文统一成 in / out;中性的直接丢
    df["direction"] = df["dir"].map(
        lambda v: "out" if "支出" in str(v) else ("in" if "收入" in str(v) else None)
    )
    df = df.dropna(subset=["direction"])

    # 4) 描述
    df["desc"] = (df["party"].fillna("") + " " + df["item"].fillna("")).str.strip()

    df["src"] = src
    return df[["date", "amt", "direction", "desc", "src"]]

df = pd.concat([
    normalize(read_wechat("wechat.csv"), "微信"),
    normalize(read_alipay("alipay.csv"), "支付宝"),
    normalize(read_bank("bank.csv"), "银行"),
], ignore_index=True)

# 跨来源去重(同一笔交易可能在两份账单里都出现)
df = df.drop_duplicates(subset=["date", "amt", "direction"]).sort_values("date")

最后一步去重很关键:按「同一天 + 同金额 + 同方向」判重。这会有极小的误杀概率(同一天买了两次一样的 15 块咖啡),但比起让提现被算成两次消费,这个代价划算得多。如果你介意,可以把 desc 也加进去重条件。

五、剔掉内部流转,再分类汇总

INTERNAL_RE = "|".join(INTERNAL)

consume = df[(df["direction"] == "out") & (~df["desc"].str.contains(INTERNAL_RE, na=False))]

RULES = {
    "餐饮": "美团|饿了么|餐饮|饭店|餐厅|咖啡|瑞幸|星巴克|麦当劳|肯德基|外卖",
    "交通": "地铁|公交|滴滴|打车|加油|12306|铁路|停车|共享单车|高德",
    "购物": "淘宝|天猫|京东|拼多多|超市|便利店|商场|旗舰店",
    "居住": "房租|租金|物业|水电|燃气|宽带",
    "通讯": "话费|流量|中国移动|中国联通|中国电信",
}

def tag(df):
    df = df.copy()
    df["cat"] = "其他"
    for cat, pat in RULES.items():
        hit = df["desc"].str.contains(pat, regex=True, na=False)
        df.loc[hit, "cat"] = cat
    return df

consume = tag(consume)
print(consume.groupby("cat")["amt"].sum().sort_values(ascending=False).round(2))

支付宝的分类其实可以省事——它的「交易分类」列本身就有「餐饮美食」「交通出行」这种标签,直接拿来用比关键词匹配准得多。如果你的目标是月度复盘,用支付宝自带的分类,微信和银行才走关键词规则,这是成本最低的做法。

图3:剔除重复计算前后的月度支出

图 3 是同一份数据剔除前后的对比。左边是「把三份账单加总」的结果,右边是剔除内部流转后的真实消费。差了将近一倍——这个差距就是那些「钱只是换了个地方放」的交易。

六、不想写代码:Excel 的做法

如果你只是想算一次年度总数,不值得为它装 Python。Excel 的 Power Query 能做同样的事,只是没有代码那么省事:

1. 三份 CSV 分别用 数据 → 从文本/CSV 导入,每次都用「跳过行数」把说明行去掉;

2. 导入后手动加一列「来源」,填上微信/支付宝/银行;

3. 数据 → 获取数据 → 合并查询 → 追加,把三张表纵向拼成一张;

4. 支出统计用 SUMIFS,条件写成 收/支="支出" 且 描述<>"*还款*"。

差别在哪?Power Query 的步骤会被记住,下次导入新文件点一次「全部刷新」就行——这一点它和脚本一样省事。真正的差别是:脚本能写成通用的(换个 App 版本只改一行列名),Power Query 每换一种文件都要重新点一遍。

七、五个一定会踩的坑

坑现象解法
skiprows 写错读出来表头是 Unnamed: 0,或全是 NaN先用 Excel 打开数清楚表头在第几行,再改数字
支付宝 CSV 编码UnicodeDecodeError用 encoding="gbk",不行再试 "gb18030"
金额带全角符号¥1,234.00 转数字失败正则里把 ¥、,(全角逗号)一起替换掉
转账给朋友算成了消费人情往来看起来像餐饮转账类单独标一类「人情」,别混进生活支出
亲情卡 / 代付同一笔消费在两个人的账单里各记一次卡主账单里那笔「亲情卡」付款要剔除,否则重复

最后一条值得多说:如果你的账单里有「亲属卡」「亲情卡」的付款记录,那笔钱实际是对方在花,从你的消费里剔除、单独记「代付」,才能反映真实的家庭开销。这类交易在摘要里通常带「亲情卡」字样,加进 INTERNAL 的关键词表就行。

另一个容易忽略的:微信红包。发出红包在账单里是「支出」,收红包是「收入」。过年期间这一进一出很容易把月度数据搞乱。建议红包、转账统一归到「人情往来」,跟生活消费分开看——它属于社交支出,跟「今天吃了什么」不是一回事。

八、这套方法的边界

适合:算清楚一年的真实消费、找出被忽略的订阅和续费、给家庭预算做基数(下一步可以看《家庭年度预算怎么做》)。

不适合:实时记账(账单有 T+1 延迟,App 里的实时数据更准)、多币种(汇率换算这套不处理)、投资盈亏(券商报表比这里准得多)。

还有一个必须说的:这些文件里包含你的真实姓名、手机号、交易对象和消费习惯。 脚本全程只在你自己电脑上跑,不联网、不上传——这一点比任何「拍照识别账单」的 App 都安全。处理完之后,建议把中间生成的 Excel 也删掉,只留汇总结果。

相关文章

· 用 Python 整理银行流水:从一堆乱数据到月度报表

· 新手记账入门:用 Excel 做一张自动汇总的记账表

· Google Sheets 记账模板搭建教程:比 Excel 更适合手机记账

· 家庭年度预算怎么做:一份可直接套用的表格思路

说明:本文只介绍数据处理方法,不涉及任何记账软件或理财产品的推荐。

Comments

Popular posts from this blog

指数基金定投是什么:写给完全没接触过的人

手把手教你用 Google Blogger 免费搭建博客(2026 版)

RSS 还能用吗:2026 年用免费工具搭建自己的信息流