在编写某些脚本的时候往往需要使用到定时执行任务,定时执行任务可以通过下发多进程或者使用某些调度算法实现,在python中为开发者提供了一个库可以快速实现定时任务并且不让主进程受到干扰。这个库就叫做:APScheduler

简介

dvanced Python Scheduler (APScheduler) 是一个Python库,可实现延迟调度要执行Python代码的功能,可以只执行一次,也可以定期执行。可以随时添加新任务或删除旧任务。如果将job任务存储在数据库中,这些任务还将在重新启动调度程序后保持它们的状态并继续运行。当重新启动调度程序时,它将运行离线时应该运行的所有job任务,这个功能可以让程序弥补由于外部情况损失的任务。这个库有四个组件:

  • triggers触发器 : 包含调度逻辑,每一个job有它自己的触发器,用于决定job下一次运行时间。除了初始配置外,触发器完全是无状态的。
  • job stores作业存储 : 存储被调度的job,默认的job存储是简单地把job存储在内存中,其他的job存储是保存在数据库中。Job的数据在保存到持久化存储时被序列化,并在加载时进行反序列化。job存储(默认存储除外)不将job数据保存在内存中,而是充当后台保存、加载、更新和搜索job的中间人。job存储永远不能在调度程序之间共享。
  • executors执行器 : 负责处理job的运行,通过将job中指定的可调用对象 提交给一个线程或进程池来运行。当job完成时,执行器将会通知调度器,然后调度程序发出相应event。
  • schedulers调度器 : 一个应用程序中通常只有一个调度器在运行,应用程序开发人员通常不会直接处理job存储、执行器和触发器,相反,调度器程序提供了处理这些事件的接口。
    配置job存储和执行器都是在调度器中完成,例如添加、修改和移除job。

简单实用

APScheduler能够很轻易的实现定时任务的三个步骤:

  • 新建scheduler调度器
  • 向调度器添加一个job调度任务
  • 运行job调度任务

即创建任务,添加任务,启动任务

案例一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 使用blocking阻塞调度程序来调度每隔3秒执行一次的作业
from datetime import datetime
import os
from apscheduler.schedulers.blocking import BlockingScheduler
def tick():
print('Tick! The time is: %s' % datetime.now())

if __name__ == '__main__':
scheduler = BlockingScheduler()
scheduler.add_job(tick, 'interval', seconds=3)
print('Press Ctrl+{0} to exit'.format('Break' if os.name == 'nt' else 'C'))
try:
scheduler.start()
except (KeyboardInterrupt, SystemExit):
pass

但是这个案例是会阻塞程序运行的,本质上与for循环实现的功能差不多一般情况下大多数人不会在有这样需求的情况下考虑它,多数时候我们希望我们是任务在后台被调度,不会阻塞程序的正常执行,如下

案例二:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from datetime import datetime
import time
import os
from apscheduler.schedulers.background import BackgroundScheduler
def tick():
print('Tick! The time is: %s' % datetime.now())

if __name__ == '__main__':
scheduler = BackgroundScheduler()
scheduler.add_job(tick, 'interval', seconds=3)
scheduler.start()
print('Press Ctrl+{0} to exit'.format('Break' if os.name == 'nt' else 'C'))
try:
# This is here to simulate application activity (which keeps the main thread alive).
while True:
time.sleep(2)
except (KeyboardInterrupt, SystemExit):
# Not strictly necessary if daemonic mode is enabled but should be done if possible
scheduler.shutdown()

这个调度器实现了同样的功能,也是每三秒执行一次,但是调度程序不会阻塞主进程的正常进行,更多调度方法如下:

  • BlockingScheduler : 调度器在当前进程的主线程中运行,也就是会阻塞当前线程。
  • BackgroundScheduler : 调度器在后台线程中运行,不会阻塞当前线程。(在没有使用下面5个框架时使用)
  • AsyncIOScheduler : 结合 asyncio 模块(一个异步框架)一起使用。
  • GeventScheduler : 程序中使用 gevent(高性能的Python并发框架)作为IO模型,和 GeventExecutor 配合使用。
  • TornadoScheduler : 程序中使用 Tornado(一个web框架)的IO模型。
  • TwistedScheduler : 配合 TwistedExecutor使用。
  • QtScheduler : 配合 Qt 应用使用。

除了定时任务,当我们程序只需要在某一个特定时间执行一件事情,不需要重复执行的时候,调度器很明显就不够使用了,这时候就需要定时器也叫触发器:

1
2
# 在 2019-03-29 14:00:00 时刻运行一次 job_func 方法
scheduler.add_job(job_func, 'date', run_date=datetime(2019, 3, 29, 14, 0, 0), args=['text'])

触发器同时也支持其他的时间格式:

1
2
3
sched.add_job(my_job, 'date', run_date=date(2019, 3, 29), args=['text'])
sched.add_job(my_job, 'date', run_date='2019-03-29 14:30:05', args=['text'])
sched.add_job(my_job, args=['text']) #立即运行

触发器除了能够定时触发还能够在某一个固定的时间段内执行指定数量的任务:

1
2
# 在 2019-03-29 14:00:01 ~ 2019-03-29 14:00:10 之间, 每隔两分钟执行一次job_func方法。
scheduler.add_job(job_func, 'interval', minutes=2, start_date='2019-03-29 14:00:01' , end_date='2019-03-29 14:00:10')

以及周期性触发:

1
2
# 在2019-03-30 00:00:00之前,每周一到周五的5:30(am)触发
sched.add_job(job_function, 'cron', day_of_week='mon-fri', hour=5, minute=30, end_date='2019-03-30')