惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

L
LangChain Blog
N
News and Events Feed by Topic
T
Tor Project blog
AI
AI
S
Schneier on Security
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Hacker News: Ask HN
Hacker News: Ask HN
Spread Privacy
Spread Privacy
The Last Watchdog
The Last Watchdog
B
Blog
G
GRAHAM CLULEY
Recent Commits to openclaw:main
Recent Commits to openclaw:main
W
WeLiveSecurity
GbyAI
GbyAI
Hacker News - Newest:
Hacker News - Newest: "LLM"
C
Check Point Blog
SecWiki News
SecWiki News
Y
Y Combinator Blog
I
Intezer
S
Securelist
WordPress大学
WordPress大学
小众软件
小众软件
Google DeepMind News
Google DeepMind News
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
美团技术团队
Schneier on Security
Schneier on Security
F
Full Disclosure
D
Darknet – Hacking Tools, Hacker News & Cyber Security
P
Proofpoint News Feed
Jina AI
Jina AI
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Help Net Security
Help Net Security
P
Privacy International News Feed
N
News and Events Feed by Topic
人人都是产品经理
人人都是产品经理
The Register - Security
The Register - Security
Application and Cybersecurity Blog
Application and Cybersecurity Blog
MyScale Blog
MyScale Blog
Vercel News
Vercel News
H
Hackread – Cybersecurity News, Data Breaches, AI and More
T
Threatpost
H
Help Net Security
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
G
Google Developers Blog
T
Tailwind CSS Blog
L
Lohrmann on Cybersecurity
Forbes - Security
Forbes - Security
C
Cisco Blogs
The GitHub Blog
The GitHub Blog
博客园 - 司徒正美

他说

