Python个人财务管理系统:自动解析微信支付宝账单的本地账本 简介这是一份基于Python开发的个人财务管理系统完整项目面向毕业设计、课程设计、Python Web与人工智能应用方向的开发者可用于账目录入、条件查询、分类统计、预算设置及账单提醒等日常财务场景并融入微信/支付宝账单自动解析与AI智能分类能力对个人资金流进行直观跟踪。资源包共30个文件以10个py源码文件为核心覆盖数据库、用户管理、账单处理等模块另含8张功能界面截图、Markdown说明文档、环境配置示例、Docker及Makefile部署脚本等便于快速理解系统结构与二次开发压缩包仅665KB体量精简。目前已有78人学习下载代码入口与模块划分清晰包含app.py、数据库操作、多平台账单处理器、用户认证等并配有工具说明和更新日志可较好支撑理财项目实战、毕业设计参考也可作为学习Python文件处理、数据存储与Web开发的综合样例。1. Python个人财务管理系统是什么能自动吃进微信账单的本地账本拿到这个“Python个人财务管理系统.zip”之前我以为是又一个只能手动录流水的小工具毕竟市面上叫“个人财务管理系统”的课程设计项目太多了。翻完源码才发现真正值钱的不是那套增删改查页面而是wechat_bill_processor.py和alipay_bill_processor.py这两个账单解析器——它们能把微信、支付宝导出的原始 CSV 自动清洗、去重、分类并写入数据库配合bill_classifier.py做关键词规则分类这套“账单文件 → 结构化数据”的链路才是这个项目最值得拆的部分。它适合三类人做 Python 毕业设计或课程设计的学生想把自己微信支付宝流水统一管起来的普通用户以及想搞清楚“CSV 解析、去重、分类入库”这一整套流程的入门开发者。技术栈不重但链路完整从 Docker 部署到用户权限都齐了。下面我按源码结构、启动方式、账单导入的顺序拆给你看照着走一遍就能跑起来。2. 系统拆解入口、数据表与账单导入链路整个系统的核心链路是“账单文件 → 解析器 → database.py → SQLite”app.py 只是把结果用 Web 页面露出来。压缩包里的 tools.md 说明了依赖和工具CHANGELOG.md 记录了版本变更建议先翻这两个文件能少走很多弯路。接下来我按入口、数据层、导入链路三个部分拆开讲。2.1 入口与用户管理app.py 和 addUser.py 怎么配合app.py 是 Web 应用入口负责登录路由、页面渲染和会话管理。它不直接碰数据库而是通过 user_manager.py 和 database.py 拿数据。第一次打开系统会看到登录页普通用户登录后只能录账单、查账单管理员才能跑导入脚本——这种拆法在答辩时很好讲实际用起来也不容易误删数据。新增用户不用去数据库里插记录项目里单独写了 addUser.py。在项目目录下执行python addUser.py --username admin --password yourpassword这个脚本会先检查用户名是否已存在再写入 users.json用于会话校验和数据库中的用户表用于权限校验。注意一个细节用户的写入顺序是“先写库再写 JSON”如果任意一步失败脚本会回滚并打印错误信息不会出现半个用户状态。密码强度只做了最低要求——至少 6 位不限制特殊字符。这里有一个非常容易踩的坑如果你只改了 users.json 而没有同步到数据库登录时一切正常但所有需要写库的操作都会报“用户不存在”。因为 app.py 的权限判定走的是 Flask session而从库里取用户信息是在真正做数据操作时才会调 user_manager.py 的校验函数。我第一次跑的时候就是在 json 里手动加了用户结果录第一笔账就翻车了。用户登录后的会话用 Flask 签名 Cookie 存储默认的 secret_key 需要从环境变量里读。如果 .env 没配Flask 会用开发模式的固定值这在本地练习没问题但一旦部署到公网别人就能伪造会话 Cookie。我一般会在 .env 里加一行SECRET_KEY$(openssl rand -hex 32)生成随机值启动前再 export 一下。这个改动不涉及项目代码但能让整个系统的安全性上一个台阶。2.2 database.py 的建表逻辑与账单类型定义database.py 是整个数据层的核心封装了 SQLite 的连接、建表和增删改查。它没有用 ORM直接写 SQL——好处是依赖少、逻辑透明毕业设计里很好解释坏处是你必须手动保证表结构一致性稍后第四节会讲一个因此踩的坑。我翻了一遍源码主要的表有三张bill 表存流水明细user 表存用户budget 表存预算。bill 表的字段大致是这样字段类型说明idINTEGER PRIMARY KEY AUTOINCREMENT主键bill_typeTEXT支出 / 收入 / 转账amountREAL金额正负由类型决定categoryTEXT分类如餐饮、交通bill_timeTEXT交易发生时间remarkTEXT备注sourceTEXT微信 / 支付宝 / 手动建表过程不需要你手动执行启动 app.py 时 database.py 会检查表是否存在不存在就自动执行 CREATE TABLE。但如果数据库文件是旧版本建的、又缺了新字段它不会自动 ALTER TABLE。升级代码后启动如果报no such column: xxx最简单的处理方式是先备份再重建。可以先看看项目目录下有哪些数据库文件ls -la *.db有历史数据的话先用 sqlite3 工具导出一份备份sqlite3 finance.db .dump finance_backup.sql确认备份没问题后删掉旧 finance.db 重新启动让系统自动建新表。说实话这个“删库重建”的操作我在本地做过好几次每次都先用 dump 备份否则一个月的账单流水说没就没。bill_types.py 是分类体系的定义文件它把 category 和关键词映射绑定在一起。比如“餐饮”关联“外卖|午饭|美团|饿了么”“交通”关联“滴滴|地铁|公交|加油”。bill_classifier.py 会读取这份映射对每条账单的 remark 做关键词匹配匹配不到就归到“其他”。这套分类逻辑没有深度学习含量但它是真能用的——只要你把自己的高频场景词加进去分类准确率能到八成以上。后面第五节我会专门讲怎么把这个规则引擎改造成可自我更新的版本。2.3 微信和支付宝账单导入两条解析链路对比微信和支付宝导出的 CSV 格式完全不一样所以项目里做了两个独立的处理器wechat_bill_processor.py 对应 import_wechat_bills.pyalipay_bill_processor.py 对应 import_alipay_bills.py。两个脚本各自有命令行入口统一通过 Makefile 或 start.sh 调用。微信账单导出的 CSV 开头是这样的前两行是标题和导出时间第三行才是真正的表头“交易时间, 交易类型, 交易对方, 商品, 收/支, 金额, 支付方式, 当前状态, 交易单号, 商户单号, 备注”。所以解析器第一步就是跳过前面两行垃圾数据再按逗号逐行拆分。支付宝账单的导出格式略有不同交易时间字段是2023-01-01 12:00:00金额列里可能带¥符号和千分位逗号两个解析器都做了对应的清洗。手动导入一条账单文件的方式是python import_wechat_bills.py --file 微信账单.csv python import_alipay_bills.py --file 支付宝账单.csv两个脚本都通过--file参数指定路径。如果你不加这个参数脚本会把当前目录下第一张 csv 文件拿来自动导入——这是个隐藏很深的行为我一开始没看源码时以为它会报错结果直接把目录里的测试数据导进去了。判断是否是文件路径时也要注意脚本里用的是os.path.exists相对路径是相对于你执行命令的目录不是项目根目录。所以如果你在项目目录下用python import_wechat_bills.py --file ~/Downloads/wechat.csv这样带绝对路径的命令没问题但如果你在别的目录下执行脚本相对路径就会找不到文件。导入过程中的去重逻辑很关键两个解析器都维护了一份“已导入单号集合”微信用“交易单号”支付宝用“交易号”以这个字段作为唯一键。第二次导入同一个文件时重复的交易会被跳过不会产生重复账目。这意味着你每个月导出一次微信账单、反复导入都不会炸掉。如果你自己二次开发这个去重键建议保留这是整个导入链路里最不能丢的设计。这套导入链路的处理逻辑大致是读文件 → 按行解析 → 清洗金额和时间 → 匹配分类 → 去重 → 批量写入 database.py。理解了这个顺序你就能明白为什么某些账单录入后分类是“其他”——不是解析器坏掉了而是备注里没有命中 bill_types.py 里的关键词。遇到这种情况去改分类映射比改代码快得多。3. 部署与运行本地启动和 Docker Compose 两套方案这个项目给出了两套启动方式裸机跑和 Docker Compose。裸机适合日常开发调试Docker 适合想快速看效果或者部署到服务器上。两套方案用到的都是同一套代码区别只在环境和进程管理层面。3.1 本地裸机运行依赖安装与 start.sh 做了什么本地跑起来需要 Python 3.8 以上版本官方文档里没写具体版本但我在 3.8 到 3.11 上都试过都能正常跑。安装依赖用一个命令解决pip install -r requirements.txtrequirements.txt 里锁定了 Flask、Pandas 等核心依赖。注意这里没有用 requirements-dev.txt 之类的拆分配置所以如果你只是想跑起来不需要额外装 dev 依赖。安装完依赖后最标准的启动方式是执行 start.shbash start.shstart.sh 脚本内部做的事情很简单先检查数据库文件是否存在不存在则让 database.py 自动建表再启动 app.py 对应的 Flask 服务。它会读取 .env 里的环境变量如果 .env 不存在就报警告但继续用默认配置。默认端口是 5000访问http://127.0.0.1:5000就能打开登录页。如果你在 Windows 上跑没有 bash 环境可以手动按顺序执行python database.py --init python app.py第一条命令是初始化数据库第二条是启动 Web 服务。其实 database.py 的 init 逻辑在 app.py 启动时也会跑一遍但显式执行一次能让你提前看到数据库建好没有避免后面导入账单时才报错。在 Linux 下我更推荐用 Makefilemake install # 安装依赖 make run # 启动服务Makefile 把 start.sh 里的步骤包装成了带依赖的 targetmake install会先检查 pip 版本再装 requirements.txtmake run会先确认端口没被占用再启动。如果你要在后台跑可以自己写个 nohup 包装但更省事的方案是直接看下一节的 Docker。3.2 Docker Compose 部署一条命令起来的原理如果你不想污染本地 Python 环境或者想快速让项目在服务器上跑起来用 Docker Compose 是最省事的。项目根目录下有个 docker-compose.yml里面定义了服务名、端口映射和挂载卷。启动命令很简单docker compose up --build -d--build 参数会先构建镜像-d 表示后台运行。镜像构建过程由 Dockerfile 完成Dockerfile 里大致做这几件事基于 python:3.10-slim 基础镜像 → 复制 requirements.txt → 安装依赖 → 复制项目代码 → 声明 5000 端口。注意 Dockerfile 里没有 COPY 数据库文件进去所以数据是挂在宿主机指定目录下的相当于容器只跑代码数据留在外面。docker-compose.yml 里的卷映射通常长这样volumes: - ./data:/app/data这样容器内的 database 文件会写到宿主机的 data 目录。好处是容器删了重建数据还在坏处是你必须把 .env 里的DB_PATH指到/app/data/下否则数据库文件还会落在容器内一旦容器销毁就没了。我第一次用 Docker 跑这个项目时就吃过大亏容器重建后所有账单都不见了后来才意识到是 DB_PATH 没改。你可以先启动容器再用 docker logs 看日志里打的数据库路径docker logs 容器ID | grep -i database如果日志里显示数据库文件路径在容器根目录而不是挂载卷里那就赶紧改 .env 里的 DB_PATH然后再 rebuild。这个操作比事后写一堆 MySQL 迁移脚本简单得多也符合这个项目“轻量”的定位。3.3 环境变量与配置文件.env.example 到底要填什么项目在根目录放了一个 .env.example这是所有环境配置的唯一入口。我把它打开看一眼里面的字段不多但每个都影响运行行为。一份典型的内容是SECRET_KEYrandom_string DATABASE_PATH./data/finance.db PORT5000 DEBUGFalseSECRET_KEY 就是第一节说的 Flask 会话签名密钥别用默认值。DATABASE_PATH 默认是相对路径指向项目目录下的 data 子目录。如果你用 Docker Compose记得改成/app/data/finance.db。PORT 是 Flask 监听端口和 docker-compose.yml 里暴露的端口要对应。DEBUG 在本地调试时可以设成 True部署时一定设成 False否则出错信息会直接怼到页面上。这里有个常见的误解.env.example不是让你直接改它的而是让你复制一份成.env再改。项目代码加载环境变量时只认.env这个文件名.env.example只是模板。如果你直接改.env.example系统不会读它启动时会弹一堆警告。正确做法是cp .env.example .env vim .env改完 .env 后再执行 start.sh 或 docker compose 重启服务。这个“复制模板”的习惯能救你很多次——我见过不止一个同学直接把敏感信息写进 .env.example 然后推到 GitHub隐私全暴露了。.gitignore 里已经把 .env 排除在外所以只要你用的是 .env就能避开这个风险。4. 避坑与排查账单导入失败的五个真实案例账单导入是功能核心但也是问题最密集的地方。我把自己和身边朋友跑这个项目时遇到过的五个典型问题列出来每个都按“现象 → 原因 → 解决”来讲你照着排查能省不少时间。案例一第一行数据导入后分类全是“其他”现象导入微信账单后所有记录的分类都落到了“其他”连“餐饮”这种高频分类都没识别出来。原因微信导出 CSV 是带 BOM 的 UTF-8 格式用open(filename, encodingutf-8)读文件时第一列的表头“微信支付账单明细”前面会多一个不可见的\ufeff字符导致解析器在匹配列名时找不到“备注”这一列备注字段读到的是空值。解决在解析器里加一行line line.lstrip(\ufeff)或者在open时就用encodingutf-8-sig。这个坑非常隐蔽因为报错不一定出现只是数据静默出错。我自己是在看了导入日志后才发现列名对不上又去查了微信支付官方文档才想起来 BOM 的问题。案例二支付宝 CSV 打开后中文全乱码现象在 Windows 系统上直接双击打开支付宝导出的 CSV中文全是乱码。原因支付宝导出文件默认是 GBK 编码而解析器里写的是encodingutf-8读出来的内容乱七八糟。解决导入脚本里要把编码参数换成gbk或gb18030。这个改动只影响支付宝解析器微信解析器保持utf-8-sig不动。如果你是自己写脚本导入历史账单也可以用 pandas 读文件时指定两个编码做 fallback。这个问题的根子是 Windows 和 macOS 产出的文件编码不一致不是项目本身的逻辑错误。案例三金额字段带 ¥ 符号和千分位逗号导入后金额变成 0现象支付宝账单里金额列是¥1,234.56解析器取到后 float() 转换失败默认填了 0。原因清洗逻辑只处理了单个¥符号没处理数字中间的逗号。解决在导入脚本的金额清洗函数里先把¥和空格替换掉再移除所有逗号最后才转 float。代码逻辑大概是这样的# 支付宝金额清洗注意去除千分位逗号 raw_amount row[金额].replace(¥, ).replace(,, ).strip() amount float(raw_amount)这个清洗顺序不能反如果先转 float 再去逗号数值直接翻车。我这个坑是导了三个月账单后发现的环比图表上好几个月的餐饮支出都离奇偏低追查下来才知道是这问题。案例四重复导入同一份账单账单数翻倍现象同一个微信账单文件导入两次数据库里账单数变成两倍。原因第一次导入后微信账单文件被处理完就删除了单号记录或者根本没写入去重表第二次导入时无法识别重复。解决检查 import 脚本里保存单号的逻辑通常是把交易单号写到一张import_log表或 JSON 文件里。确认这张表的数据在导入过程中没有被清空。我在源码里发现项目本身是维护了去重集合的如果你自己改了代码很容易把这段逻辑误删。血泪经验去重标识一定要持久化别放在内存里容器重启后就没有了。案例五Docker 部署后导入账单提示数据库路径不存在现象docker compose up 成功后打开页面导入账单报错sqlite3.OperationalError: unable to open database file。原因容器内没有/app/data这个目录或者 .env 里的 DATABASE_PATH 还是相对路径SQLite 在容器工作目录里创建文件时没有权限。解决在 docker-compose.yml 的 volumes 里明确把宿主机目录挂载到 /app/data并在 .env 里写成/app/data/finance.db。另外在 Dockerfile 里加一行RUN mkdir -p /app/data保证容器启动时目录已存在。这个坑我第二次才躲过去当时一直在宿主机上找数据库文件根本不会意识到容器内和宿主机是两个文件系统。5. 进阶让 bill_classifier 从死关键词变成可自我更新的规则引擎跑通基础功能后体验上最大的瓶颈是分类准确率——bill_types.py 里预置的关键词覆盖不了你个人的真实消费场景。比如你在游戏平台充值备注里是“Steam”默认关键词表里没有所有游戏支出都会被归到“其他”统计图表瞬间失去意义。这里我给你一个我常用的改造思路把关键词规则外置成 JSON 文件然后在导入时把未命中的交易记录到“待分类”表里定期由用户确认一次确认结果自动写回规则库。做法很简单先创建一个 rules.json结构是这样{ 餐饮: [外卖, 美团, 饿了么, 麦当劳, 星巴克], 交通: [滴滴, 地铁, 公交, 加油, 停车], 游戏: [Steam, PlayStation, 任天堂, 网易] }然后改 bill_classifier.py让它启动时读取这个 JSON而不是直接从 bill_types.py 的常量里取。分类逻辑走一遍先精确匹配备注中的关键词匹配不到就返回“其他”并把这条记录的 id 和备注写进一个 calleduncategorized的表里。你在 Web 界面上加一个“确认分类”页面看到未分类记录时下拉框选一个真分类系统把这个备注和分类提取成新规则追加到 rules.json下次再遇到同样的商户名就自动分对了。# 分类器核心逻辑先读规则库再逐条匹配 import json with open(rules.json, r, encodingutf-8) as f: RULES json.load(f) def classify(remark): for category, keywords in RULES.items(): for kw in keywords: if kw.lower() in remark.lower(): return category return 其他这段代码比原来的实现多了两个优点一是规则不用改代码就能加二是关键词匹配时统一转小写避免“steam”和“Steam”不一致导致漏分。配合“未分类确认页”你只需要连续做一周的“事后归类”规则库就能覆盖你 90% 的日常消费场景。每次导入新账单后分类准确率反而越用越高这个体验已经不是毕业设计能给你的了更像一个真正的个人财务工具。从那以后我每次拿到这类“分类靠写死关键词”的项目都会把规则外置这个改造排在第一位——不只因为它好改更因为它让我不必反复去源码里找硬编码。这个项目交付后我自己的账单已经在 Docker 里跑了四个月微信账单每月导一次分类准确率稳定在 90% 左右去重一次没出过问题。希望这个思路也能帮到你。本文还有配套的精品资源点击获取