
做 financial-services 这类项目很多人第一反应是银行后台、信贷系统那种庞然大物。我自己做的这个项目恰恰相反它是个很朴素的“账本整理服务”把分散在多家银行、微信、支付宝里的交易流水捞出来清洗成统一结构再映射出“钱到底去哪儿了”、月度现金流趋势和储蓄水平。听起来简单真要做到能长期用、不烂尾中间踩过的坑比想象中多不少。这个项目适合两类人一是想给自己或家庭做财务体检的好奇者二是需要给非财务团队做经营口径数据的初级分析师。它不打算去预测股价也不碰监管合规那套高成本链路核心就一句话——把真实发生的每一笔账变成能连续对比、可复查的数字。接下来的内容我会按自己做这套系统的真实顺序来写先把目标讲清楚再讲数据清洗、科目映射、指标设计最后是几个最典型的翻车现场和解决办法。1. 先把盘子定死financial-services 项目到底要解决什么问题1.1 最核心的痛点不是缺数据而是数据“长不一样”我刚开始做这个项目时最天真的想法是只要把账单全部导出统计不是分分钟的事吗真正动手才发现不同渠道导出的数据根本就不是同一物种。同一笔餐饮消费在 A 银行 App 里叫“快捷支付-商户平台”到了 B 行信用卡账单上可能叫“财付通转账收入”微信支付账单里则是一串“商户单号”加一个看起来像公司名的字段。日期格式也千奇百怪有的是“2024-01-05 08:12:33”有的是“2024/1/4”还有的是 Excel 里被格式化后的序列号。正负号更不统一有的银行支出是负数、收入是正数有的银行恰好反过来。如果只是拿其中一家银行的流水做分析问题不大。但一旦想看清“全家的钱到底怎么流动”这堆格式不齐的数据就会让所有统计口径失真。所以整个项目的第一步不是搞什么高级算法而是先把这些“长得不一样”的流水统一成一张表。我把这一步叫作“七表归一”。数据来源日期字段示例金额方向对手信息特点A 行网银2024-01-05 08:12:33支出为负数常见“支付宝-某某商户”B 行信用卡2024/1/4支出为正数商户名称冗长且带区域码微信支付2024-01-04 21:03:07收入/支出分列商户名与商品备注混在一起支付宝2024年01月04日支出为负数有“交易对方”和“商品说明”两列这张表看着简单却是整个 financial-services 项目的地基。地基不稳后面所有指标都像沙子上盖楼每个月数字都不一样没人敢信。1.2 范围怎么切才能不把自己拖死“账本整理”听起来很小但往深了想可以无限膨胀要不要做预算要不要做投资账户要不要做应收应付我见过不少同类项目最后烂尾就是因为范围越做越大账单、基金、股票、房贷、报销全塞进来个个逻辑不同最后连开发者自己都不想打开。我最后定的边界是三条只处理“活期类资金账户”和“信用类账户”的流水。储蓄卡、信用卡、微信零钱、支付宝余额算一类基金、股票、房贷这种另建独立表不在现金流分析里混算。只做“事后统计”不做“实时预测”。财务健康分析看的是月度趋势对实时性要求没那么高月末对齐一次足够。不做严格复式记账凭证只做“类复式”的科目标记。每一笔流水除了记金额、账户、对手、日期还必须带上一个“科目”方便按用途聚合。这三条边界特别关键。尤其是第一条如果把基金申赎的确认金额当成收入或支出来记账储蓄率会被扭曲得离谱。买入基金只是资产形态从现金变为份额不是“花了钱”卖出基金也不是“挣了钱”只有真正落到口袋里的收益部分才算收入。这个口径上的纠结几乎每个做过个人财务分析的人都会遇到。2. 数据进场的三道坎账单解析、编码统一、字段补全2.1 不同来源入账时的格式血泪史数据进系统的过程我建议设计成一条固定的流水线导出文件 → 检查表头 → 标准格式转换 → 指纹去重 → 入库。这条流水线里最麻烦的不是代码而是“识别不同来源的账单长什么样”。银行导出的数据大概有三类第一类是干干净净的 CSV 或 Excel列名规范直接读就行第二类是带抬头说明、底部有合计行、中间穿插“补登”“冲正”字样的报表第三类是最坑的 PDF内容需要 OCR 或人工复制。我强烈建议如果项目要长期维护优先把导入口子对准 CSV 和 ExcelPDF 只做一次性导入的辅助通道不要花太多精力去做完美解析。原因很简单PDF 排版稍有变化就识别失败维护成本高到离谱。清洗顺序我总结成五步每一步都有明确产物去掉文件前几行的标题、银行名称、导出时间去掉最后几行的“合计”“制表人”。把列名统一成标准命名日期、金额、对方、用途、账户。把日期统一成 ISO 格式“YYYY-MM-DD”把时间戳里的空格和时区差异洗掉。把金额统一为十进制数字去掉千分位逗号、货币符号、全角空格并规定“支出为负、收入为正”。生成唯一指纹作为整行记录的幂等键。这里必须多说一句日期问题。很多人只保留“交易日期”但信用卡账单和部分钱包账单还有“记账日期”。交易日期是用户实际刷卡的时间记账日期是银行清算入账的时间两者可能差一到两天。如果只按交易日期统计月底那几天会凭空多出一批下月才入账的交易如果只按记账日期统计用户会对不上自己到底哪天花的钱。我的做法是两列都保留日常分析默认用交易日期对账时再看记账日期。2.2 幂等导入和指纹去重少一步都会崩溃账单导入最怕的是“重复”。同一个导出文件不小心执行了两次或者同一个账单被分别从手机和电脑导出来系统里就出现两条一模一样的记录。要解决这个不能靠“人工检查”必须靠唯一键去重。金融流水的唯一键我用的是 MD5 指纹。生成指纹时把账户、交易日期、金额、对手名称、用途摘要拼成一段字符串再做哈希。注意千万不要把“导入时间”或“系统自增 ID”放进指纹否则同一笔交易换个时间导入就会生成不同指纹去重就失效了。import hashlib def normalize_amount(v: str) - str: v str(v).replace(,, ).replace(¥, ).strip() if v.endswith(元): v v[:-1] # 统一负数表示把括号、DR 标记转成负号 v v.replace(, -).replace((, -).replace(, ).replace(), ) return v def build_fingerprint(row: dict) - str: raw |.join([ row[account_id], row[trans_date], normalize_amount(row[amount]), row[counterparty].strip().lower(), row[category_hint].strip(), ]) return hashlib.md5(raw.encode(utf-8)).hexdigest()实际运行中同一笔交易在不同平台显示的“对方名称”可能多带几个空格或括号我在生成指纹前都做了 strip 和 lower。有些银行的对手名称还附带“尾号8888”之类我会先做一轮清洗把“尾号”“卡号后四位”这种高变部分剥掉再生成指纹。这样处理之后重复导入基本可以挡在数据库之外。3. 科目映射financial-services 的核心不是炫技而是分类稳定3.1 科目树转账和消费必须分开否则储蓄率永远是错的数据进了库接下来是分类。很多财务类项目会直接拿关键词去套“餐饮”“交通”“购物”但我觉得更重要的是先建一棵“科目树”。科目树就是你能把所有收支归进去的分类体系至少分三层一级科目、二级科目、三级科目。我实际用的是收入工资、经营收入、投资收益、礼金、退款支出餐饮、居住、交通、购物、医疗、教育、娱乐、人情、其他内部转移账户互转、信用卡还款、存现金很多人会忽略“内部转移”这一层于是信用卡还款被重复记为支出。举个例子你用储蓄卡给信用卡还款 3000 元如果系统把储蓄卡的这笔转出记为“支出”又把信用卡上的消费 3000 元也记为“支出”这个月总支出就虚增了 3000。正确做法是信用卡的每一笔消费直接记为对应类别的支出储蓄卡还款动作则记为“内部转移”只在总资产层面做抵消不进收支表。这一条原则定下来之后储蓄率才不会被污染。储蓄率的计算口径很简单但我见过至少五种写法差别非常大。我用的公式是储蓄率 实际到手收入 - 总支出/ 实际到手收入 × 100%这里的“总收入”只包含工资、经营等真实到账的钱“总支出”只包含生活消费类支出。基金收益、退款、报销都不进收入账户互转也不进支出。如果你想更保守可以把“投资定投”也视同支出这样算出来的就是“强制储蓄后的真实可支配空间”。3.2 规则匹配关键词优先级决定分类准确率科目树搭好之后真正的体力活是写“关键词匹配规则”。这些规则要解决的核心问题是看到一条流水如何判断它是餐饮还是购物我的规则结构是一个按优先级排列的列表先匹配先生效。每一条规则有“匹配字段”“关键词或正则表达式”“目标科目”“优先级”。优先级特别高的规则往往是那些含义非常明确的比如“星巴克”“瑞幸”直接归“餐饮-咖啡”“滴滴出行”“高德打车”直接归“交通-打车”。下面是我实际用过的简化版规则表规则匹配关键词目标科目优先级R001美团|饿了么|外卖支出:餐饮:外卖10R002星巴克|瑞幸|库迪支出:餐饮:咖啡10R003滴滴|高德打车|出租车支出:交通:打车9R004京东|淘宝|拼多多支出:购物:电商8R005加油|中国石油|中国石化支出:交通:加油7R006医院|门诊|药房支出:医疗:药品7注意 R004 和 R005 的区别京东上也能买加油卡中国石油也可能有便利店消费。所以不能简单把“加油”放在“京东”之前否则线上买的加油卡被误归为即时加油。我的处理方法是优先看更明确的场景词再看平台词。像“京东-加油卡”这种实测会被 R004 先匹配成“购物”虽然不够完美但总体偏差在可接受范围。还有一类很难处理的是“微信支付-商户单号”。微信账单里的对手名往往不完整但备注里可能有“收款方备注”。我的办法是直接用整条流水拼接字段来匹配把“对方”“商品”“备注”三个字段拼成一个大字符串再跑关键词规则。这样能捞回不少漏网的分类。规则匹配的运行方式我用的是 Python 正则对每一条流水逐条跑规则列表。数据量小的时候没问题几十万行之后就会发现速度慢得难受。优化方法也很简单不要逐条跑所有几百条规则而是先把流水按照“包含常见平台词”做一次粗筛再对粗筛结果跑精细规则。实测下来处理十万条流水的时间从二十分钟降到两分钟。3.3 映射质量自检宁可少归不可乱归规则写得再多也总有漏网之鱼。所以必须做“分类置信度自检”。我每周会抽一批流水人工检查这些流水是否分到了正确的科目并统计一个比例正确分类数除以抽样总数。如果低于 95%说明规则需要补充或调整。具体操作时我一般抽两类样本一类是金额比较大的单笔另一类是系统标注为“其他支出”的未分类项。大额影响统计结果未分类项是规则盲区。把这两块盯住分类质量基本不会失控。还要给自己留一个后门不要直接在原表上改分类结果而是把“人工修正”单独存一张覆盖表。规则映射是自动的人工修正可以覆盖但每次运行流水线时重新映射再用覆盖表覆盖原结果。这样规则更新半年后还可以回看老数据不会出现“旧账分类已经定死新规则只影响新账”的撕裂感。4. 指标与可视化余额只是起点现金流与结构比才是重点4.1 五个指标定下来仪表盘才有骨架数据清晰后选指标反而变成最考验判断力的事。我见过不少同类仪表盘一上来就是一堆折线图、饼图但真正能指导行动的指标其实就五六个。我最终留下的是这五个月度储蓄率衡量结余能力公式上面已经写了。支出结构比把支出按一级科目拆开看“餐饮、居住、购物、娱乐”各自占比。固定支出覆盖月数等于活期总余额除以每月固定支出回答“不工作还能撑几个月”。现金流波动率近三个月总支出的标准差与均值之比用来判断账单稳定性。预算偏离度实际支出比预算多还是少按一级科目分别算。这五个指标里面现金流波动率最容易被忽略。它看着抽象但对做财务体检很有用。如果一个人每个月收入固定支出却忽高忽低说明预算能力偏弱如果波动率低于 15%说明支出比较可控。这个比单纯看余额有用得多。4.2 可视化的建设原则先看一屏摘要再点进去看明细仪表盘不必做得花哨。我见过最实用的是一个“一屏摘要 二级明细页”的结构。主页面只放五个核心指标的数字卡片、一个近六个月的支出趋势柱状图、一个月度支出结构的堆叠条形图。二级页面才是流水明细表允许按科目、账户、日期筛选。技术选型上我没用重型 BI因为数据量不大自己做一个简单的 Web 页面就够了。后端用一个轻量级接口读数据库前端用图表库画图。如果只是自己看甚至可以用现成的开源仪表盘工具关键是底层中间表要做得清晰。我强烈建议不要在前端做聚合计算。所有指标都在 SQL 层先算好前端只负责展示。为什么因为前端如果自己 filter 账单、自己算月度占比过了半年你都不知道当时数字怎么来的。SQL 里写清楚口径所有人都能复查。5. 实操中的六大坑从账实差异到月底对账5.1 典型问题和排查过程这个项目我做了大半年真正让我抓狂的往往不是算法而是数据本身的“反常识”。下面这几个坑几乎每个做财务数据项目的人都会遇到我直接列成一张排查表现象根因解决方式月末余额差异很大交易日期和记账日期错位对账时使用“记账日期”日常分析使用“交易日”同一笔转账出现两次转账在转出和转入账户各记一条指纹规则加入“对方账户尾号”识别后归入内部转移退款导致当月支出为负退款被记为负支出退款单独标记为“冲正”不进支出统计信用卡还款被重复计支出还款与消费同时在流水中出现用科目树隔离“内部转移”类目某银行部分行被归为“其他”商户名称使用简称规则轮不到补充少量明确规则避免盲目加关键词汇率变动导致外币消费差异记账汇率与结算汇率不同保留“原始币种金额”和“记账币种金额”两列这些坑里“同一笔转账出现两次”最隐蔽。比如你把钱从 A 行转给家属的 B 行卡A 行流水有“转账支出”B 行流水有“转账存入”如果系统不认识这个关联你的总资产可能没变收支表却同时多了一笔收入和支出。我在指纹生成前会把对手名称里的“转账”“尾号XXXX”清洗出来结合金额和时间自动给这笔记录打上“内部转账”标记。5.2 对账的正确姿势先对总账再对分类账月底对账时我自己的顺序是先对总账再对分类账。总账就是“本月总收入-本月总支出”是否等于“月末总资产-月初总资产-本月存入资金-外部还款”。如果总账对不平先别急着查分类一定是某个数据源漏了或者日期切错了。分类账对不平最常见的原因是“冲正”和“退款”。要么把退款当成了收入要么把冲正当成了支出。我给的处理方式是把退款独立成一个标记字段统计时既不出现在收入里也不出现在支出里只体现在资产变动明细中等下次对账时专门核对这些专属标记项。这里送大家一个我后来才想明白的技巧每月对账不要等到月底再做而是每个周末简单跑一次“本周收支合计与银行余额快照”的差异。差异如果超过 1%立刻查如果小于 1%留到月底统一处理。别小看这个习惯它让我的月末对账时间从一小时缩到十五分钟。6. 这套 financial-services 体系还能往哪儿延伸项目跑顺之后最让我欣慰的不是某个仪表盘多好看而是这套数据体系能承载更多扩展。当前我用它做了这样几件事月度预算预警当某科目支出超过当月预算 90% 时推送提醒季度自动生成一份“家庭财务简报”只保留核心指标和环比变化还做了一个“年度消费档案”把全年的支出结构压缩成两个页面过年时看看特别有感觉。这些扩展的成本都不高关键还是底层的科目树、指纹和口径足够稳。如果哪天真要接更多账户比如房贷、车贷、保险也只会在科目树上加节点不会动摇已经清洗好的历史数据。从我个人的体会讲做这个 financial-services 项目最大的收获不是“省钱省了多少”而是终于敢拍着胸脯说“我知道钱花在哪了”。以前查月度支出要开三个 App 相互猜现在打开一页仪表盘就能复盘任何一个月。这个过程里踩过的格式坑、规则坑、对账坑回头看都是值得的因为每填一个坑这套账本的可信度就更硬一点。如果你也正准备做类似的数据整理项目建议你先别急着写代码把科目树和边界问题想清楚后面会少走一大半弯路。