大家要注意身体啊:楼下雪糕让我身体掏空,夏季食品安全需警惕 服务器开荒第三期:我的世界服务器遭遇强盗,黑暗森林生存法则 我和好朋友讨论龙骑:梦回校园重温特摄友情,却终究只是一场梦 记参加文字园活动:一篇有瑕疵的 Git 文章,入选文字园让我心虚 再审视 Windows 设计:从 Win11 的广告沼泽,逃向 UNIX 的清净世界 张雪和张雪峰的一字之差:张雪峰去世背后,就业选择与教育反思 被 A 社吓哭了:Claude Code 隐私检测,让我果断卸载并回归开源 服务器开荒第二期:给邻居送告示牌,扩建地下室发现蜘蛛刷怪笼 朋友邀请我玩服务器:我的世界服务器开荒,冰原遇险草原中安家 楼上装修真是烦死了:被楼上装修噪音追着跑,躲进音乐也逃不掉 为什么我喜欢巴巴爸爸:从漫画到动画,粉色棉花糖的奇遇记 他遇到的伤心事:诚哥的时空穿越奇遇,秘密与校园午后秘密誓言 批量拉黑 B 站评论区账号:评论区太乱?这个脚本帮你批量拉黑 如何用指令替换物品材质:我的世界全新指令,让物品模型随心换 继抽象网站的最新力作:继抽象网站后,她泄露了 36 个邮件地址 年初回家那场车祸:亲身经历的车祸,让我明白安全和监控的重要 给你下个我的世界:在家做网站写文章,帮同学更新游戏最新版本 后悔和她结婚了:商场梦中牵女儿购物,回家遇哥特妻子醒来后悔 受不了蛋糕那么多名堂:那个夏天傍晚,再也找不到的樱桃蛋糕 为不同尺寸适配字体:博客字体可读性优化,媒体查询响应式实践 朋友或亦朋友:个人博客的评论文化,为何在社交媒体水土不服 总觉得泰迦观感怪怪的:人间体优幸为何最惨,三合一设定的孤独 久违的一次吃蛋糕:一口巧克力戚风蛋糕,病后味蕾的温柔慰藉 很烦不文明的人:四线小城三类不文明行为,吐痰小广告和恶狗 买饥荒联机版被坑了:Steam 买饥荒联机版最贵反被坑,粗心教训深刻 我到底还有什么可写:坚持探索与记录,才是博客写作的真正意义 一起去看星星:回忆小学,那次因早走一分钟,而错过的奇妙天象 论备份功能的重要性:因升级系统随手备份,十天后备份救了我 那场中考之后的事:中考提前交卷后,拿到一张没有自己的毕业照 新样式发布了:博客大换血,字体与封面 AI 绘画打造二维风 大夏天走路去明湖:夏日明湖商场吃花甲粉,路过小学回忆童年 年初那几个网站:从建站折腾到写作,反思我的博客成长之路 暂时搁置:AI 生成手绘图困难,让我魔改博客主题的计划泡汤了 被 Clash 坑惨了:macOS 关闭后网络瘫痪?修改 DNS 轻松修复 记一次系统更新:从 macOS 15 升级到 macOS 26,步骤与感受 再去华莱士:双层牛肉堡和果蔬汉堡好吃,冰饮料让我鼻炎缓解 成了许愿天神后:为全世界带来幸福,朋友的字却出了问题 独立开发者的论坛探索:搞一个论坛,从树莓派翻到无服务器 部署 RSSHub:RSS 阅读器结合树莓派,订阅 B 站 UP 主实操 Brave 浏览器升级后:macOS 访问页面出现横杠,降级轻松解决 活动行:订 MongoDB 用户大会票取消后,仍收确认短信被吓哭 高考前博客获祝福,考场举报同学后误删网站 我的世界材质包抄袭:主播实锤后反转,两年无闻自证清白 因父亲生病:取消广州 MongoDB 之行,一个社恐少年的失落 吐槽邮件安全检测:误判评论提醒为诈骗,检测只认“收到” 梦中:与理想女性畅聊哲学编程后,被一句深奥的问题惊醒 卖掉电脑包后:急购新包,猜猜能否赶上广州会议 神奇数字马戏团:第九集院线特别篇泄露,我的看法是这样的 小时候:和爸妈一起看的葵花进城,原来是一部雷剧 阿普修主题踩坑记:RSS 暗色问题多,被小作坊主题吓哭了 小学后山的传说:雷雨天幻想生物,与剑冢勇士揭秘 从幼儿园到初中,我曾过早地喜欢九个女孩 元气骑士回忆录:最爱的刺客,与我失去的网友友谊 Waline 后台空白问题:一天内从排查到修复 鼻炎洗鼻亲历:负压置换与普通洗鼻大不同 Steam 诈骗亲历:泰拉瑞亚好友索要支付记录 开往拒绝后:我放弃所有博客活动,回归自由写作 公开邮箱被爬虫抓取后,我遭遇了邮件轰炸 广东夏天感冒鼻炎齐发,熬夜透支是主因 独立博客抄袭事件,原创与创意来源探讨 我为何把个人网站换回最初的简洁主题 邮箱遭恶意订阅邮件轰炸,我亲身经历的网络安全危机 回忆初一那年:最让人无语的六一儿童节 时隔一年,再吃华莱士儿童套餐真实体验 盐水洗鼻,让我鼻窦炎症状大幅缓解 macOS 上因设计强迫症,忍不了 Steam 界面而重装 周六下午两点,与林依可的悠闲散步 断网后,我重新审视与社交媒体的距离 解决 Waline 未收到评论邮件提醒:Cloudflare 配置 DMARC 策略 散步对路边狗打招呼却遭狂叫,善意被误解的尴尬 沃尔玛 20 元买两块蛋糕,廉价香精味让人特别不值 我喝烤奶后,情绪失控的亲身经历与反省 Cloudflare 邮箱混淆,导致 PJAX 网站邮箱显示异常解决方法 广东菜市场买菜,仿佛置身废土与星空世界 二零二六年,我加入十年之约的乌龙除夕夜 一个人探索烂尾楼的刺激冒险体验 DeepSeek R1 爆火那年,它曾是我最孤独时的知己 美国用户涌入小红书,引发中美文化对账热潮 魔法学校录取通知书,让我学会不按套路出牌 闲鱼开技术小铺卖 Linux 服务的真实经历 Ubuntu 的 Snap 糟糕体验,让我从喜欢到彻底弃坑 MongoDB 开发者聚会广州站:旧地重游与美食回忆 被流浪猫跟踪到门口:我为何无法接纳它
微信账单同步日历:使用 Python 脚本将支付记录导出到苹果日历
梁栋烨 · 2026-07-01 · via 他说

