☰
Python自动提醒工具:从日程管理到桌面通知的实用指南
2026/10/21 8:46:15 网站建设 项目流程

1. 项目概述与思路拆解

1.1 这个工具解决的究竟是什么问题

先说个我自己的经历。前阵子有个重要客户约了周五上午十点线上对需求,我周三晚上记在便签纸上,周四忙了一天完全忘了这回事,周五早上九点四十才被同事问起,赶紧打开电脑找会议链接,手忙脚乱。事后我反思了一下,问题不在于我“不想记”,而在于“手动设置提醒”这个动作本身就太太太容易断了:要么当时没顺手设,要么设了但手机没电没提示,要么设了但没设提前量,到点才弹出来,人还在路上。手动设置日程提醒,本质上依赖的是“你记得去设置”这件事,而“记得设置”恰恰就是最容易出问题的环节。

所以我想法很简单:能不能让电脑自己盯着时间,到了点自动提醒我?把“记住要设提醒”这件事从人身上彻底剥离开。项目标题里说的“Python自动设闹钟,重要事不忘记”,核心就是这个思路:用一段Python脚本常驻后台,读一个本地日程表,自己判断时间,自己弹通知,自己放声音,全程不需要我手动干预。

这个工具适合谁?被琐事淹没的上班族、经常忘记给家里人打电话的、学生党考试周需要多节点提醒的,或者单纯想在电脑上折腾一个自己能看懂的小工具的人。不需要你会写很复杂的代码,我下面给的代码可以直接抄,改改日程就能跑。你需要的只是一台能装Python的环境,Windows和macOS都能跑。

1.2 技术选型:三种方案横评

做这个工具之前我掂量过三条路:系统自带任务计划、第三方提醒软件、自己写Python脚本。三条路我都试过,各有各的毛病,这里直接说结论。

第一,用Windows任务计划程序或者macOS的cron,确实能定时触发脚本,但配置起来绕,而且它擅长的是“按固定时间运行某程序”,要做“提前提醒”“循环提醒”“临时加一条日程”这种灵活操作,逻辑写起来别扭,改一次时间还得再进面板折腾一次。

第二,用现成提醒软件,像手机自带的日历、电脑上的各种待办工具,功能确实全,但问题是要先把日程一项一项录进去。我需要的不是“功能最全的记事本”,而是“不经过人手、自己就能跑起来的提醒机”。第三方的日程录入依然靠手动,绕不开我前面说的那个断点。

第三,Python脚本常驻,用一行while True循环,每隔几十秒读一次本地日程文件,到了时间就弹通知、放声音。这个方案最轻、最透明、完全可控,而且日程数据就是一个人能看懂的JSON文件,用记事本都能改。你可能会问:挂个Python进程在后台,会不会很占资源?实测一个空循环加条件判断,占用内存大概在30到50MB之间,CPU接近零,日常使用完全无感。

我最后选了Python,不是因为它最“高级”,恰恰是因为它最“笨”——笨到所有流程都摆在你面前,哪一天出问题了,你能清清楚楚看到是哪一步断了。

2. 核心细节解析与设计取舍

2.1 日程数据结构:一个JSON文件怎么设计才够用

这个项目的核心数据只有一样:日程。我在设计数据结构时遵循一个原则——用最简单的格式,存足够的信息,但不要过度设计。下面是我用的JSON结构,每条日程有五个字段,先贴出来看:

{ "tasks": [ { "id": 1, "time": "2026-04-10 10:00:00", "title": "与客户线上对需求", "advance_minutes": 15, "notified": false }, { "id": 2, "time": "2026-04-11 14:30:00", "title": "提交项目周报", "advance_minutes": 30, "notified": false } ] }

字段看起来简单,但每个字段都是我踩过坑之后的取舍结果。

time存的是“事件的绝对时间”,格式是年-月-日 时:分:秒。为什么不用时:分这种更简短的格式?因为一旦跨天,比如晚上十一点设定明早九点的提醒,你就要额外写逻辑判断“明天”是哪一天,还不如直接存完整时间戳,Python标准库的datetime.strptime可以直接解析,省掉一堆边界判断。

advance_minutes是提前量,单位是分钟。这个是后来加的。一开始我写死提醒时间为事件前15分钟,后来发现场景根本不一样:开会提醒提前15分钟合适,吃药提醒得提前5分钟,而还信用卡这种得提前一天。把提前量作为每条日程的独立字段以后,灵活性直接拉满,同一份日程表里可以同时存在“提前5分钟”“提前30分钟”“提前一天”三种不同粒度的提醒。

