用OpenAI API和Python打造自动记账工具:让大模型理解你的每一笔消费
2026/9/20 20:46:01 网站建设 项目流程

简介:这是一个基于OpenAI的自动记账工具Python实现,面向希望用自然语言处理能力简化财务记录的开发者和财务自动化爱好者。项目核心是利用OpenAI接口解析账单、发票等文本,自动抽取日期、金额、商品名称等信息,并以结构化数据输出,便于生成财务报告和后续分析,从而减少人工录入误差。压缩包共51个文件,约37KB,以36个Python脚本为主,覆盖主程序、工具模块、模型接口、错误处理与测试用例;同时配有yaml/yml配置、pytest.ini、Makefile、requirements.txt和README,便于环境搭建与二次开发。目录按utils、routers、models、tests等模块划分,并提供prompts提示词配置、task.yaml和bill.yaml示例,以及devcontainer开发容器配置,方便直接运行和改造。已有230人学习下载,适合快速搭建智能记账原型,也可作为OpenAI与Python整合实践的学习参考。 先说一个我自己的真实状态:记账这件事,我从大学断断续续记到现在,最长记录坚持了大概三周,最短的一次当天就放弃了。不是不想记,是真的烦——每笔都要手动选分类、输入金额、填备注,稍微忙两天就断档,断了就更不想补。后来我索性换了个思路:既然我每天都在跟各种消费消息打交道,不如让 OpenAI 帮我记,用 Python 写一个自动记账工具,把“人记账”变成“机器理解账单”。

这篇文章就把我这个项目完整拆开讲。核心是一个用 Python 调用 OpenAI API 的自动记账流程:它读取微信/支付宝导出的账单文本或我随手发的消费记录,提取时间、金额、商户、商品分类,然后自动写入本地 SQLite 数据库,附带月度统计和分类汇总。适合对 Python 有一定基础、想用大模型解决实际生活问题的人参考,也适合想了解 OpenAI API 实战用法、函数调用、JSON Mode 怎么落地的同学。我会把提示词设计、分类体系、去重逻辑、异常处理这些关键点都写清楚。

1. 为什么记账工具要交给大模型做

1.1 手动记账和关键字规则的死穴

传统记账工具的无非两种:一种是人肉记,每笔都自己填;另一种是规则引擎,靠关键词匹配自动分类,比如看到“美团”就归餐饮,看到“滴滴”就归交通。

这两种方案我都试过。人肉记的问题上面说了,坚持不下去。规则引擎的问题更隐蔽:它只能处理你预先定义好的情况。我去年在美团买过一张电影票,被分到了餐饮;在滴滴上叫过一辆拉货的面包车,被分到了交通。这种错一次两次还能手动改,十次八次之后你就开始怀疑这个工具到底有没有在帮你。更别说那些商户名和实际消费内容完全对不上的情况,比如在便利店买日用品,商户名却是“某某超市”,规则引擎根本无从判断。

这里的关键不是规则写得不好,而是现实世界的消费文本太随意、太碎片化。大模型恰恰擅长处理这种“不标准的自然语言”,它能结合上下文推断出这条记录到底在讲什么。这就是我决定用 OpenAI 来做自动记账的核心理由——把“需要理解”的部分交给大模型,把“必须精确”的部分留给代码。

1.2 一条记账数据的诞生链路

我的设计思路是这样的:整个工具本质上是一个转换器,输入是任意的消费文本,输出是结构化的账单记录。中间所有环节都用 Python 串联起来。

具体链路是:账单文本(手动输入 / 文件导入) → Prompt 组装 → 调用 OpenAI Chat Completions API → 返回结构化 JSON → Python 解析校验 → 写入 SQLite → 生成月度报表。

这么设计的核心原则是:大模型只负责理解语义、抽取信息,不负责算数和存储。比如“昨天中午和张三吃饭AA,每人85”这句话,模型负责识别出这是一笔餐饮支出、金额85元、日期是昨天;而“85 是否等于总数的一半”“这笔要不要写入数据库”“数据库里有没有重复记录”这些必须精确的逻辑,全部由 Python 代码处理。人机分工清楚了,工具才能稳定。

这个链路里最关键、也最影响效果的就是 Prompt 设计,下面重点展开。

2. 提示词设计:整个项目的灵魂

2.1 让模型按固定枚举分类

