schedule.every()后必须调用run_pending(),否则任务不会执行;需配合while True循环和time.sleep(1)定期检查,避免阻塞、时间表达错误及退出未清理等问题。

schedule.every() 后必须调用 run_pending()
很多人写完 schedule.every(10).minutes.do(job) 就以为任务会自动执行,结果发现什么都没发生——根本原因是 schedule 不启动后台线程,它只负责“记事本”,不负责“翻日历”。你得自己每轮主动检查:schedule.run_pending()。漏掉这句,任务永远卡在待办列表里。
典型写法是套个无限循环加休眠:
import schedule
import time
<p>def job():
print("任务执行中")</p><p>schedule.every(10).minutes.do(job)</p><p>while True:
schedule.run_pending()
time.sleep(1) # 每秒检查一次,别用 0(空转耗 CPU)</p>
注意:time.sleep(1) 是平衡精度与开销的关键——设太小(如 0.1)会徒增 CPU 占用;设太大(如 60)可能错过触发窗口。
避免在定时任务里直接写阻塞操作
比如你在 job() 里调用 requests.get() 或 subprocess.run(),一旦网络超时或子进程卡住,整个 while True 循环就会被拖住,后续所有定时任务全部延迟甚至停摆。
稳妥做法是把耗时/不可控操作移到线程或异步逻辑中:
- 简单场景:用
threading.Thread(target=job_body).start()快速解耦 - IO 密集型:改用
asyncio+aiohttp,再配合schedule的run_all()手动触发(注意schedule本身非异步) - 生产环境:直接换
APScheduler,它原生支持线程池和异步 executor
时间表达易错点:at() 和 every(n).unit 的混用
schedule.every().day.at("10:30") 是合法的,但 schedule.every(2).days.at("10:30") 会报 AttributeError: 'Job' object has no attribute 'at' ——因为 at() 只支持单位为 day、hour、minute 的单次周期,不支持复数形式(days、hours)。
正确写法只有两种:
- 每天固定时间:
schedule.every().day.at("10:30") - 每隔 N 天执行(不指定钟点):
schedule.every(2).days.do(job)
想实现“每 48 小时执行一次且在上午 10:30”,得自己算下次时间戳,用 schedule.every().seconds.do(job).tag('interval') 配合手动 schedule.cancel_job() + 重新 every().seconds.do() 设置,schedule 库本身不支持这种组合。
退出时记得清理,否则 Ctrl+C 可能卡死
在终端运行时按 Ctrl+C,Python 默认抛 KeyboardInterrupt,但如果你的 job() 正在做文件写入、数据库连接或网络请求,没加 try/except 就可能让资源没释放干净,下一次运行时报 “Address already in use” 或 “Permission denied”。
更隐蔽的问题是:schedule.clear() 不会自动调用,未取消的任务仍留在内存里。建议加个退出钩子:
import signal
import sys
<p>def exit_handler(signum, frame):
schedule.clear()
print("定时任务已清理,正在退出")
sys.exit(0)</p><p>signal.signal(signal.SIGINT, exit_handler)</p>
实际部署时,真正麻烦的往往不是怎么写,而是怎么让这个 while 循环不被 systemd 杀掉、不因日志满盘而静默失败、不因时区切换误触发——这些都得靠外部机制兜底,schedule 本身只管“该不该现在跑”。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











