文章目录
一、简单实现
1、sleep
精度低(受系统调度影响)、阻塞主线程、程序退出后任务停止。
py
import time
# 定义要定时执行的任务
def task():
print(f"任务执行时间:{time.strftime('%Y-%m-%d %H:%M:%S')}")
# 定时任务主逻辑(每隔5秒执行一次)
if __name__ == "__main__":
interval = 5 # 执行间隔(秒)
while True:
task()
# 等待指定时间后再执行下一次
time.sleep(interval)
2、crontab系统级
bash
# 编辑crontab规则
crontab -e
# 添加规则(每分钟执行test.py)
* * * * * /usr/bin/python3 /path/to/test.py
二、schedule库
官方文档:https://schedule.readthedocs.io/en/stable/index.html
bash
# 安装
pip install schedule
1、使用
schedule.every(时间).单位.do(任务函数):核心语法,定义定时规则;
schedule.run_pending():检查并执行到期的任务;
优点:语法直观、支持丰富的定时规则(按秒 / 分 / 时 / 日 / 周 / 月);
缺点:仍需主线程循环、不支持任务持久化、无法处理任务并发。
py
import schedule
import time
import datetime
# 定义定时任务
def daily_task():
print(f"每日任务执行:{datetime.datetime.now()}")
def every_10_seconds_task():
print(f"每10秒任务执行:{datetime.datetime.now()}")
# 配置定时规则
if __name__ == "__main__":
# 1. 每隔10秒执行一次
schedule.every(10).seconds.do(every_10_seconds_task)
# 2. 每天固定时间(比如8点30分)执行
schedule.every().day.at("08:30").do(daily_task)
# 3. 每周一执行
schedule.every().monday.do(daily_task)
# 4. 每分钟的第15秒执行
schedule.every().minute.at(":15").do(every_10_seconds_task)
# 持续运行调度器
while True:
schedule.run_pending() # 检查是否有任务需要执行
time.sleep(1) # 每秒检查一次
三、APScheduler库
官方文档:https://pypi.org/project/APScheduler/
文档:https://apscheduler.readthedocs.io/en/master/?badge=3.x
bash
# 安装
pip install apscheduler
APScheduler的核心由4部分组成,理解它们是使用的基础:
调度器(Scheduler) :整个库的核心,负责管理任务的注册、触发、执行。不同调度器适配不同场景(见下文案例)。
触发器(Trigger) :定义任务的执行时间规则,核心有3种:
date:一次性触发(指定具体时间执行1次)。
interval:固定时间间隔触发(如每5分钟执行)。
cron:类Unix cron表达式触发(支持复杂规则,如每周一8点)。
作业存储(Job Store) :保存任务的信息,默认是MemoryJobStore(内存存储,重启丢失),也支持Redis/MongoDB/SQLAlchemy(数据库)等持久化存储。
执行器(Executor) :负责执行任务,默认ThreadPoolExecutor(线程池),也可使用ProcessPoolExecutor(进程池,适合CPU密集型任务)。
场景1:一次性任务(date触发器)
适用场景:需要在某个具体时间点执行一次的任务(如2025年1月1日0点执行新年祝福发送、定时执行一次数据备份)。
python
from apscheduler.schedulers.blocking import BlockingScheduler
from datetime import datetime, timedelta
# 定义要执行的任务
def one_time_task(name):
print(f"一次性任务执行:{name},当前时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
# 创建阻塞式调度器(适用于独立脚本)
scheduler = BlockingScheduler()
# 添加一次性任务:3秒后执行(也可指定具体时间,如datetime(2025, 1, 1, 0, 0, 0))
run_time = datetime.now() + timedelta(seconds=3)
scheduler.add_job(
one_time_task, # 要执行的函数
'date', # 触发器类型
run_date=run_time, # 执行时间(datetime对象/字符串均可)
args=["新年数据备份"], # 传递给函数的参数
id="one_time_job" # 任务ID,用于后续管理
)
print(f"调度器已启动,任务将在 {run_time.strftime('%Y-%m-%d %H:%M:%S')} 执行")
try:
scheduler.start() # 启动调度器(阻塞主线程)
except KeyboardInterrupt:
# 按Ctrl+C停止调度器
scheduler.shutdown()
print("调度器已停止")
BlockingScheduler是阻塞式调度器,启动后主线程被占用,适合独立的定时脚本。
run_date支持字符串格式(如"2025-01-01 00:00:00"),无需手动构建datetime对象。
场景2:固定时间间隔任务(interval触发器)
适用场景:每隔固定时间执行一次的任务(如每5分钟检查服务器状态、每1小时同步一次数据)。
python
from apscheduler.schedulers.blocking import BlockingScheduler
from datetime import datetime, timedelta
# 定义间隔执行的任务
def interval_task():
print(f"间隔任务执行,当前时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
scheduler = BlockingScheduler()
# 添加间隔任务:每2秒执行一次,20秒后停止
scheduler.add_job(
interval_task,
'interval',
seconds=2, # 间隔单位(支持seconds/minutes/hours/days/weeks)
start_date=datetime.now(), # 开始时间(默认立即开始)
end_date=datetime.now() + timedelta(seconds=20), # 结束时间
max_instances=1, # 最多同时运行1个实例(避免任务叠加)
id="interval_job"
)
print("间隔任务调度器已启动(每2秒执行,20秒后停止),按Ctrl+C退出")
try:
scheduler.start()
except KeyboardInterrupt:
scheduler.shutdown()
print("调度器已停止")
max_instances是核心参数,避免短间隔任务(如1秒)未执行完又触发新实例,导致资源耗尽。
支持start_date/end_date精准控制任务的生命周期。
场景3:CRON表达式任务(cron触发器)
适用场景:复杂时间规则的定时任务(如每天8点打卡、每周一18点统计数据、每月1号凌晨2点备份),是最常用的触发器。
CRON表达式格式(与Linux cron一致):秒 分 时 日 月 周 年(年可选),支持通配符*(任意)、*/n(每隔n)、-(范围)、,(枚举)。
python
from apscheduler.schedulers.blocking import BlockingScheduler
from datetime import datetime
# 定义CRON任务
def cron_task():
print(f"CRON任务执行,当前时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
scheduler = BlockingScheduler()
# 案例1:每分钟的第30秒执行(如 10:00:30、10:01:30...)
scheduler.add_job(cron_task, 'cron', second=30, id="cron_job1")
# 案例2:每天早上8点整执行(取消注释即可测试)
# scheduler.add_job(cron_task, 'cron', hour=8, minute=0, second=0, id="cron_job2")
# 案例3:每周一、三、五的18:30:00执行
# scheduler.add_job(cron_task, 'cron', day_of_week='1,3,5', hour=18, minute=30, second=0, id="cron_job3")
# 案例4:每月1号和15号的凌晨2点执行
# scheduler.add_job(cron_task, 'cron', day='1,15', hour=2, minute=0, second=0, id="cron_job4")
print("CRON任务调度器已启动(每分钟30秒执行),按Ctrl+C退出")
try:
scheduler.start()
except KeyboardInterrupt:
scheduler.shutdown()
print("调度器已停止")
常用CRON参数速查:
second:秒(0-59)
minute:分(0-59)
hour:时(0-23)
day:日(1-31)
month:月(1-12)
day_of_week:周(0-6,0=周日,或用mon/tue/wed等)
场景4:带参数的定时任务
适用场景:任务需要动态传入参数(如根据不同用户ID发送提醒、处理不同数据源)。
python
from apscheduler.schedulers.blocking import BlockingScheduler
from datetime import datetime
# 带参数的任务函数
def param_task(user_id, message):
print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 向用户 {user_id} 发送消息:{message}")
scheduler = BlockingScheduler()
# 方式1:位置参数(args)
scheduler.add_job(
param_task,
'interval',
seconds=3,
args=[1001, "您的订单已发货"], # 列表形式传递位置参数
id="param_job1"
)
# 方式2:关键字参数(kwargs)
scheduler.add_job(
param_task,
'interval',
seconds=3,
kwargs={"user_id": 1002, "message": "您的会员即将到期"}, # 字典形式传递关键字参数
id="param_job2"
)
print("带参数的任务调度器已启动,按Ctrl+C退出")
try:
scheduler.start()
except KeyboardInterrupt:
scheduler.shutdown()
print("调度器已停止")
args和kwargs可单独/结合使用,但需保证参数与任务函数的定义匹配。
场景5:任务的暂停、恢复、移除
适用场景:动态管理任务(如临时暂停某个任务排查问题、不再需要的任务直接移除)。
python
from apscheduler.schedulers.blocking import BlockingScheduler
from datetime import datetime
import time
# 测试任务
def dynamic_task():
print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 动态任务执行")
scheduler = BlockingScheduler()
# 添加任务
scheduler.add_job(dynamic_task, 'interval', seconds=2, id="dynamic_job")
# 启动调度器
scheduler.start(paused=False)
print("任务已启动,5秒后暂停...")
time.sleep(5)
# 暂停任务(通过任务ID)
scheduler.pause_job("dynamic_job")
print("任务已暂停,5秒后恢复...")
time.sleep(5)
# 恢复任务
scheduler.resume_job("dynamic_job")
print("任务已恢复,5秒后移除...")
time.sleep(5)
# 移除任务
scheduler.remove_job("dynamic_job")
print("任务已移除,5秒后停止调度器...")
time.sleep(5)
# 停止调度器
scheduler.shutdown()
print("调度器已停止")
核心方法:
pause_job(job_id):暂停指定任务
resume_job(job_id):恢复暂停的任务
remove_job(job_id):永久移除任务
get_job(job_id):获取任务详情(返回Job对象)
场景6:非阻塞调度器(BackgroundScheduler)
适用场景:Web应用(Flask/Django)、后台服务(不希望阻塞主线程)。
python
from apscheduler.schedulers.background import BackgroundScheduler
from datetime import datetime
import time
# 后台任务
def background_task():
print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 后台任务执行")
# 创建后台调度器(不阻塞主线程)
scheduler = BackgroundScheduler()
# 添加间隔任务
scheduler.add_job(background_task, 'interval', seconds=2, id="background_job")
# 启动调度器(非阻塞,主线程继续执行)
scheduler.start()
print("后台调度器已启动,主线程继续执行其他逻辑...")
# 主线程模拟Web服务的核心逻辑
try:
while True:
time.sleep(1)
print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 主线程运行中...")
except KeyboardInterrupt:
scheduler.shutdown()
print("调度器已停止,主线程退出")
BackgroundScheduler启动后不阻塞主线程,是Web应用的首选。
主线程退出后,后台调度器也会停止,需保证主线程持续运行(如Flask的app.run())。
场景7:任务异常处理
适用场景:避免单个任务出错导致调度器崩溃,或记录任务异常日志。
python
from apscheduler.schedulers.blocking import BlockingScheduler
from datetime import datetime
import logging
# 配置日志(记录异常)
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# 模拟会抛出异常的任务
def error_task():
logger.info("开始执行可能出错的任务")
1 / 0 # 故意触发除零异常
logger.info("任务执行完成")
# 自定义异常监听器
def job_exception_listener(event):
if event.exception:
logger.error(f"任务 {event.job_id} 执行失败:{event.exception}", exc_info=True)
else:
logger.info(f"任务 {event.job_id} 执行成功")
scheduler = BlockingScheduler()
# 添加任务
scheduler.add_job(error_task, 'interval', seconds=3, id="error_job")
# 监听任务执行事件(成功/失败)
scheduler.add_listener(job_exception_listener, eventtypes=['job_executed', 'job_error'])
print("带异常处理的调度器已启动,按Ctrl+C退出")
try:
scheduler.start()
except KeyboardInterrupt:
scheduler.shutdown()
print("调度器已停止")
即使任务抛出异常,调度器仍会继续运行,不会整体崩溃。
通过eventtypes可精准监听指定类型的事件(如仅监听错误job_error)。
场景8:任务持久化(SQLAlchemyJobStore)
适用场景:调度器重启后任务不丢失(如服务重启后,定时任务自动恢复)。
python
from apscheduler.schedulers.blocking import BlockingScheduler
from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore
from datetime import datetime
# 配置作业存储:使用SQLite数据库(支持MySQL/PostgreSQL)
job_stores = {
'default': SQLAlchemyJobStore(url='sqlite:///jobs.db') # 任务存储在jobs.db文件中
}
# 创建调度器并指定作业存储
scheduler = BlockingScheduler(jobstores=job_stores)
# 定义持久化任务
def persistent_task():
print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 持久化任务执行")
# 添加任务(自动保存到数据库)
scheduler.add_job(
persistent_task,
'interval',
seconds=3,
id="persistent_job",
replace_existing=True # 避免重复添加同名任务
)
print("持久化任务调度器已启动,任务信息保存在jobs.db中,按Ctrl+C退出")
try:
scheduler.start()
except KeyboardInterrupt:
scheduler.shutdown()
print("调度器已停止,任务信息仍保存在数据库中,重启后可恢复")
支持多种数据库:
MySQL:mysql+pymysql://user:password@host:port/dbname
PostgreSQL:postgresql://user:password@host:port/dbname
重启调度器后,任务会从数据库自动加载,无需重新注册。