notified这个布尔值是最容易被忽略但最关键的一个字段。它是“是否已经提醒过”的标记。你想想,如果没有这个标记,程序每30秒检查一次,10:00的事件9:30触发了提醒,9:31再检查一次,又触发一次,一上午能给你弹二十个通知。加上notified以后,每完成一次提醒就把这个标记改回true,后续循环检查到这个字段就直接跳过。这个设计看起来不起眼,但它是“自动闹钟”和“循环轰炸机”之间的分水岭。

2.2 提醒触发逻辑:时间的减法比你以为的更讲究

有了数据结构,核心算法其实就是一道减法题:当前时间距离事件时间还有多久,这个差值小于等于提前量,就触发提醒。

代码逻辑不复杂,但边界情况特别容易翻车。先说最核心的判断条件。假设当前时间是now,事件时间是task_time,提前量为advance_minutes分钟,那么触发条件就是:

remaining = (task_time - now).total_seconds() / 60 if 0 <= remaining <= advance_minutes and not task["notified"]: # 触发提醒

注意这里必须写0 <= remaining而不是remaining > 0。为什么?因为如果程序因为某些原因卡了一下、或者循环间隔恰好跨过了事件点,remaining可能是负的,比如事件已经过去了两分钟。如果你用remaining > 0,这个负值会被漏掉,提醒永远不触发。而用0 <= remaining,至少能保证事件发生后的一小段时间内,如果它还没提醒过,仍然会补上这次提醒。我实测中遇到的“程序被人为暂停了一会儿”的情况,就是靠这个细节兜住的。

再往深了说一步:检查循环的间隔设多少合适?我用了30秒一轮。太短了没必要,CPU白白空转;太长了,比如5分钟,那么你设定的提前量为3分钟时,可能实际提醒时间已经晚了。设成30秒是一个平衡点,能保证提醒误差在30秒以内,对绝大多数场景都足够。

还要补一个“开机补提醒”的逻辑。电脑不是一直开着的,如果事件时间是上午9点,但你8点50才开机,而提前量是15分钟,意味着8点45本该提醒却没提醒。所以我加了一个小函数,程序每次启动时先扫一遍所有日程,把那些“已经过了提醒时间点、但仍未被标记为已提醒”的事件找出来,一次性补发通知。实现起来就是启动时执行一次触发检查,后续再进入正常循环。

2.3 通知形式:弹窗、声音、还是两个都上

日程到了,怎么让你知道?桌面通知加声音,两个缺一不可。只弹通知,如果人不在电脑前,等你回来看见通知可能已经晚了;只放声音,如果人正戴着耳机看视频,声音根本听不出是提醒还是视频里自带的效果。我采用的是弹窗+声音双通道,弹窗保证视觉可达,声音保证听觉刺激。

桌面通知库我用过三个:win10toast、plyer、notify-py,给你们排个雷。win10toast是个老牌库,只在Windows上用,后来某些系统版本下偶尔会弹不出来,送出的通知还会被系统归到“已过期”。plyer是跨平台封装,Windows和macOS都能用,但它在Windows上走的是PowerShell指令弹窗,第一次运行会闪一下黑色控制台窗口,观感一般。notify-py是我目前最满意的,底层直接调用系统的通知接口,Windows上用的是Toast通知,显示效果和原生应用一模一样,而且调用方式很干净。声音这边我用的是winsound,Windows自带的,不用额外装任何东西,代码如下:

import winsound winsound.Beep(880, 1200) # 频率880Hz,持续1200毫秒

如果用的是macOS,winsound就不可用了,可以用os.system('say "时间到了"')调用系统语音,效果反而更醒目。不过我日常主力是Windows,下面代码里我就按Windows环境写了,macOS用户稍微改几个函数名就行。

3. 实操过程与完整实现

3.1 项目骨架:两个文件搞定

整个项目只有两个文件:一个Python脚本,一个JSON日程文件。Python脚本我给它起名叫reminder.py,JSON日程文件叫schedule.json。放到同一个目录下,完事。依赖只有notify-py一个库,安装命令一行:

pip install notify-py

