APScheduler定时任务框架的使用

前言

APScheduler基于Quartz的一个Python定时任务框架,实现了Quartz的所有功能,使用起来十分方便。提供了基于日期、固定时间间隔以及crontab类型的任务,不仅可以添加、删除定时任务,还可以将任务存储到数据库中、实现任务的持久化。基于这些功能,我们可以很方便的实现一个python定时任务系统。

安装

1、利用pip进行安装
$ pip install apscheduler

(python3版本要留意pip和pip3)

2、源码安装
$ python setup.py install

APScheduler有四种组成部分:

triggers(触发器):触发器包含调度逻辑,每一个作业有它自己的触发器,用于决定接下来哪一个作业会运行,除了他们自己初始化配置外,触发器完全是无状态的。
job stores(作业存储):用来存储被调度的作业,默认的作业存储器是简单地把作业任务保存在内存中,其它作业存储器可以将任务作业保存到各种数据库中,支持MongoDB、Redis、SQLAlchemy存储方式。当对作业任务进行持久化存储的时候,作业的数据将被序列化,重新读取作业时在反序列化。
executors(执行器):执行器用来执行定时任务,只是将需要执行的任务放在新的线程或者线程池中运行。当作业任务完成时,执行器将会通知调度器。对于执行器,默认情况下选择ThreadPoolExecutor就可以了,但是如果涉及到一下特殊任务如比较消耗CPU的任务则可以选择ProcessPoolExecutor,当然根据根据实际需求可以同时使用两种执行器。
schedulers(调度器):调度器是将其它部分联系在一起,一般在应用程序中只有一个调度器,应用开发者不会直接操作触发器、任务存储以及执行器,相反调度器提供了处理的接口。通过调度器完成任务的存储以及执行器的配置操作,如可以添加。修改、移除任务作业。

APScheduler提供了七种调度器:

BlockingScheduler:适合于只在进程中运行单个任务的情况,通常在调度器是你唯一要运行的东西时使用。
BackgroundScheduler: 适合于要求任何在程序后台运行的情况,当希望调度器在应用后台执行时使用。
AsyncIOScheduler:适合于使用asyncio异步框架的情况
GeventScheduler: 适合于使用gevent框架的情况
TornadoScheduler: 适合于使用Tornado框架的应用
TwistedScheduler: 适合使用Twisted框架的应用
QtScheduler: 适合使用QT的情况

APScheduler提供了四种存储方式:

MemoryJobStore
sqlalchemy
mongodb
redis

APScheduler提供了三种任务触发器:

data:固定日期触发器:任务只运行一次,运行完毕自动清除;若错过指定运行时间,任务不会被创建
interval:时间间隔触发器,每隔多长时间触发一次
cron:cron风格的任务触发 ,可以每天早上固定时间点执行一次任务

data

固定时间调度(作业只会执行一次)

import time
from apscheduler.schedulers.blocking import BlockingScheduler

def my_job():
    print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())))
sched = BlockingScheduler()
## 采用dete固定时间模式,在特定时间只执行一次
sched.add_job(my_job, 'date', run_date='2019-01-01 00:00:00)
sched.start()

interval

例如每隔五秒执行一次:

import time
from apscheduler.schedulers.blocking import BlockingScheduler

def my_job():
    print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())))
#### 采用interval固定间隔模式,每隔五秒只执行一次
sched.add_job(my_job, 'interval', seconds=5)
sched.start()

间隔调度,参数如下:

weeks (int)  –  间隔几周 
days (int)  –  间隔几天 
hours (int)  –  间隔几小时 
minutes (int)  –  间隔几分钟 
seconds (int)  –  间隔多少秒 
start_date (datetime|str)  –  开始日期 
end_date (datetime|str)  –  结束日期 
timezone (datetime.tzinfo|str)  –  时区 

cron

定时调度(例如在每一天上午八点半或者12点半执行任务)

import time
from apscheduler.schedulers.blocking import BlockingScheduler

