使用APScheduler开启定时任务

前言

相比基于Linux的crontab定时任务模块来说,在Python中使用APScheduler创建定时任务更加方便、精确,编写方式也更符合Python风格,毕竟crontab里的各种**看得我心慌。且APScheduler也更适用于Django、Flask环境中添加动态任务。这篇文章就APScheduler 的使用做个简单总结。

从一个简单示例开始

先写个简单定时器示例,从代码中理解APScheduler的使用方法:

from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.executors.pool import ProcessPoolExecutor
# 执行器配置:使用线程池运行定时任务,且最大线程数为5个
executors = {
    'default': ThreadPoolExecutor(max_workers=5)
}
# 配置一个调度器对象
scheduler = BackgroundScheduler(executors=executors)
# 给调度器添加定时任务task_func, 指定每日的3点运行一次
scheduler .add_job(task_func, "cron", hour=3)
# 开启定时任务
scheduler .start()

以上便是创建一个每日定时运行一次的定时任务的基本方式。

配置说明

使用apscheduler配置并开启定时任务的方法大致了解后,再去了解更多的配置项和对应实现的功能。

  • 安装方式:pip install apscheduler
1. 调度器Scheduler

要实现定时任务,首先需要初始化一个调度器对象,例如上例中使用的调度器为BackgroundScheduler类,只需scheduler = BackgroundScheduler()便创建出一个调度器对象。

  • BackgroundScheduler类

导入方式:from apscheduler.schedulers.background import BackgroundScheduler

适用于在框架环境中使用,如Django、Flask中,该调度器实例在调用.start()方法后不会发生阻塞,而是如其名一样在后台运行,不影响框架中其他代码的执行。

  • BlockingScheduler类

from apscheduler.schedulers.blocking import BlockingScheduler

适用于作为独立进程时使用,在自己定义的脚本程序中使用BlockingScheduler类更加方便,该调度器实例在调用.start()方法后会阻塞,等待下一个定时时间点的到来再继续执行。

2. 执行器executors

在示例中使用的executor是线程池ThreadPoolExecutor的方式,对应着,当然也可以使用进程池ProcessPoolExecutor的方式。
其内部调用的本身是Python内置的concurrent.futures模块下的ThreadPoolExecutor及ProcessPoolExecutor类。

  • ThreadPoolExecutor线程池
    以多线程的方式运行定时任务,一般情况使用该方案即可。
  • ProcessPoolExecutor进程池
    以多进程的方式运行定时任务,当任务为CPU密集型时,应考虑选择该方案,当然,该方案会更加消耗主机资源。

使用方法如示例所示,以字典方式定义执行器,传入最大线程或进程数后,在调度器实例中指定,或使用.add_executor()添加即可。

  executors = {
      'default': ThreadPoolExecutor(20)
  }
  scheduler = BackgroundScheduler(executors=executors)
# 也可使用下面的方式添加执行器
# scheduler.add_executor()
3. 触发器Trigger

在使用sched.add_job()方法给调度器添加任务时,需要传入定时启动的方式和规定的运行时间。
以下是add_job方法源码中定义可传入的参数:

def add_job(self, func, trigger=None, args=None, kwargs=None, id=None, name=None,
                misfire_grace_time=undefined, coalesce=undefined, max_instances=undefined,
                next_run_time=undefined, jobstore='default', executor='default',
                replace_existing=False, **trigger_args):

第一个参数func为需要定时执行的任务函数名;
第三、四个参数为定时任务func的参数,如原func函数有参数,以列表或字典的形式传入即可。
第二个trigger即为触发器,指定执行任务的时机,常见的trigger参数有以下3个,具体用法释义在每个代码注释中包含:

  • 1)date 在特定的时间日期执行
 from datetime import date

# 在2019年11月6日00:00:00执行
scheduler.add_job(my_job1, 'date', run_date=date(2009, 11, 6))

# 在2019年11月6日16:30:05执行,两种写法均可
scheduler.add_job(my_job2, 'date', run_date=datetime(2009, 11, 6, 16, 30, 5))
scheduler.add_job(my_job3, 'date', run_date='2009-11-06 16:30:05')