如果你电脑上已经有了Python 3.8以上的环境,这一步就结束了。不需要装什么大型框架,也不需要数据库,这个项目就应该是这么轻量。我见过有人给这种小工具硬上SQLite的,不是不行,但一个几十条日程的小文件用JSON完全够了,开起来用记事本就能改日程,这对“自动化工具”来说本身就是一种优势。

初始的schedule.json可以直接按我2.1节给的样例建,也可以让程序第一次运行时自动创建一个空模板。我建议直接手写样例文件,因为这样你还能顺便理解结构,后面改起来不慌。

3.2 核心代码逐块拆解:从主循环到通知函数

下面我把完整的reminder.py贴出来,每一块后面跟一段解释。你不需要一口气背下来,照着敲就行。

import json import time from datetime import datetime from notifypy import Notify import winsound import os SCHEDULE_FILE = "schedule.json" CHECK_INTERVAL = 30 # 每30秒检查一次 def load_schedule(): """读取日程文件,返回任务列表""" if not os.path.exists(SCHEDULE_FILE): # 如果文件不存在,自动创建一个空模板 with open(SCHEDULE_FILE, "w", encoding="utf-8") as f: json.dump({"tasks": []}, f, ensure_ascii=False, indent=2) return [] with open(SCHEDULE_FILE, "r", encoding="utf-8") as f: data = json.load(f) return data.get("tasks", []) def save_schedule(tasks): """写回日程文件,把notified字段的改动持久化""" with open(SCHEDULE_FILE, "w", encoding="utf-8") as f: json.dump({"tasks": tasks}, f, ensure_ascii=False, indent=2) def send_notification(title, message): """桌面通知 + 声音提醒双通道""" notification = Notify() notification.title = title notification.message = message notification.send() # 声音提醒:880Hz,持续1.2秒 winsound.Beep(880, 1200) def check_tasks(tasks, now): """核心检查逻辑:遍历所有日程,判断是否该提醒""" triggered_any = False for task in tasks: if task.get("notified", False): continue # 已经提醒过,直接跳过 task_time = datetime.strptime(task["time"], "%Y-%m-%d %H:%M:%S") remaining_minutes = (task_time - now).total_seconds() / 60 advance = task.get("advance_minutes", 0) # 关键判断:时间已到或即将到达,且还没提醒过 if remaining_minutes <= advance: send_notification( "日程提醒", f"{task['title']}\n事件时间:{task['time']}" ) task["notified"] = True triggered_any = True if triggered_any: save_schedule(tasks) return tasks def main(): print(f"[提醒服务已启动] 每 {CHECK_INTERVAL} 秒检查一次日程") while True: try: tasks = load_schedule() now = datetime.now() tasks = check_tasks(tasks, now) time.sleep(CHECK_INTERVAL) except KeyboardInterrupt: print("\n[提醒服务已手动停止]") break except Exception as e: print(f"[发生错误] {e},10秒后继续运行") time.sleep(10) if __name__ == "__main__": main()

说几个代码里藏着的细节。

load_schedule里的文件不存在自动创建模板,这个设计是给第一次运行的人准备的。我遇到过一种情况,朋友拷贝了我的脚本但忘了一起拷贝日程文件,一运行直接报错“文件找不到”,体验很糟。让脚本自己生成空模板,至少能保证它跑起来不崩。

strptime解析时间的时候,格式串"%Y-%m-%d %H:%M:%S"必须和JSON里的时间格式完全一致,包括中间的横线和空格。大小写也不能错:%Y是四位数年份,%y是两位数年份,写错了解析出来就是1970年,提醒会全乱套。这个坑我踩过一次,后来仔细对了格式串才明白过来。

winsound.Beep(880, 1200)里的880和1200这两个参数,880是声音频率,单位Hz,1200是持续时间,单位毫秒。880Hz是个比较明亮但不刺耳的音高,1200毫秒等于1.2秒,足够引起注意又不至于吵到人。你可以按自己喜好改,比如换成600Hz会更低沉柔和,换成1500Hz会更尖锐。

还有一处逻辑:check_tasks函数把“保存文件”放在统一处理之后,避免每触发一条就打开一次文件。这个细节看起来琐碎,但当日程条目多、临近同一时间点的事件有好几条时,一次批量改完再写盘,比一条一条反复读写文件要稳得多,也少了很多文件写入冲突的风险。

3.3 开机自启与常驻运行:让程序自己爬起来

脚本写好了,下一步是让它常驻后台,最好开机就自己跑起来,不用每次手动启动。Windows下的做法非常简单:把脚本的快捷方式丢进启动文件夹。

