aoi学院

Aisaka's Blog, School of Aoi, Aisaka University

Python-第14篇《智能记账本》

导语

上一篇我们做了股票监控器,学会了从网络获取实时数据。不过,投资只是理财的一部分,更基础的是日常收支管理。今天我们来打造一款“懂你”的智能记账本——它能自动识别消费类别、在你乱花钱时发出警告,还能一键生成月度报表。


本篇目标

  • 设计简单的记账数据存储格式
  • 实现收支自动分类(关键词匹配)
  • 添加预算超支预警功能
  • 生成带图表的月度收支报表
  • 打造交互式记账程序

一、准备工作:创建记账数据文件

首先,我们需要一个地方存储记账数据。用CSV文件最简单,可以用Excel打开,也能被Python轻松读写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# ========== Step 1: 创建记账数据文件 ==========
import csv
import os
from datetime import datetime
import pandas as pd
import matplotlib.pyplot as plt

# 定义记账文件路径
RECORD_FILE = "my_accounts.csv"

def init_account_file():
"""初始化记账文件(如果不存在)"""
if not os.path.exists(RECORD_FILE):
with open(RECORD_FILE, 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
# 写入表头:日期, 类型(收入/支出), 分类, 金额, 备注
writer.writerow(['日期', '类型', '分类', '金额', '备注'])
print(f"✅ 已创建记账文件: {RECORD_FILE}")
else:
print(f"📂 已存在记账文件: {RECORD_FILE}")

# 初始化
init_account_file()

运行后,你会在当前文件夹看到一个my_accounts.csv文件,这就是我们记账本的数据库啦!


二、收支分类自动识别

2.1 设计分类规则

自动分类的核心是“关键词匹配”。比如备注里包含“吃饭”、“外卖”、“餐厅”,就归为“餐饮”类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# ========== Step 2: 自动分类引擎 ==========
# 定义分类关键词字典
CATEGORY_RULES = {
"餐饮": ["吃饭", "外卖", "餐厅", "咖啡", "奶茶", "快餐", "食堂", "美食"],
"交通": ["打车", "地铁", "公交", "滴滴", "加油", "停车", "高铁", "机票"],
"购物": ["淘宝", "京东", "拼多多", "购物", "衣服", "鞋子", "包包", "化妆品"],
"娱乐": ["电影", "游戏", "KTV", "演唱会", "旅游", "门票", "健身", "视频会员"],
"生活缴费": ["水电费", "电费", "水费", "燃气", "话费", "网费", "房租", "物业"],
"人情往来": ["红包", "礼物", "礼金", "请客", "聚餐", "随礼"],
"医疗保健": ["医院", "药店", "看病", "体检", "买药", "医疗"],
"学习提升": ["买书", "课程", "培训", "学习", "考试", "资料"],
"工资收入": ["工资", "薪水", "薪资", "奖金"],
"其他收入": ["红包", "转账", "退款", "副业"]
}

def auto_category(note, trans_type):
"""
自动识别分类
note: 备注信息
trans_type: 类型("收入" 或 "支出")
"""
note = note.lower() # 转为小写

# 根据类型选择分类规则
if trans_type == "收入":
check_cats = {"工资收入": CATEGORY_RULES["工资收入"],
"其他收入": CATEGORY_RULES["其他收入"]}
else: # 支出
check_cats = {k: v for k, v in CATEGORY_RULES.items() if k not in ["工资收入", "其他收入"]}

# 匹配关键词
for category, keywords in check_cats.items():
for keyword in keywords:
if keyword in note:
return category

# 如果没匹配到,支出默认"其他支出",收入默认"其他收入"
return "其他支出" if trans_type == "支出" else "其他收入"

# 测试一下
print("🧠 分类测试:")
print(f"'中午外卖' -> {auto_category('中午外卖', '支出')}")
print(f"'工资发放' -> {auto_category('工资发放', '收入')}")
print(f"'淘宝购物' -> {auto_category('淘宝购物', '支出')}")

输出结果:

1
2
3
4
🧠 分类测试:
'中午外卖' -> 餐饮
'工资发放' -> 工资收入
'淘宝购物' -> 购物

2.2 添加记账功能

现在实现添加记录的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# ========== Step 3: 添加记账记录 ==========
def add_record():
"""交互式添加记账记录"""
print("\n" + "=" * 40)
print("添加新记录")
print("=" * 40)

# 1. 输入类型
while True:
trans_type = input("类型(1-收入,2-支出):").strip()
if trans_type == "1":
trans_type = "收入"
break
elif trans_type == "2":
trans_type = "支出"
break
else:
print("请输入1或2!")

# 2. 输入金额
while True:
try:
amount = float(input("金额:").strip())
if amount <= 0:
print("金额必须大于0!")
else:
break
except:
print("请输入有效的数字!")

# 3. 输入备注
note = input("备注(如:午饭外卖):").strip()

# 4. 自动分类
category = auto_category(note, trans_type)
print(f"📌 自动分类为:{category}")

# 5. 获取当前日期
date_str = datetime.now().strftime('%Y-%m-%d')

# 6. 写入文件
with open(RECORD_FILE, 'a', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerow([date_str, trans_type, category, amount, note])

print("✅ 记录添加成功!\n")

# 测试添加几条记录
# add_record()
# add_record()

三、预算超支提醒

3.1 设置月度预算

先为每个支出分类设置月度预算:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# ========== Step 4: 预算管理系统 ==========
# 设置各类别的月度预算(元)
MONTHLY_BUDGET = {
"餐饮": 2000,
"交通": 800,
"购物": 1500,
"娱乐": 1000,
"生活缴费": 1500,
"人情往来": 800,
"医疗保健": 500,
"学习提升": 500,
"其他支出": 500
}

def check_budget_alert():
"""检查预算超支情况"""
print("\n" + "=" * 40)
print("预算使用情况检查")
print("=" * 40)

# 读取本月数据
df = pd.read_csv(RECORD_FILE, encoding='utf-8')
df['日期'] = pd.to_datetime(df['日期'])

# 获取当前月份
current_month = datetime.now().strftime('%Y-%m')
df_this_month = df[df['日期'].dt.strftime('%Y-%m') == current_month]

# 只统计支出
df_expense = df_this_month[df_this_month['类型'] == '支出']

# 按分类统计
expense_by_cat = df_expense.groupby('分类')['金额'].sum()

# 检查每类预算
alert_triggered = False
for category, budget in MONTHLY_BUDGET.items():
spent = expense_by_cat.get(category, 0)
usage_rate = (spent / budget) * 100 if budget > 0 else 0

# 显示进度条
bar_length = 20
filled = int(bar_length * min(usage_rate / 100, 1))
bar = "█" * filled + "░" * (bar_length - filled)

print(f"\n{category}:")
print(f" 预算: ¥{budget:.2f} | 已用: ¥{spent:.2f}")
print(f" [{bar}] {usage_rate:.1f}%")

# 超支警告
if usage_rate >= 100:
print(f" 🚨 **已超支!** 本月在'{category}'上已花费¥{spent:.2f},超出预算¥{spent - budget:.2f}")
alert_triggered = True
elif usage_rate >= 80:
print(f" ⚠️ **即将超支!** 已使用{budget:.1f}%")
alert_triggered = True

if not alert_triggered:
print("\n✅ 恭喜!各类支出都在预算范围内,继续保持!")

return expense_by_cat

四、月度报表生成

4.1 生成统计图表

matplotlib生成漂亮的收支报表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# ========== Step 5: 月度报表生成器 ==========
def generate_monthly_report():
"""生成本月收支报表"""
print("\n" + "=" * 40)
print("生成月度报表")
print("=" * 40)

# 读取数据
try:
df = pd.read_csv(RECORD_FILE, encoding='utf-8')
df['日期'] = pd.to_datetime(df['日期'])
except:
print("📭 暂无记账数据,无法生成报表")
return

# 获取当前月份
current_month = datetime.now().strftime('%Y-%m')
df_this_month = df[df['日期'].dt.strftime('%Y-%m') == current_month]

if df_this_month.empty:
print(f"📭 {current_month} 暂无数据")
return

# 1. 收入支出总览
income = df_this_month[df_this_month['类型'] == '收入']['金额'].sum()
expense = df_this_month[df_this_month['类型'] == '支出']['金额'].sum()
balance = income - expense

print(f"\n📊 {current_month} 收支总览")
print(f"总收入: ¥{income:10,.2f}")
print(f"总支出: ¥{expense:10,.2f}")
print(f"净结余: ¥{balance:10,.2f}")
print(f"结余率: {(balance/income*100 if income>0 else 0):10.1f}%")

# 2. 支出分类饼图
expense_by_cat = df_this_month[df_this_month['类型'] == '支出'].groupby('分类')['金额'].sum()

plt.figure(figsize=(12, 5))

# 子图1:支出分类饼图
plt.subplot(1, 2, 1)
plt.pie(expense_by_cat.values, labels=expense_by_cat.index, autopct='%1.1f%%')
plt.title(f'{current_month} 支出分类')

# 子图2:每日支出趋势
plt.subplot(1, 2, 2)
daily_expense = df_this_month[df_this_month['类型'] == '支出'].groupby(df['日期'].dt.day)['金额'].sum()
plt.plot(daily_expense.index, daily_expense.values, marker='o')
plt.title('每日支出趋势')
plt.xlabel('日期')
plt.ylabel('金额(元)')
plt.grid(True)

plt.tight_layout()
plt.savefig(f'{current_month}_report.png', dpi=150, bbox_inches='tight')
print(f"\n✅ 图表已保存为: {current_month}_report.png")
plt.show()

# 3. 详细流水
print(f"\n📝 详细流水(按日期排序):")
df_sorted = df_this_month.sort_values('日期', ascending=False)
for _, row in df_sorted.iterrows():
print(f"{row['日期'].strftime('%m-%d')} {row['类型']} {row['分类']:>6} ¥{row['金额']:>8.2f} {row['备注']}")

五、完整项目代码

现在把所有功能整合成一个交互式程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# ========== 智能记账本 - 完整版 ==========
import csv
import os
import pandas as pd
import matplotlib.pyplot as plt
from datetime import datetime

# 配置文件
RECORD_FILE = "my_accounts.csv"
CATEGORY_RULES = {
"餐饮": ["吃饭", "外卖", "餐厅", "咖啡", "奶茶", "快餐", "食堂", "美食"],
"交通": ["打车", "地铁", "公交", "滴滴", "加油", "停车", "高铁", "机票"],
"购物": ["淘宝", "京东", "拼多多", "购物", "衣服", "鞋子", "包包", "化妆品"],
"娱乐": ["电影", "游戏", "KTV", "演唱会", "旅游", "门票", "健身", "会员"],
"生活缴费": ["水电费", "电费", "水费", "燃气", "话费", "网费", "房租", "物业"],
"人情往来": ["红包", "礼物", "礼金", "请客", "聚餐", "随礼"],
"医疗保健": ["医院", "药店", "看病", "体检", "买药", "医疗"],
"学习提升": ["买书", "课程", "培训", "学习", "考试", "资料"],
"工资收入": ["工资", "薪水", "薪资", "奖金"],
"其他收入": ["红包", "转账", "退款", "副业"]
}

MONTHLY_BUDGET = {
"餐饮": 2000, "交通": 800, "购物": 1500, "娱乐": 1000,
"生活缴费": 1500, "人情往来": 800, "医疗保健": 500,
"学习提升": 500, "其他支出": 500
}

def init_file():
if not os.path.exists(RECORD_FILE):
with open(RECORD_FILE, 'w', newline='', encoding='utf-8') as f:
csv.writer(f).writerow(['日期', '类型', '分类', '金额', '备注'])
print("📁 初始化记账文件...")

def auto_category(note, trans_type):
note = note.lower()
if trans_type == "收入":
check_cats = {"工资收入": CATEGORY_RULES["工资收入"],
"其他收入": CATEGORY_RULES["其他收入"]}
else:
check_cats = {k: v for k, v in CATEGORY_RULES.items()
if "收入" not in k}

for cat, keywords in check_cats.items():
for kw in keywords:
if kw in note:
return cat
return "其他支出" if trans_type == "支出" else "其他收入"

def add_record():
print("\n" + "=" * 40)
print("📝 添加新记录")
print("=" * 40)

# 类型
while True:
t = input("类型(1-收入,2-支出): ").strip()
if t in ["1", "2"]:
trans_type = "收入" if t == "1" else "支出"
break
print("请输入1或2!")

# 金额
while True:
try:
amount = float(input("金额: ").strip())
if amount > 0:
break
print("金额必须大于0!")
except:
print("请输入数字!")

# 备注
note = input("备注(如:午饭外卖): ").strip()
category = auto_category(note, trans_type)
print(f"📌 自动分类: {category}")

# 保存
with open(RECORD_FILE, 'a', newline='', encoding='utf-8') as f:
csv.writer(f).writerow([
datetime.now().strftime('%Y-%m-%d'),
trans_type, category, amount, note
])
print("✅ 保存成功!")

# 检查预算
if trans_type == "支出":
input("\n按回车键查看预算使用情况...")
check_budget()

def view_records():
"""查看历史记录"""
print("\n" + "=" * 40)
print("📋 历史记录")
print("=" * 40)

try:
df = pd.read_csv(RECORD_FILE, encoding='utf-8')
df['日期'] = pd.to_datetime(df['日期'])
df = df.sort_values('日期', ascending=False)

print(f"\n{'日期':<12} {'类型':<4} {'分类':<8} {'金额':>8} {'备注'}")
print("-" * 60)
for _, r in df.iterrows():
print(f"{r['日期'].strftime('%m-%d'):<12} {r['类型']:<4} {r['分类']:<8} ¥{r['金额']:>7.2f} {r['备注']}")
except:
print("暂无记录!")

def check_budget():
"""检查预算"""
try:
df = pd.read_csv(RECORD_FILE, encoding='utf-8')
df['日期'] = pd.to_datetime(df['日期'])
df_this_month = df[df['日期'].dt.strftime('%Y-%m') == datetime.now().strftime('%Y-%m')]
df_expense = df_this_month[df_this_month['类型'] == '支出']
expense_by_cat = df_expense.groupby('分类')['金额'].sum()

print("\n💰 本月预算使用情况:\n")
for cat, budget in MONTHLY_BUDGET.items():
spent = expense_by_cat.get(cat, 0)
rate = (spent / budget * 100) if budget else 0
bar = "█" * int(min(rate, 100) / 5) + "░" * (20 - int(min(rate, 100) / 5))
print(f"{cat:>8}: [{bar}] {rate:>5.1f}% (¥{spent:.0f}{budget:.0f})")

except Exception as e:
print(f"检查失败: {e}")

def report():
"""生成报表"""
try:
df = pd.read_csv(RECORD_FILE, encoding='utf-8')
df['日期'] = pd.to_datetime(df['日期'])

current_month = datetime.now().strftime('%Y-%m')
df_this = df[df['日期'].dt.strftime('%Y-%m') == current_month]

if df_this.empty:
print("本月暂无数据!")
return

# 收支总览
income = df_this[df_this['类型'] == '收入']['金额'].sum()
expense = df_this[df_this['类型'] == '支出']['金额'].sum()
balance = income - expense

print("\n" + "=" * 40)
print(f"📊 {current_month} 收支总览")
print("=" * 40)
print(f"总收入: ¥{income:10,.2f}")
print(f"总支出: ¥{expense:10,.2f}")
print(f"净结余: ¥{balance:10,.2f}")

# 生成图表
expense_by_cat = df_this[df_this['类型'] == '支出'].groupby('分类')['金额'].sum()

if not expense_by_cat.empty:
plt.figure(figsize=(12, 5))

# 饼图
plt.subplot(1, 2, 1)
plt.pie(expense_by_cat.values, labels=expense_by_cat.index, autopct='%1.1f%%')
plt.title('支出分类')

# 趋势
plt.subplot(1, 2, 2)
daily = df_this[df_this['类型'] == '支出'].groupby(df['日期'].dt.day)['金额'].sum()
plt.plot(daily.index, daily.values, marker='o')
plt.title('每日支出趋势')
plt.xlabel('日期')
plt.ylabel('金额')
plt.grid(True)

plt.tight_layout()
plt.savefig(f'{current_month}_report.png', dpi=150, bbox_inches='tight')
print(f"\n✅ 图表已保存: {current_month}_report.png")
plt.show()

except Exception as e:
print(f"生成报表失败: {e}")

def main():
"""主菜单"""
init_file()

while True:
print("\n" + "=" * 50)
print("智能记账本")
print("=" * 50)
print("1. 添加新记录")
print("2. 查看历史记录")
print("3. 预算使用情况")
print("4. 生成本月报表")
print("5. 退出")
print("=" * 50)

choice = input("请选择功能(1-5): ").strip()

if choice == "1":
add_record()
elif choice == "2":
view_records()
elif choice == "3":
check_budget()
elif choice == "4":
report()
elif choice == "5":
print("👋 感谢使用,再见!")
break
else:
print("请输入1-5之间的数字!")

if __name__ == "__main__":
main()

六、使用方法

保存为smart_account_book.py,然后运行:python smart_account_book.py

使用示例:

1
2
3
4
5
6
7
8
9
10
==================================================
智能记账本
==================================================
1. 添加新记录
2. 查看历史记录
3. 预算使用情况
4. 生成本月报表
5. 退出
==================================================
请选择功能(1-5):1

然后按照提示操作即可,所有数据会自动保存到my_accounts.csv


七、知识点加油站

7.1 CSV文件操作

  • csv.writer():写入数据
  • pd.read_csv():读取为DataFrame
  • encoding='utf-8':确保中文不乱码

7.2 日期处理技巧

1
2
3
4
5
6
7
8
from datetime import datetime

# 获取当前日期
datetime.now().strftime('%Y-%m-%d') # 2026-01-26

# 从DataFrame中提取月份
df['日期'].dt.strftime('%Y-%m') # 提取年月
df['日期'].dt.day # 提取日

7.3 Matplotlib基础

  • plt.figure(figsize=(宽, 高)):创建图表
  • plt.subplot(行, 列, 第几个):子图
  • plt.savefig():保存图片
  • plt.show():显示图表

八、总结

  • ✅ 自动分类的记账系统
  • ✅ 预算超支预警
  • ✅ 带图表的月度报表
  • ✅ 完整的交互式程序

下篇预告

第15篇《Excel批量处理器》——解放双手,让Python帮你自动化处理几十个Excel文件,文件处理的效率神器!


🤖 Powered by Kimi K2 Thinking 💻 内容经葵葵🌻审核与修改