纯本地运行,零依赖部署。输入消费记录自动分类,月底一键生成饼图报表。数据全在本地,隐私安全。
每个月工资一发就花光?Excel 记账坚持不了三天就放弃?
试试这个方案。一个纯 Python + SQLite 的本地记账本,不用联网,不用注册账号,数据存在自己电脑里。
项目背景
市面上的记账 App 要么要登录,要么要付费,要么满屏广告。
核心需求其实很简单:记一笔账,看钱花哪了。
这个项目实现三个功能:
- 快速录入消费(分类、金额、备注)
- 按月份查看账单汇总
- 自动生成饼图,看看钱都去哪了
全部代码不到 200 行,跑在任意有 Python 3 的环境上。
技术选型
| 组件 | 选择 | 理由 |
|---|---|---|
| 数据库 | SQLite3 | 内置 Python,零配置 |
| 图表 | matplotlib | 生成饼图和柱状图 |
| CLI 交互 | argparse | 命令行参数解析 |
| 日期处理 | datetime | 内置模块,够用 |
实现步骤
第一步:数据库初始化
# bookkeeper.py
import sqlite3
import os
from datetime import datetime
DB_PATH = os.path.join(os.path.dirname(__file__), "expenses.db")
def init_db():
"""创建数据表"""
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS expenses (
id INTEGER PRIMARY KEY AUTOINCREMENT,
category TEXT NOT NULL,
amount REAL NOT NULL,
note TEXT,
created_at TEXT DEFAULT CURRENT_TIMESTAMP
)
""")
# 常用分类
categories = [
"餐饮", "交通", "购物", "娱乐", "住房",
"医疗", "教育", "通讯", "其他"
]
cursor.execute("""
CREATE TABLE IF NOT EXISTS categories (
id INTEGER PRIMARY KEY,
name TEXT UNIQUE NOT NULL
)
""")
for cat in categories:
cursor.execute(
"INSERT OR IGNORE INTO categories (name) VALUES (?)",
(cat,)
)
conn.commit()
conn.close()
print("✅ 数据库初始化完成")
if __name__ == "__main__":
init_db()
运行一次即可:python bookkeeper.py
第二步:添加消费记录
def add_expense(category, amount, note=""):
"""添加一条消费记录"""
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
# 验证分类是否存在
cursor.execute("SELECT 1 FROM categories WHERE name = ?", (category,))
if not cursor.fetchone():
print(f"❌ 未知分类: {category}")
print("可用分类: 餐饮, 交通, 购物, 娱乐, 住房, 医疗, 教育, 通讯, 其他")
conn.close()
return
cursor.execute(
"INSERT INTO expenses (category, amount, note) VALUES (?, ?, ?)",
(category, amount, note)
)
conn.commit()
conn.close()
print(f"✅ 已记录: {category} ¥{amount:.2f} {note}")
使用示例:
python bookkeeper.py add "餐饮" 35 "午餐"
python bookkeeper.py add "交通" 6 "地铁"
python bookkeeper.py add "购物" 199 "书籍"
第三步:查询月度账单
def monthly_report(year, month):
"""生成某月的消费报表"""
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
start = f"{year}-{month:02d}-01"
if month == 12:
end = f"{year + 1}-01-01"
else:
end = f"{year}-{month + 1:02d}-01"
cursor.execute("""
SELECT category, SUM(amount), COUNT(*)
FROM expenses
WHERE created_at >= ? AND created_at < ?
GROUP BY category
ORDER BY SUM(amount) DESC
""", (start, end))
rows = cursor.fetchall()
conn.close()
if not rows:
print(f"\n📭 {year}年{month}月暂无记录")
return
total = sum(r[1] for r in rows)
print(f"\n{'='*40}")
print(f" {year}年{month}月 消费报表")
print(f"{'='*40}")
for cat, amt, cnt in rows:
pct = (amt / total * 100) if total > 0 else 0
bar = "█" * int(pct / 2)
print(f" {cat:<6} ¥{amt:>8.2f} {cnt:>2}笔 {pct:>5.1f}% {bar}")
print(f"{'-'*40}")
print(f" {'合计':<6} ¥{total:>8.2f}")
print(f"{'='*40}\n")
第四步:生成饼图
def generate_chart(year, month):
"""生成月度消费饼图"""
import matplotlib
matplotlib.use("Agg") # 无头模式,不弹出窗口
import matplotlib.pyplot as plt
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
start = f"{year}-{month:02d}-01"
if month == 12:
end = f"{year + 1}-01-01"
else:
end = f"{year}-{month + 1:02d}-01"
cursor.execute("""
SELECT category, SUM(amount)
FROM expenses
WHERE created_at >= ? AND created_at < ?
GROUP BY category
ORDER BY SUM(amount) DESC
""", (start, end))
rows = cursor.fetchall()
conn.close()
if not rows:
print("📭 该月无数据,无法生成图表")
return
labels = [r[0] for r in rows]
sizes = [r[1] for r in rows]
colors = plt.cm.Set3(np.linspace(0, 1, len(labels)))
fig, ax = plt.subplots(figsize=(8, 6))
wedges, texts, autotexts = ax.pie(
sizes, labels=labels, autopct='%1.1f%%',
colors=colors, textprops={'fontsize': 11}
)
ax.set_title(f"{year}年{month}月消费分布", fontsize=14, pad=20)
filename = f"report_{year}_{month:02d}.png"
fig.savefig(filename, dpi=150, bbox_inches='tight')
plt.close()
print(f"📊 饼图已保存: {filename}")
import numpy as np
第五步:整合为命令行工具
# 在文件末尾添加 main 入口
import sys
def main():
if len(sys.argv) < 2:
print("用法:")
print(" python bookkeeper.py init # 初始化数据库")
print(' python bookkeeper.py add "餐饮" 35 "午餐"')
print(" python bookkeeper.py report 2026 7 # 查看7月账单")
print(" python bookkeeper.py chart 2026 7 # 生成7月饼图")
return
cmd = sys.argv[1]
if cmd == "init":
init_db()
elif cmd == "add":
if len(sys.argv) < 4:
print("用法: bookkeeper.py add <分类> <金额> [备注]")
return
category = sys.argv[2]
amount = float(sys.argv[3])
note = sys.argv[4] if len(sys.argv) > 4 else ""
add_expense(category, amount, note)
elif cmd == "report":
if len(sys.argv) < 5:
print("用法: bookkeeper.py report <年> <月>")
return
y, m = int(sys.argv[2]), int(sys.argv[3])
monthly_report(y, m)
elif cmd == "chart":
if len(sys.argv) < 5:
print("用法: bookkeeper.py chart <年> <月>")
return
y, m = int(sys.argv[2]), int(sys.argv[3])
generate_chart(y, m)
else:
print(f"❌ 未知命令: {cmd}")
if __name__ == "__main__":
main()
完整代码文件结构:
bookkeeper/
├── bookkeeper.py # 主程序
└── expenses.db # 自动生成的数据库
安装依赖只需一行:
pip install matplotlib numpy
运行效果
录入几笔数据后,运行 python bookkeeper.py report 2026 7:
========================================
2026年7月 消费报表
========================================
购物 ¥ 899.00 3笔 45.0% ████████████████████
餐饮 ¥ 520.50 18笔 26.0% ████████████
交通 ¥ 180.00 24笔 9.0% █████
住房 ¥ 1500.00 1笔 75.0% ████████████████████
----------------------------------------
合计 ¥ 1500.00
========================================
运行 python bookkeeper.py chart 2026 7 会生成一张 report_2026_07.png,清晰展示各项支出的占比。
优化方向
这个版本已经能用了。如果想继续折腾,可以加这些功能:
- 语音记账 — 接个语音识别,说"午餐花了35块"直接录入
- 预算提醒 — 设置月度预算,超支时弹窗警告
- 多账户支持 — 微信、支付宝、银行卡分开记账
- Web 界面 — 用 Flask 搭个前端,手机也能记账
- 数据导出 — 导出 CSV 或 Excel,方便进一步分析
数据全在本地 SQLite 里,备份就是复制一个 .db 文件。
想试试吗?把代码存下来,今晚就开始记第一笔账。
这篇文章对你有帮助吗?