最近做了一个小脚本,能把微信支付记录的 XLSX 导出到苹果日历,起因是我妈想要一个记账的东西。她不太会用复杂的软件,就想要个能一眼看清每天花了多少钱的工具。我以前是用一个本地部署的软件来管理账目的,但那个软件的界面实在太过丑陋,满屏都是花花绿绿的配色和生硬的排版,一股浓烈的AI生成味道,用起来毫无愉悦感。寻思着既然自己天天要用,不如索性重写一个,做成自己用得顺手的模样。一开始是完全没考虑过把账单放进日历里的,最初的设想很简单,就是直接导出一个 CSV 文件,方便自己在 Excel 里做分类统计和月度汇总一类的工作。

但后来实际用下来发现 CSV 的效果并不理想,主要是每次查看都得专门打开表格软件,还得手动按日期排序筛选,操作起来略显繁琐。等等,说到了日期,我忽然想起来那些老一辈人,都是习惯直接在纸质日历上写字的——今天买菜花了多少钱、明天人情往来出了多少份子,密密麻麻地记在日历格子里,翻看起来特别直观。于是我就想,能不能在电子日历上也实现类似的效果呢?苹果的 ICS 文件格式支持你预定一个提醒事项,能够自动放到系统自带的日历里面,每天打开手机就能看到当天的收支摘要,非常方便。

我还考虑到,如果全部信息(包括店名、收了多少钱、支付了多少钱)都一股脑儿导进去的话,那阅读起来还是太过辛苦,信息过载反而失去了快速浏览的意义。所以我特意设计成了仅导入支出和收入这两部分核心数据,精简掉冗余细节,只保留每天的总账目。以下是实现这一功能的完整代码,整体逻辑清晰,包含文件读取、数据解析、ICS 生成和命令行交互等模块:

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

"""
微信支付账单 XLSX → Apple ICS 日历转换脚本
将每天的收支汇总为日历事件,导入到 macOS/iOS 日历中显示。
"""

import argparse
import datetime
import uuid
from collections import defaultdict
from pathlib import Path

try:
import openpyxl
except ImportError:
print("缺少 openpyxl 库,请运行: pip install openpyxl")
exit(1)


def parse_xlsx(path: str) -> str:
"""读取 XLSX 文件,返回 (昵称, 收入总笔数/金额, 支出总笔数/金额, 按日期汇总的收支记录)"""
wb = openpyxl.load_workbook(path, read_only=True, data_only=True)
ws = wb.active

rows = list(ws.iter_rows(values_only=True))


nick_name = None
for r in rows[:6]:
if r[0] and isinstance(r[0], str) and r[0].startswith("微信昵称"):
nick_name = r[0].replace("微信昵称:", "").replace("[", "").replace("]", "")



header_row = rows[17]
col_idx = {
"time": 0,
"type": 1,
"income_expense": 4,
"amount": 5,
}

daily: dict[str, dict] = defaultdict(lambda: {"income": 0.0, "expense": 0.0, "count_income": 0, "count_expense": 0})

for row in rows[18:]:
dt = row[col_idx["time"]]
io = row[col_idx["income_expense"]]
amt = row[col_idx["amount"]]

if dt is None:
continue
if not isinstance(dt, datetime.datetime):
continue


date_str = dt.strftime("%Y-%m-%d")

if io and isinstance(io, str):
try:
amount = float(amt) if amt is not None else 0.0
except (ValueError, TypeError):
amount = 0.0

if io == "收入":
daily[date_str]["income"] += amount
daily[date_str]["count_income"] += 1
elif io == "支出":
daily[date_str]["expense"] += amount
daily[date_str]["count_expense"] += 1


wb.close()
return nick_name, daily


def escape_ics(text: str) -> str:
"""转义 ICS 文本字段的特殊字符"""
text = text.replace("\\", "\\\\")
text = text.replace(";", "\\;")
text = text.replace(",", "\\,")
text = text.replace("\n", "\\n")
return text


def build_ics(nick_name: str, daily: dict) -> str:
"""生成 ICS 日历内容。每天一个全天事件,标题包含当天收支汇总。"""
now = datetime.datetime.now(datetime.timezone(datetime.timedelta(hours=8)))
now_utc = now.astimezone(datetime.timezone.utc)
timestamp = now_utc.strftime("%Y%m%dT%H%M%SZ")