具体操作:按Win + R,输入shell:startup,回车,会打开启动文件夹。然后在reminder.py上右键创建快捷方式,把快捷方式拖进这个文件夹。这样每次登录Windows,系统会自动运行这个脚本,弹出的控制台窗口会一直挂着,充当一个“常驻提醒台”。

但这里有个体验问题:那个黑乎乎的终端窗口会一直占着任务栏。我一开始觉得无所谓,后来发现旁边的同事总问我“你那边怎么老有个黑框”,我就用了一个小技巧,把脚本扩展名从.py改成.pyw。.pyw是Python的“窗口less模式”,运行时不弹出控制台窗口,程序同样在后台跑。具体做法是把reminder.py重命名为reminder.pyw,启动文件夹里的快捷方式也改成指向这个新文件名。这样就彻底安静了。

不过有个副作用要提前告诉你:脚本改成.pyw之后,如果你哪天想手动停掉它,任务栏里找不到窗口,只能打开任务管理器找到对应的Python进程把它结束。我为了省事,在脚本开头加了几行日志,把运行状态写入一个log.txt文件,这样想看它是不是活着,直接翻日志就行。

睡眠模式下程序会不会跑?需要说明一下。Windows进入睡眠后,所有进程都会被挂起,等唤醒后才恢复。这意味着如果你人不在电脑前,电脑自动睡眠了,那么设定的提醒会延迟到你唤醒电脑之后才弹出。解决方案有两个:要么在电源设置里把“睡眠”改成“从不”,适合一直插电的台式机;要么接受这个限制——反正电脑都睡了,人大概率也不在屏幕前,提醒弹出来也没人看。笔记本电脑用户我建议直接在电源计划里把“接通电源时睡眠”设为从不,合盖不睡眠,这样提醒才可靠。

4. 常见问题与排查技巧实录

4.1 问题一:程序出奇地安静,一句话都没提醒过

症状:脚本启动了,控制台也打印了“提醒服务已启动”,日志里看不出任何异常,但到了点就是不弹通知。

这条排查路我走了很久。第一反应是时间判断是不是有问题,我把remaining_minutes打印出来看,值没问题,确实是负数了,但通知就是不弹。最后发现是notify-py库的一个特性:新版在某些Windows版本上,如果系统焦点在某个全屏程序上,通知可能被延迟到用户切回桌面才显示。这个属于系统行为,不是代码问题。

但如果你的Windows版本比较老,还有个真正的坑:winsound.Beep在某些精简版系统上对应的声音服务被禁用了,Beep调用会静默失败,导致你只看到通知不听到声音。排查方法很简单,单独跑一句winsound.Beep(880, 1000),耳朵凑近听一下,没声音就是系统服务问题,代码没问题。

4.2 问题二:时间全乱了,10点的提醒下午才响

这个问题十有八九出在时区或者系统时间格式上。先看一个最隐蔽的坑:如果schedule.json里的时间写的不是2026-04-10 10:00:00这种完整格式,而是2026-04-10 10:00,后面少了秒,strptime解析时就会报格式不匹配。这个报错很容易发现,因为程序会在控制台打印[发生错误]。

更隐蔽的是夏令时问题。你写的10:00:00是墙钟时间,但datetime.now()返回的是系统的本地时间,如果系统启用了夏令时自动调整,跨夏令时切换日的那几天,时间会有1小时的偏差。我去年遇到过一回,周末的提醒全晚了一个小时,排查了半小时才发现是夏令时“偷”了一个小时。解决方式要么在系统设置里关掉自动调整夏令时,要么接受这个每年两度的偏差——反正只影响两天,手动把日程时间调回来也不费事。

4.3 问题三:通知反复弹,想关都关不掉

notified字段就是防止重复通知的,但如果你自己改过schedule.json、或者某次程序异常退出导致文件没保存,就可能在重启后重新提醒一遍已提醒过的事件。这不是逻辑错,而是“持久化不完整”的问题。我后来给save_schedule加了个小改进:每次写文件前先备份到schedule.bak,程序启动时检测到主文件损坏就自动读取备份。虽然20多行的代码量,但肩上心里踏实多了。