刚开始我把分类开放给模型自由发挥,结果它一周内给了我四十多种分类:今天叫“餐饮”,明天叫“吃饭”,后天变成“美食”。分类维度不一致,月底统计数据根本没法看。

后来我学乖了:先定义一套固定的分类枚举,让模型只能从这些枚举里选。我现在的分类体系是九大类:餐饮、交通、购物、居住、娱乐、医疗、教育、人情、其他。这个数量级对个人记账足够用,分类太多模型容易混淆,太少又失去统计意义。

对应的 Prompt 片段长这样:

你是一个个人财务助理。用户会给你一条消费记录,请从中提取信息并输出 JSON。 可用分类(只能选其中一个):餐饮、交通、购物、居住、娱乐、医疗、教育、人情、其他。 输出 JSON 格式: { "amount": 金额数字(数字类型,不要带货币符号), "category": "分类", "merchant": "商户名或简要描述", "time": "YYYY-MM-DD HH:MM:SS", "note": "一句话备注" }

注意几个细节:分类是枚举值,只能选一个,杜绝自由发挥;amount 指定为数字类型,避免模型输出“85元”这种带单位的值;time 指定精确格式,让后续 Python 解析省掉一堆麻烦。

2.2 JSON Mode 与温度参数

OpenAI 的 API 提供了一个response_format参数,设成{ "type": "json_object" }就能强制模型输出 JSON,这在实际使用中非常救命。没有它的时候,模型偶尔会在 JSON 外面包一层 Markdown 代码块,或者加一句“好的,这是你的账单记录”,Python 直接解析失败。

温度参数我固定设为 0。记账不是写诗,不需要模型发挥创造力,温度越低输出越稳定。我在测试阶段试过 0.7,分类结果飘得厉害,同一句话可能两次调用给出不同分类;设为 0 之后基本稳定。

核心调用代码:

from openai import OpenAI import json client = OpenAI(api_key=API_KEY) def parse_bill(text: str) -> dict: system_prompt = "你是一个个人财务助理。用户会给你一条消费记录,请从中提取信息并输出 JSON。" user_prompt = f"""可用分类(只能选其中一个):餐饮、交通、购物、居住、娱乐、医疗、教育、人情、其他。 输出格式:{{"amount": 金额数字, "category": "分类", "merchant": "商户名", "time": "YYYY-MM-DD HH:MM:SS", "note": "备注"}} 消费记录:{text}""" resp = client.chat.completions.create( model="gpt-4o-mini", temperature=0, response_format={"type": "json_object"}, messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt} ] ) return json.loads(resp.choices[0].message.content)

模型选型方面我用的gpt-4o-mini,因为记账任务不需要太强的推理能力,但需要一定的语义理解,mini 型号在成本和效果之间是最平衡的。我也试过更便宜的旧版模型,分类准确率会掉一些,主要体现在金额提取和商户名归纳上。

2.3 金额提取的几个边界条件

金额是最不能出错的部分,所以 Prompt 里我特意加了三条规则:

  • 如果用户说“AA 每人 85”,金额取每人实付的 85,而不是总价;
  • 如果用户说“大概”“差不多”这类模糊词,按用户给的数值为准,不要自行修正;
  • 负数表示退款或收入,原样保留负号。

第一条是踩过坑的。我最初没写这条规则,模型面对“打车花了28,两个人AA”这种输入时,有时候返回 28,有时候返回 14,完全看它心情。加了明确规则之后,基本稳定在 14。

还有一个小细节:merchant字段我没让它必须填真实商户名,而是允许填“简要描述”。因为很多时候用户输入的是“楼下便利店买电池”,没有商户名,强行要求提取反而会出错。让模型用原文概括最稳妥。

3. 工程落地:文件监听、去重与幂等写入

3.1 三种录入方式打通日常习惯

光有 API 调用还不够,工具得能方便地接收账单数据。我做了三种录入入口:

第一种是我自己写的最常用的:一个简单的命令行交互,python record.py "晚饭 35 元",直接把一句话作为参数传给工具,解析后写入数据库。

第二种是文件监听模式,用watchdog库监听一个固定的文件夹,我把微信支付的账单导出文件、支付宝的 CSV 账单丢进去,工具自动读取、逐条解析并入库。

第三种是批量补录模式:把一段时间内积累的随手记一起处理,比如我在备忘录里攒了十几条消费记录,一次性粘到一个文本文件里,工具按行拆分逐条解析。