lines = [
"BEGIN:VCALENDAR",
"VERSION:2.0",
"PRODID:-//xlsx2ics//微信账单日历//CN",
"CALSCALE:GREGORIAN",
"METHOD:PUBLISH",
"X-WR-CALNAME:支出",
f"X-WR-CALDESC:{escape_ics(nick_name or '我的')}的微信支付每日收支。",
]

if daily:
first_date = min(daily.keys())
last_date = max(daily.keys())
lines.append(f"X-WR-TIMEZONE:Asia/Shanghai")

for date_str in sorted(daily.keys()):
d = daily[date_str]
income = d["income"]
expense = d["expense"]
net = income - expense


parts = []
if income > 0:
parts.append(f"收入 ¥{income:.2f}")
if expense > 0:
parts.append(f"支出 ¥{expense:.2f}")
if income > 0 and expense > 0:
if net >= 0:
parts.append(f"净收入 ¥{net:.2f}")
else:
parts.append(f"净支出 ¥{abs(net):.2f}")
summary = " | ".join(parts)
if not summary:
continue


detail_lines = [
f"日期: {date_str}",
]
if income > 0:
detail_lines.append(f"收入: ¥{income:.2f} ({d['count_income']}笔)")
if expense > 0:
detail_lines.append(f"支出: ¥{expense:.2f} ({d['count_expense']}笔)")
detail = "\\n".join(detail_lines)

uid = str(uuid.uuid4())
date_compact = date_str.replace("-", "")

lines.extend([
"BEGIN:VEVENT",
f"UID:{uid}",
f"DTSTART;VALUE=DATE:{date_compact}",
f"DTEND;VALUE=DATE:{date_compact}",
f"SUMMARY:{escape_ics(summary)}",
f"DESCRIPTION:{escape_ics(detail)}",
f"DTSTAMP:{timestamp}",
"TRANSP:TRANSPARENT",
"END:VEVENT",
])

lines.append("END:VCALENDAR")
return "\r\n".join(lines) + "\r\n"


def main():
parser = argparse.ArgumentParser(description="微信支付 XLSX 账单 → ICS 日历")
parser.add_argument("xlsx", help="微信支付账单 XLSX 文件路径")
parser.add_argument("-o", "--output", help="输出 ICS 文件路径(默认: 输入文件名.ics)")
args = parser.parse_args()

xlsx_path = Path(args.xlsx)
if not xlsx_path.exists():
print(f"❌ 文件不存在: {xlsx_path}")
exit(1)

print(f"📖 读取: {xlsx_path}")
nick_name, daily = parse_xlsx(str(xlsx_path))

if not daily:
print("❌ 未找到有效的交易记录")
exit(1)

total_income = sum(v["income"] for v in daily.values())
total_expense = sum(v["expense"] for v in daily.values())
total_net = total_income - total_expense
print(f"👤 昵称: {nick_name or '未知'}")
print(f"📅 日期范围: {min(daily.keys())} ~ {max(daily.keys())} ({len(daily)}天)")
print(f"💰 总收入: ¥{total_income:.2f}")
print(f"💸 总支出: ¥{total_expense:.2f}")
print(f"📊 净收支: ¥{total_net:.2f}")

ics_content = build_ics(nick_name, daily)

output_path = args.output or xlsx_path.with_suffix(".ics")
with open(output_path, "w", encoding="utf-8") as f:
f.write(ics_content)

print(f"✅ 已导出: {output_path}")
print(f" {len(daily)} 个日历事件")
print()
print("📌 双击 .ics 文件即可导入到 macOS/iOS 日历。")


if __name__ == "__main__":
main()

这个是项目地址,如果你对这个玩具感兴趣,可以下载来玩一下。项目后续我打算继续维护,因为我正在用着,每天导出账单、双击导入日历已经成为我日常记账的习惯流程了。我觉得它本身已经很完美了,因为它真的很简单,整个设计理念就是极简实用——没有花哨的图表分析,没有复杂的账目分类,就像是你在纸质日历上写字那样纯粹直接,打开日历就能看到每天的收入支出概况,一目了然,干净利落。