# 立即执行,不指定具体时间时会立即执行任务
scheduler.add_job(my_job4, 'date')  
scheduler.start()
  • 2)interval 经过指定的时间间隔执行
from datetime import datetime

# 每两小时执行一次
scheduler.add_job(job_function, 'interval', hours=2)

# 在2019年10月10日09:30:00 到2020年6月15日的时间内,每两小时执行一次
scheduler.add_job(job_function, 'interval', hours=2, start_date='2019-10-10 09:30:00', end_date='2020-06-15 11:00:00')

interval可使用的时间参数如下:

weeks (int) – 等待的周数
days (int) – 等待的天数
hours (int) – 等待的小时数

minutes (int) – 等待的分钟数
seconds (int) – 等待的秒数
start_date (datetime|str) – 区间计算的起点时间
end_date (datetime|str) – 区间计算的终止时间
timezone (datetime.tzinfo|str) – 用于日期/时间计算的时区

  • 3)cron按指定的周期执行
    在最初的示例中使用的便是该类触发器,按指定的周期循环运行。
# 在每天的3:00点运行
scheduler.add_job(task_func, "cron", hour=3)

# 在每个6、7、8、11、12月的第三个周五的00:00, 01:00, 02:00和03:00 执行
scheduler.add_job(job_function, 'cron', month='6-8,11-12', day='3rd fri', hour='0-3')

# 在2019年5月30日前的周一到周五的5:30执行
scheduler.add_job(job_function, 'cron', day_of_week='mon-fri', hour=5, minute=30, end_date='2019-05-30')

cron可使用的时间参数如下:

year (int|str) – 年(4位数的年份)
month (int|str) – 月(1-12)
day (int|str) – 日 (1-31)
week (int|str) – ISO周日历 (1-53)
day_of_week (int|str) – 英文简写或数字表示的星期几 (0-6 或者 mon,tue,wed,thu,fri,sat,sun)
hour (int|str) – 时 (0-23)
minute (int|str) – 分 (0-59)
second (int|str) – 秒 (0-59)
start_date (datetime|str) – 最早可能触发的日期/时间(包括在内)
end_date (datetime|str) – 最晚可能触发的日期/时间(包括在内)
timezone (datetime.tzinfo|str) – 用于日期/时间计算的时区 (默认为调度程序的时区)

触发器的参数看起来内容太多,但其实都不用记住,只需在明确需求后去上述参数中查找,组合并设置自己想指定的定时方案即可。

4. 任务储存器

除以上3个核心功能外,定时任务还可以指定持久储存方案。
默认的储存器使用的是使用内存,当任务意外中断,重启后定义的任务也会重新被添加到调度器,简单而高效;
但当作业中断需要从中断中恢复任务,则在配置时应该使用数据库来作为任务储存器,具体使用什么数据库,可以自行选择SQLite、MongoDB、Redis。
定义方法如下,这里不做过多介绍,如需使用,再查找相关文档:

from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore
jobstores = {
    'mongo': {'type': 'mongodb'},
    'default': SQLAlchemyJobStore(url='sqlite:///jobs.sqlite')
}
scheduler = BackgroundScheduler(executors=executors, jobstores=jobstores)

以上。

最后,如果需要创建的定时任务太多,应该了解学习下RabbitMQ异步队列的使用。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容

  • 一点知识 在JAVA开发领域,目前可以通过以下几种方式进行定时任务: Timer:jdk中自带的一个定时调度类,可...
    雅倩兰爸爸阅读 3,743评论 1 28
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,034评论 1 32
  • 高阶函数:将函数作为参数 sortted()它还可以接收一个key函数来实现自定义的排序,reversec参数可反...
    royal_47a2阅读 624评论 0 0
  • 7月14日 现在的状态用迷茫二字最为贴切, 现在的我每天诚惶诚恐的工作、生活。 上班每天小心翼翼,生怕出现一点问题...
    度浩阅读 221评论 2 1
  • 郁江大桥 1976年,郁江河上迎来了第一座大桥,时隔41年,这座桥经历了诸多磨难,破败不堪,已经不能承受车子的碾压...
    种豆南下阅读 221评论 0 0