我最早意识到“金融服务”这件事不能全扔给手机里的各种App,是在某个月底准备还信用卡的时候。打开银行App看一眼余额,再翻两个记账软件对一下上个月的开销,发现数字怎么都对不上,好几笔自动扣款我压根没印象。那一刻我就在想:与其让数据散落在十几个账户里,不如自己动手,搭一套能把这些信息汇到一处、还能自动算清楚的服务出来。于是就有了我这套跑在自托管服务器上的“financial-services”项目。
这个项目本质上是一个个人/家庭级的金融信息聚合与自动化处理系统:它把银行卡、信用卡、第三方支付、基金账户的流水统一抓取、清洗、入库,再通过一套规则做账单分类和月度统计,最后用一条净值曲线告诉我每个月底到底攒下了多少钱、钱都花到了哪里。文章适合两类人看:一类是像我一样被各种账户搞到头大的普通用户,想用自托管方案拿回数据控制权;另一类是正在做个人开发项目、想找一套可复用的账户聚合与账单分析设计思路的开发者。下面我按整个项目的拆解、选型、实操和踩坑顺序,把这套方案里最关键的东西讲透。
1. 项目定位与整体设计思路
1.1 为什么需要自己搭一套 financial-services
先交代清楚背景,这套东西不是凭空造出来的。很多人其实已经有记账习惯,手机上装过一堆App,但它们解决的是一个点上的问题,没有把“钱”这件事连起来看。比如你每个月的工资流水在A银行,日常消费走微信和支付宝,基金定投在B平台,信用卡账单又在C银行系统里。结果是:每个平台都只能看到局部数据,想统算总资产、总负债、净资产和现金流变化,就得手动导账单、填Excel、做分类,耗时不说,还经常记错。
自己做一套服务,最大的价值不是“记账”,而是把金融数据当作“数据资产”来管理。它的核心需求有三层:
- 汇总层:把分散在不同账户的流入、流出、余额数据统一到一个数据库里,形成个人金融数据仓库。
- 分析层:通过规则和标签对流水做自动分类,输出月度收支报表、消费趋势、净资产变化。
- 行动层:设置预算阈值和账单提醒,比如信用卡还款日前三天推送提醒、某类消费超预算时告警。
这个思路和很多记账软件的区别在于,记账软件是“用别人的系统记我的账”,信息进了人家的服务器,分类规则和统计口径也由别人定;而自建服务是“用我的系统记我的账”,数据完全可控,规则完全自己定义,想怎么算就怎么算。说白了一点,这是个把金融数据主动权拿回自己手里的方案。
1.2 核心功能拆解与设计取舍
我在第一版设计里没有贪多,只圈定了四个必须跑通的功能模块,因为我坚持一个原则:小系统先跑通闭环,再谈扩展。这四个模块分别是账户接入、数据清洗、账单分析与报表输出。
账户接入是第一道坎,也是最耗费精力的部分。银行和第三方支付平台不会公开提供个人流水查询API,所以现实可行的姿势是两种:一种是利用部分平台提供的官方导出功能,比如网银导CSV、账单PDF;另一种是自建抓取脚本,模拟登录后拿数据。考虑到稳定性和合规风险,我做的是第一种为主,抓取为辅,而且抓取只覆盖那些信息完全公开或自己有完整授权的页面。
数据清洗是整个系统的承接层。不同来源的数据格式差异很大,银行导出的CSV和支付宝的账单表格列名完全不同,日期格式有朱零二八式的差异,金额可能带逗号、带正负号,甚至有的导出来是科学计数法。这一层要做的就是统一字段、统一格式、统一状态机。
账单分析是让数据产生价值的地方。我给每笔流水打标签,比如“餐饮”“交通”“居住”“医疗”“收入”“理财”“还款”,再通过规则引擎和手工纠偏结合的方式自动分类。然后按月、按类别、按账户做聚合,生成报表。这个模块和我的预算控制强相关,后面会详细讲分类规则的坑。
报表输出最后落到一个看板页面上,展示月度收支、资产负债、消费分类占比和净资产曲线。这块不追求花哨,能用就行,数据透明比好看重要得多。
设计取舍上,我最终选择数据入库优先、展示次之的路线。刚开始总是忍不住想先把图表做得漂漂亮亮,但后来发现,只要底层流水库干净了,任何展示形态都能随时改出来;反过来底层一塌糊涂,界面再好看也只是花瓶。
2. 技术选型与架构落地
2.1 自托管方案选择:为什么不用现成App和记账软件
我身边不少朋友问我的第一句话是:你不是在用某某记账软件吗,为什么还要自己造轮子?这个问题挺有代表性,我的回答其实很直接,因为现成方案存在几个我接受不了的问题。
首先是数据主权。免费记账App的收入模式决定了用户数据大概率会被用于用户画像和商业化分析;有些高级功能还要求订阅,等于“我贡献数据还要付费”。自己搭服务之后,数据库在本地的硬盘上,采集、存储、计算都在自己控制范围内,隐私边界清清楚楚。
其次是灵活性。记账软件的分类逻辑是预设死的,比如一笔“美团外卖”默认进餐饮,但对我来说可能有一部分是工作餐、应该算差旅成本;一节线下课程的费用,软件可能放进教育,但我想把它拆成孩子教育和自我提升两类。现成软件改一次标签要手工一条条调,自建系统直接用规则批量处理,效率和准确性不在一个量级。
第三是可扩展性。现成软件的统计口径锁死,“净资产”通常只有资产减负债这一种定义,但我需要的是一个包含流动性资产、投资资产、固定资产、应付债务的多维度视图,甚至可以自定义“可自由支配资金”这种指标。这个需求在现成产品里基本无解,只有自建才能做到。
2.2 整体架构与关键组件选型
我的部署环境是一台家里常开的迷你主机,系统是Debian,所有服务都用容器方式跑。选择容器化不是因为赶时髦,而是因为这套系统要面对“抓数据脚本挂掉、数据库版本升级、定时任务权限隔离”这些状况,容器能让我一键还原一个干净环境,不至于把宿主机折腾坏。在这个基座上,我选了以下几类组件:
- 数据库用 PostgreSQL。金融数据天然是关系型的,账务流水有严格的账户、时间、类别、金额字段约束,用关系型数据库最容易保证一致性。PostgreSQL的数组、JSONB和物化视图能力又为后续打标签和分析提供了便利,一个库就把结构化和半结构化数据都装下了。
- 后端服务用 Python 的 FastAPI。原因很实际:Python在处理数据清洗和写脚本方面生态最好,FastAPI 又带自动生成的API文档,调试账户接入接口时非常方便。另一个方案是 Node.js,但考虑到数据处理代码的量,我还是选了Python。
- 前端看板用的是一个轻量级方案:后端直接用 Jinja2 模板渲染页面,配一点原生JavaScript做交互。没有上前后端分离的框架,因为页面就三四个,上了框架反而要维护构建链。
- 定时任务用系统的 cron 加 Python 脚本。不用 Celery 这类重量级调度器,吃内存又难排查,cron 足够用了。每个任务脚本独立运行、独立打日志、失败重试。
这里有一条选型经验想多说一句:个人项目的技术栈,重点是“你是否hold得住”。我见过有人为了好看把架构搞得很大,组件选到8个以上,最后部署和维护成了噩梦。自托管系统一天没跑通,再漂亮的架构都是零。
2.3 数据表设计与同步策略
账户数据入库之前,一定要先设计好表结构,否则后面每加一个渠道就要改表,关系会越来越乱。我的核心表设计经过几次迭代后稳定成了这样:
- accounts(账户表):记录账户ID、账户类型(银行借记卡、信用卡、第三方支付、投资账户)、账户名称、开户机构、当前余额、币种。
- transactions(流水表):记录每笔交易的流水号、账户ID、交易时间、金额(正负表示收支方向)、交易对手、原始描述、清洗后分类、标签。
- categories(分类表):分类ID、名称、父级分类、类型(收入/支出/转账)。
- sync_logs(同步日志表):记录每次数据接入任务的开始时间、结束时间、成功/失败状态、涉及账户、新增流水数,方便排查。
表之间避免使用过于复杂的关联,流水表保持扁平化,因为查询和分析基本都是围绕这张表展开的。引擎层面,流水表几乎只做插入和查询,更新频率很低,所以把索引集中在时间字段和账户字段上。
同步策略上,我按数据源特点区分了两种方式。银行类账户采用文件导入加定时扫描的被动模式:从网银把CSV下载到固定目录,cron 检测到新文件就触发清洗入库,入库按流水号做去重。第三方支付/投资平台采用 API 拉取的主动模式:如果有官方开放接口就更省事,没有的话就退回到导出文件。主动模式还有个细节是拉取范围,我默认只拉最近30天,防止历史数据被重复清洗,减少服务负载,也能降低出错面。
3. 核心模块实操:账户聚合、资产统计与账单分析
3.1 账户接入与数据抓取的关键细节
账户接入是整个项目中我花时间最多、踩坑也最多的环节。这里先把“读数据”的常规路径做一个梳理,大家可以根据自己的数据源情况对号入座:
- 官方导出(最省心):大多数网银、信用卡App、第三方支付都支持导出Excel或CSV账单。注意核对导出的字段完整性,有的平台默认只导出最近三个月,需要手动选时间范围。
- 银行短信/邮件解析(辅助):有些消费通知会发到邮箱或者短信里,可以写解析规则抽取时间、商户、金额。适合作为补充数据源,但不建议唯一依赖,因为通知往往有摘要,详情不完整。
- 浏览器自动化抓取(谨慎使用):模拟登录网页版账户去提取数据。这个方法维护成本高,且账号安全风险不可控,我最后只保留了一个内部小工具用于处理完全没有导出功能的平台,而且严格限制在本机使用。
接下来说说 CSV 清洗中一个大坑:不同来源的数据格式千奇百怪,不能默认列名就叫“金额”。银行A的导出表里金额带“+”和“-”号,银行B可能用“收入/支出”两个列,还有的用“借/贷”方向;日期字段有的精确到秒,有的只有年月日。我的做法是写一个适配器层,每种数据源对应一个转换函数,输入原始导出文件,输出统一格式的记录列表,再做合法性和去重校验。这个适配器层就像翻译层,把各家的“方言”翻成统一语言再入库。
去重是这里最容易翻车的地方。同一笔消费在银行流水、信用卡账单、支付平台记录里可能各出现一次,如果不做唯一性约束就会重复统计。我的做法是在 transactions 表上加一个业务流水号字段:优先取平台自带的交易号,没有的话就用“账户ID + 交易时间 + 金额绝对值 + 交易对手”四个字段拼一个指纹。指纹生成后建立唯一索引,插入时跳过冲突,这样就保证了聚合分析时不漏笔、不加笔。
3.2 资产统计与净值曲线计算
资产统计是这套 financial-services 最让我有获得感的功能。以前我翻遍各个App也算不出一个“此刻我到底有多少钱”的数,现在打开看板一秒钟就知道了。这个模块的计算逻辑并不复杂,但口径一定要想清楚。
我维护了一个“账户快照”的每日视图,每天定时把所有 accounts 表里的余额抓一遍存进 account_balances 表。这个快照表是净值曲线的基础数据。因为余额每天都在变,尤其是基金账户净值波动,必须按天记录,才能在月末回顾时还原真实轨迹。
净资产的计算分为三层:
- 流动资产:活期存款、货币基金、余额类账户,直接取当前余额。
- 投资资产:股票、基金、定期理财。这里要注意的是,要区分“持仓市值”和“累计投入”。净值曲线应该用市值,而分析投资表现时用累计投入。市值金额我会通过同步脚本从平台拉取,错了会比较难看。
- 负债:信用卡未还账单、贷款余额。负债数据从信用卡账单和贷款账户表里取,用负数表达。
净资产 = 流动资产 + 投资资产 + 负债。这里负债用负号,所以实际是减掉了。有了每日净资产,就能画出曲线,配合移动平均线,能看出一个阶段是攒钱速度在加快还是变慢。我在看板上还额外加了一项“可自由支配资金”,定义为流动资产减去近三个月平均月支出的两倍,作为应急资金安全垫的参考。
计算过程的准确率依赖两点:一是账户余额快照的完整度,缺一天就会导致曲线有断点;二是负债数据的及时更新,特别是信用卡还款后余额会瞬时变化,如果快照正好打在还款日当天,容易造成净资产虚高或虚低。这个问题的解法是快照时间统一固定在每天凌晨,避开大部分还款操作。
3.3 账单分析与分类规则
所有的流水汇入统一库之后,真正的分析才能开始。流水清洗只是让数据“能用”,分类规则才是让数据“懂你”。这个模块我建议做两轮:先用通用规则跑一遍,越简单越好;再基于跑出来的结果不断补充个性化规则。不要一开始就试图做一个AI自动分类,数据量不够的情况下,AI分出来的类别基本都不准。
我的第一轮规则是关键词匹配,但这里有个细节很多人会忽略:分类要用“黑名单”而不是“白名单”。什么意思呢?比如我把“餐饮”类定义为“包含 美团、饿了么、麦当劳、肯德基、瑞幸、星巴克、餐馆、餐饮”,这些关键词覆盖了大部分场景,但一个商家如果叫“老王美食城”而我的规则里只写了“美团”,这笔就会漏掉。如果反过来做黑名单,先排除明显不是餐饮的“房租、转账、还款、基金申购”,再把剩下的匹配进餐饮,覆盖面会大得多。实际操作中我是两者结合:先用白名单做精确匹配,再收集未匹配项人工补充规则,定期复审。
分类的另一个重点是“转账类”和“收支类”的区分。支付宝的余额宝转入、银行卡之间的互转、信用卡还款,这些本质上是资金在自有账户间的移动,不增加也不减少净资产。如果不排除掉,统计收入支出时就会出现“每月收入好几万、支出好几万”的假象。我的处理是给这些流水打上“转账/还款”类型,不进入收支报表,只影响账户余额的变化。
月度报表我输出四个核心数字:总流入、总流出、分类支出占比、月度净结余。前两个由流水聚合而来,第三个按分配好的分类做占比,第四个等于收入减去支出。此外我还加了环比数据,自动对比上个月和上上个月的支出变化,哪个分类涨了百分之几十一目了然,省下了自己翻Excel的功夫。
3.4 多账户联动与对账逻辑
这里单独拎出来讲,是因为“对账”和“汇总”其实是两码事。汇总只是把所有账户的流水加在一起,对账是要验证“数据没漏、没重、没差”。没有对账环节的金融服务,输出结果随时可能悄悄失真。
我做了一个简单但有效的对账逻辑:每个账户每个月末,拿系统里的流水汇总值跟账户真实余额差做一个校验。如果差值超过了阈值(我设的是5块钱),系统给出提醒,我再去核对原始账单。这个方法不能解决所有问题,但能快速暴露数据接入断档、漏同步、分类错误,算是把系统误差压缩在可控范围内。
这套逻辑还有个衍生功能,就是信用卡还款日和账单日的自动提醒。因为信用卡账户的流水和余额都进了系统,我可以算出每个信用卡账户的应还金额和最后还款日,再配合看板页面和个人日历推送,基本上没再出现过最低还款后才想起来还钱的情况。
4. 常见问题与排查实录
4.1 流水对不上:同步漏数据和重复数据怎么处理
这是最常见的问题。有一天我发现账户A的月度总和跟银行App里的数字差了几百块,第一个反应就是同步漏了。排查下来,漏数据的原因通常出在两个地方:一是导出文件的时间范围没有选够,只导了最近30天,但有的消费是在上个月27号入账,属于“跨月流单”;二是导出文件编码问题,银行系统给的文件是GBK编码而服务端默认UTF-8,导致部分中文描述乱码,解析后被当成异常行丢弃了。
解决办法是双管齐下。漏数据方面,每个数据源的同步任务不再简单跑一次就结束,而是把最后的成功时间点记录下来,下次同步时回退两天取数,这个安全边界能覆盖大部分跨月流单和延迟入账。乱码方面,在适配器层统一做编码检测和转换,GBK文件先转成UTF-8再解析,同时把解析失败的行输出到专门的“待人工处理”表,不直接丢弃,事后还能补漏。
重复数据的问题多半来自多源重复,比如一笔信用卡消费,既出现在银行储蓄卡扣款流水里,又出现在信用卡账单里。这种重复靠关键词分类解决不了,得靠我在3.1里提到的业务流水号唯一索引。如果指纹设计合理,同一笔钱不管从哪个源头进来,生成的指纹都一样,数据库约束会自动拦住重复插入。
4.2 自动化脚本经常挂:接口变动与容错设计
如果你也打算做自建抓取,一定要有心理准备:外部接口随时可能变。有一次第三方支付平台把登录后的页面结构调整了,我的抓取脚本直接踩空,所有定时任务连续失败三天我才发现。那之后我做了三件事,算是彻底改善了稳定性。
第一是全面加入“重试与退避”机制。网络请求失败、超时、页面结构变化导致的解析错误,不能一口气重试10次把请求频率推高,不然账号可能被临时风控。我的策略是连续失败3次后停止,等待6小时再试,第二天再失败就直接降级为人工导出导入。
第二是给每次同步都打结构化日志。日志里包含任务名、数据源、运行时长、成功行数、失败行数、异常堆栈、最终状态。以前总觉得打日志麻烦,遇到排查问题时才发现有日志和没日志的效率差别是天壤之别。现在任何一次同步异常,我先看 sync_logs 表就能定位到是网络层问题、解析层问题还是入库层问题。
第三是关键任务做双路冗余。对最重要的数据源,我不单依赖抓取脚本,还保留了解析导出文件的备线路径。一旦自动同步连续失败,系统会自动把状态标记为异常,并在看板页面显示提醒,我看到提醒后手动导一份文件丢到导入目录,任务还能继续跑起来。
4.3 安全与隐私:自托管服务的加固经验
既然是金融数据服务,安全话题绕不开。我的系统跑在家里内网,默认不对公网开放。这个决策很根本:金融数据不上公网,攻击面就小很多。但有些情况下,比如人在外面想看实时净值,就需要一个安全的远程访问方案。
我选择的办法是:在迷你主机上跑一个内网穿透服务,只暴露看板这一个端口,并且设置了比较严格的访问控制。这里我不展开讲具体组件,想分享的是我踩过的几个安全坑。首先是千万不要把数据库端口直接暴露到外网。有人图方便让PostgreSQL监听外网IP,这等于把保险箱放到公共广场上,扫描器扫到后的后果不用多说。其次所有Web页面强制走加密通道,浏览器访问时不允许出现明文连接。再者系统里所有服务用最小权限账号运行,数据库账号不授权删除表和改结构之外的能力,降低被攻破后的爆炸半径。
登录认证部分,我用最基础但也最可靠的方案:强密码加二次验证。哪怕只给一个人用,也坚决不用“123456”这种密码,测试过的人都能理解我为什么这么较真。密码库如果泄露,影响的不只是服务器,而是透过流水数据泄露出来的全部消费习惯和个人生活轨迹,这个代价谁也担不起。
4.4 报表数据“看起来不对”的几个隐藏陷阱
最后再说一种问题,就是数据本身没丢也没重,但报表数字看起来和直觉不符。这类问题最让人头大,因为不是简单补一笔数据能解决的。
我遇到过典型的三个陷阱。第一个是云闪付的消费和银行卡扣款被重复记录,但两个金额一正一负,业务流水号指纹也不一样,因为交易时间可能差一秒。这种就属于“对账平衡但汇总虚高”,我最后的解法是加了一条规则:同一账户同一秒金额相同方向相反的两笔记录自动归为转账类,并从收支汇总中剔除。
第二个陷阱是退款和优惠券的处理。一笔消费100元,随后退款30元,如果退款不算进收入、而是从支出里扣除,才能反映真实消费70元。但如果退款发生在下个月,按“交易时间”统计就会让当月的支出虚高、下月收入虚增。我的方案是统一按“实际扣款时间”归类到支出,退款单独标记为“退款收入”,再在分类报表里合并展示。
第三个陷阱是汇率问题。如果你有外币账户,记账金额必须明确基准货币,否则每月净资产曲线会随着汇率波动出现锯齿状跳变。我目前把所有外币账户统一按当日汇率折成人民币后计入快照,不做实时汇率换算的浮动处理,损失一点实时性,但换来净值曲线的可比性和稳定性。
这些坑在我刚开始跑这套系统时都踩过一遍,也是因为这些经历,最终各个模块沉淀成了我能长期用下去的工具。说白了,搭建 financial-services 这类服务,价值不在程序本身,而在于它把我从每天和账单搏斗的琐碎里解放了出来,让我每个月只需要花十几分钟看一眼看板,就能对家庭的资金状况做到心中有数。从我自己几个月的运行情况看,它已经成了家里最值得留着的自托管服务之一。
最后再分享一个小经验,也是最朴素的忠告:无论你选什么技术栈、用什么架构,第一版一定要控制在“能跑通一条完整链路”的范围。先把一家银行流水从导出到分类到出报表打通,再逐步加账户、加规则、加自动抓取。这个项目最大的乐趣不是堆功能,而是每天看着自己的资金轨迹越来越清晰,那种踏实感,是任何记账App都给不了的。