scheduler = BlockingScheduler()

def everyday_crawler_job():
    print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())))
    # subprocess.call("python Crawler.py")

sched = BlockingScheduler()
#每隔一天 执行抓包程序
# sched.add_job(everyday_crawler_job, 'interval', days=1)days
#每天早上八点半和十二点半各执行一次抓包程序
sched.add_job(everyday_crawler_job, 'cron', hour='8, 12', minute='30')
sched.start()

参数如下:

(int|str) 表示参数既可以是int类型,也可以是str类型
(datetime | str) 表示参数既可以是datetime类型,也可以是str类型
year (int|str) – 4-digit year -(表示四位数的年份,如2008年)
month (int|str) – month (1-12) -(表示取值范围为1-12月)
day (int|str) – day of the (1-31) -(表示取值范围为1-31日)
week (int|str) – ISO week (1-53) -(格里历2006年12月31日可以写成2006年-W52-7(扩展形式)或2006W527(紧凑形式))
day_of_week (int|str) – number or name of weekday (0-6 or mon,tue,wed,thu,fri,sat,sun) - (表示一周中的第几天,既可以用0-6表示也可以用其英语缩写表示)
hour (int|str) – hour (0-23) - (表示取值范围为0-23时)
minute (int|str) – minute (0-59) - (表示取值范围为0-59分)
second (int|str) – second (0-59) - (表示取值范围为0-59秒)
start_date (datetime|str) – earliest possible date/time to trigger on (inclusive) - (表示开始时间)
end_date (datetime|str) – latest possible date/time to trigger on (inclusive) - (表示结束时间)
timezone (datetime.tzinfo|str) – time zone to use for the date/time calculations (defaults to scheduler timezone) -(表示时区取值)

cron其他参数格式用法示例如下:

#表示2017年3月22日17时19分07秒执行该程序
sched.add_job(my_job, 'cron', year=2017,month = 03,day = 22,hour = 17,minute = 19,second = 07)
 
#表示任务在6,7,8,11,12月份的第三个星期五的00:00,01:00,02:00,03:00 执行该程序
sched.add_job(my_job, 'cron', month='6-8,11-12', day='3rd fri', hour='0-3')
 
#表示从星期一到星期五5:30(AM)直到2014-05-30 00:00:00
sched.add_job(my_job(), 'cron', day_of_week='mon-fri', hour=5, minute=30,end_date='2014-05-30')
 
#表示每5秒执行该程序一次,相当于interval 间隔调度中seconds = 5
sched.add_job(my_job, 'cron',second = '*/5')

在设置定时调度任务时遇到一个问题,定时任务未执行打印的log如下:

Run time of job "everyday_crawler_job (trigger: cron[hour='8,12', minute='30'], next run at: 2019-01-30 08:30:00 CST)" was missed by 0:00:01.084138

这个log的意思是:距离下次运行时间,错过了1秒,所有第二次就没有执行任务
解决方法:
在add_job()中添加参数:
misfire_grace_time: 主要就是为了解决这个was missed by 这个报错,添加允许容错的时间,单位为:s
coalesce:如果系统因某些原因没有执行任务,导致任务累计,为True则只运行最后一次,为False 则累计的任务全部跑一遍

定时任务的停止

如果我们在终端中直接执行的话,关闭终端窗口,Python任务就会中断,Python进程会被杀死,程序将停止运行。如果我们用编辑器直接运行python脚本,这样执行后,如果未正确取消进程,关闭编辑器后,程序依旧运行。

如何停止呢,可使用如下方法:

如果知道进程id直接只用kill命令结束即可,例如:
进程id:3057

$ kill 3057

如果运行后忘记进程ID,则可遵循下面的方法进行停止

ps -e | grep python

这样将会在终端列出python相关的进程:

$ ps -e | grep python
 1025 ??         0:00.41 
 1602 ??         0:00.37 
17699 ttys001    0:00.00 grep python

然后分别执行kill就可以了

推荐阅读更多精彩内容