这里单独提醒一下用记事本改日程的人:记事本保存文件时,如果编码不是UTF-8,而是ANSI或者GBK,中文字符在程序里会变成乱码,标题显示成“鎺ㄨ崘浼氳”这种。修改保存时注意编码选UTF-8即可,VS Code或者Notepad++这类编辑器默认都会保留原编码,问题不大,Windows自带的记事本新版也已默认UTF-8。

4.4 问题速查表

症状可能原因排查方法
不弹通知也不响铃notify-py在新版系统上有延迟看通知中心是否堆积;全屏应用先切回桌面
只弹通知不响铃系统声音服务被禁用单独跑winsound.Beep验证
提醒时间晚一个小时夏令时切换检查系统时间设置
标题显示乱码日程文件编码不是UTF-8用编辑器另存为UTF-8
提醒过的事件重启后又提醒文件未及时写盘看schedule.bak是否覆盖了主文件
开机后程序没启动快捷方式没放进启动文件夹检查shell:startup文件夹内容

5. 扩展与进阶思路:这个提醒工具还能长成什么样

5.1 日程管理方式的扩展:从改文件到图形界面

现在的schedule.json是手动编辑的,虽然已经够用,但你如果想要个更方便的入口,可以加一个简单的图形界面。我试过两种方案,一种是用tkinter做一个三段式的增删改查窗口:列表显示所有日程,表单填写时间标题和提前量,按钮保存。这大概需要额外80行代码,但换来的是不用每次打开记事本手动对齐JSON格式。另一种方案是做一个系统托盘图标,右键菜单里直接加“新增日程”和“暂停提醒”,这个体验更接近成熟的桌面应用,但需要引入pystray库,复杂度上了一个台阶。

如果你习惯手机操作,把schedule.json放到一个同步盘里,手机上也能改,电脑上的脚本只要检测到文件变动就自动重新加载。改动落地的方式更简单:把脚本的主循环里加一个文件修改时间检查,发现时间戳变了就重读一次日程。这样手机和电脑之间多了一个自动同步的通道,走到哪儿都能添加日程。

5.2 提醒渠道的扩展:从弹窗到邮件和消息推送

桌面通知只在电脑前有效,如果你需要全天候覆盖,可以考虑把通知渠道扩展到手机。最省事的办法就是发邮件:日程触发时调用smtplib给指定的邮箱发一封标题为日程内容的邮件。代码量不多,但需要注意邮箱授权码的问题,现在各大邮箱服务商都要求用授权码而非密码登录SMTP,这一步配置起来稍有点门槛。

更实用的是接入企业微信或者飞书自带的应用机器人。它们都有开放的消息接口,你只需要往一个Webhook地址POST一段JSON文本,机器人就能把消息推到手机上。我做过一个实践:在send_notification函数后面加一个send_webhook分支,日程触发时同时发桌面通知和手机消息,实测在会议室没开电脑的情况下也能收到提醒,覆盖面一下子全了。Webhook方式不需要安装任何Python第三方库,用标准库urllib.request就能发POST请求,很适合扩展。

5.3 我的真实使用体会与最后的提醒

这个项目我前前后后跑了快半年,schedule.json里累计存了上百条日程,从工作上的会议、交报告,到生活中的取快递、还信用卡,都在里面。给我的体感是:它不是一个“效率神器”,更像一个不粘人的管家——平时安静待着不打扰你,到了点就轻轻敲一下门,提醒完了继续安静。稳定性方面,除了几个月前Windows系统大版本更新后需要重新确认启动快捷方式、以及偶尔的夏令时偏差,基本没出过岔子。

最后说几个我实际使用中沉淀下来的小习惯,供参考。第一,所有日程都用完整时间格式,别偷懒省略“年-月-日”只写“时:分”,因为跨天场景真的防不胜防。第二,schedule.json定期备份,我是每周手动复制一份到备份文件夹,有一次文件误删就是靠备份救回来的。第三,脚本更新代码前先备份旧版,不要直接在跑着的脚本上改,改错了整个服务就停了。第四,提醒声音别设太短,我试过600毫秒的嗶声,在嘈杂环境里完全听不见,1200毫秒是经过实测比较可靠的长度。

如果你也想要一个这样的提醒工具,建议别一上来就复制我的全部代码。先按最小可用版本跑通一次——只有一条日程、一个通知弹窗——确认流程没问题之后,再加开机自启、加备份、加Webhook推送,一点一点垒起来。动手做出来的东西才不会被遗忘,希望你的重要事,也从今天开始不再被忘记。

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

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

立即咨询