这里有个容易踩坑的点:微信/支付宝导出的 CSV 是带表头的,而且字段顺序经常变,直接整行丢给模型会很混乱。我的做法是先用 Pandas 读取 CSV,只保留“交易时间、交易类型、商品说明、收/支”这几列,然后拼接成一条自然语言描述再传给模型。这样做模型处理起来稳定得多。

3.2 去重与幂等:防止重复记账

重复记账是自动记账工具最容易犯的毛病。文件监听模式下,同一个账单文件可能被重复读取;手动输入时,同一笔消费也可能被不小心写两次。

我的去重方案是给每条记录生成一个唯一指纹:取“时间 + 金额 + 商户名摘要”三个字段拼接后做 MD5。写入数据库之前先查一下指纹是否已存在,存在就跳过。这个指纹设计的关键是粒度控制:太粗会把两笔相同金额的消费误判为重复,太细又起不到去重效果。实测下来“时间精确到分钟 + 金额精确到分 + 商户名”的组合比较合适。

幂等性的另一个保障是数据库写入操作本身用事务包住,任何一步失败都整体回滚,避免半条记录残留在库里。这在批量补录大量消费记录时尤其重要,不会出现“有的写了有的没写”的状态。

3.3 API Key 管理与异常兜底

API Key 我放在环境变量里,代码里一律通过os.getenv("OPENAI_API_KEY")读取,不硬编码在源代码中,避免把 Key 提交到 Git 仓库。

调用异常处理做了三层兜底:

import time from openai import OpenAI client = OpenAI(api_key=API_KEY) def call_with_retry(messages, max_retries=3): for attempt in range(max_retries): try: resp = client.chat.completions.create( model="gpt-4o-mini", temperature=0, response_format={"type": "json_object"}, messages=messages ) return resp except Exception as e: wait = 2 ** attempt print(f"请求失败,{wait} 秒后重试: {e}") time.sleep(wait) raise RuntimeError("OpenAI API 调用失败")

第一层是网络层面的重试,指数退避,避免瞬时故障导致整批数据处理失败。第二层是 JSON 解析兜底:万一模型返回的文本里有额外的字符,先用正则把最外层的 JSON 部分切出来再解析。第三层是字段校验:解析成功之后必须校验amount是数字、category在枚举列表里、time能转成日期格式,任何一项不满足就标记为“待人工处理”,而不是直接入库。

3.4 关于接口访问的部署建议

关于 OpenAI API 的访问环境,我的做法是:把整个工具部署在一台海外的云服务器上运行,服务器上装 Python、跑定时任务,本地只需要通过简单的上报接口或文件同步方式把账单数据传过去。如果你不方便自己搭海外服务器,也可以选择国内大模型厂商提供的兼容 OpenAI 格式的 API 网关服务,代码上只需要改base_urlapi_key两行,其余逻辑完全不用动。

顺带说一句,如果你使用的是第三方 API 网关,注意确认网关服务商是否兼容response_format参数。部分网关对 Chat Completions 的参数透传支持不全,特别是 JSON Mode 这类新参数,会导致输出不稳定。

4. 实测翻车现场:金额看错、分类漂移与 JSON 围城

4.1 金额识别的三个真实翻车案例

工具上线之后我用真实消费数据跑了整整一个月的回归测试,从 316 条记录里发现了 9 条识别错误的,其中金额错误就占了 6 条。我把经典案例拿出来复盘一下。

案例一:“花呗还款 1500”。这条被正确识别为“居住-其他”,但其实我想表达的是转账还款,不该计入日常消费支出。这类“转账/还款不算消费”的规则我在后期加入了 Prompt 的排除列表。

案例二:“中午和同事吃饭,我请客,点了 168 的套餐”。模型把金额识别成 168 是没问题的,但它把时间默认成了当天中午,而实际记账时间已经是晚上了。这里的问题在于时间推断,如果没有明确的日期信息,模型默认当前时刻,需要人工确认。

案例三:“打车去机场,路上买了瓶水 3 块,车费 45”。这是我故意测的复合场景——一条输入里有两笔消费。模型只提取了最后一笔 45,没有拆分两笔。后来我在 Prompt 里加了“如果一条记录包含多笔独立消费,按多笔分别返回,输出 JSON 数组”,才处理这种情况。

4.2 分类漂移与提示词修正过程

分类漂移是我早期遇到最头疼的问题。固定枚举分类之后,模型的分类稳定性大幅提升,但仍然有边界情况。比如“买了一杯喜茶”有时候归“餐饮”,有时候归“购物”,原因是我在 Prompt 里把“购物”描述成“日常百货、服饰、电子产品等”,而模型对“奶茶属于餐饮还是购物”的语义理解本身就有摇摆空间。

我的修正办法是在 Prompt 里显式加入每个分类的典型示例和边界说明:

分类说明: - 餐饮:日常吃饭、外卖、奶茶咖啡、烟酒、零食 - 购物:服饰、数码、日用品、美妆、书籍(非教育类) - 交通:打车、地铁、公交、加油、停车、违章罚款 - 娱乐:电影、游戏、健身、旅游门票、会员充值

加了示例之后,分类准确率从最初的 87.2% 提升到了 95.6%。这验证了一个经验:与其让模型猜测你的分类直觉,不如把分类标准明明白白写清楚。

4.3 JSON 解析失败的降级处理

就算开了 JSON Mode,解析失败仍然会出现,只是概率低了很多。在一次批量处理 200 条记录的测试中,有 3 条返回了非标准 JSON,具体表现是:字段名带了空格、字符串里包含了未转义的双引号、金额字段变成了“85.0 元”这种带单位的文本。

我的降级策略分三步:先用json.loads尝试标准解析;失败后用正则把最外层的{...}切出来,再尝试解析;还失败就把这条记录标记为parse_error=1,和原始文本一起存进数据库的待处理表,之后我手动看一眼分类。这种做法比反复重试更实际,因为模型连续两次返回同样格式错误的概率很低,与其干等,不如先跳过、最后统一人工校对。

5. 进阶适配:从自用脚本到半自动记账系统

5.1 用 Whisper 打通语音记账

命令行输入再方便,也不如说话来得快。后来我给工具加上了语音入口:用openai-whisper本地模型把语音转成文本,再把文本送进上面这套解析流程。我现在每天的通勤路上,想到什么消费就按住手机语音键说一句“今天咖啡 25,中午外卖 38”,回到家打开电脑,所有记录已经躺在数据库里了。

语音识别的准确率在安静环境下接近 98%,但嘈杂环境(比如地铁上)会有一些识别错误,比如“25”识别成“250”。所以语音入口的数据会额外打一个source=voice标记,我定期抽查这部分数据的准确率。这个设计提醒了我:任何自动化工具都应该给数据留一个可追溯的来源标记,方便日后审计。

5.2 月度统计与预算提醒

数据积累起来之后,统计报表才有意义。我用纯 Python 写了一个简单的统计模块:按月份汇总各类消费金额、计算环比变化、统计单笔最大支出、生成当月预算使用比例。

预算提醒的逻辑也很简单:每个月初设定各分类的预算金额,每当新写入一笔消费后,检查该分类当月累计支出是否超过预算的 80%,超过就在命令行输出提醒。

这部分没有用任何重量级框架,就是标准的datetimesqlite3操作。我一直觉得个人记账工具的精髓是轻量:够用就好,不需要为统计功能引入一个完整的前端框架集合。

5.3 本地模型作为备选方案

最后说一下模型选型的可替代性。OpenAI 的 API 自然好用,但它不是唯一选择。OpenAI SDK 的设计本身是兼容多种后端的,通过修改base_urlapi_key,可以无缝切换到其他提供兼容接口的大模型服务。

如果你有隐私顾虑,比如不希望把消费数据发送到外部 API,可以考虑用ollama在本地跑一个开源模型(比如 qwen 系列、llama 系列)。实测下来,本地 7B 级别的模型在分类准确率上比 GPT-4o-mini 落后大约 6 到 8 个百分点,但在“不联网、数据不出本机”的场景下,这个代价是值得的。对我个人来说,日常消费数据的敏感级别没有高到必须本机处理,所以目前主力还是走云端 API,本地模型当离线备胎。

在我连续用了这套工具半年之后,最深刻的感受是:所谓“自动记账”,并不是让工具替我做财务决策,而是把从“产生消费记录”到“进入统计报表”这一段路上所有机械重复的动作全部省掉了。我现在做的只是每天花三十秒把当天的消费随口说一遍,月底打开报表就能看到钱到底花在了哪里。这个过程中踩过的坑——分类漂移、金额误判、重复记账、JSON 解析失败——每一个都让我对“大模型落地实际生活”这件事有了更清醒的认知:模型负责懂得,代码负责可靠。把两者恰到好处地结合起来,这个工具才真正值得长期用下去。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询