python定時任務(wù)框架apscheduler

前言

說到定時任務(wù)浓领,你會想起 linux 自帶的 crontab ,windows 自帶的任務(wù)計劃固蚤,都可以實現(xiàn)守時任務(wù)。沒錯歹茶,操作系統(tǒng)基本都會提供定時任務(wù)的實現(xiàn)夕玩,但是如果你想要更加精細(xì)化的控制你弦,或者說任務(wù)程序需要跨平臺運行,最好還是自己實現(xiàn)定時任務(wù)框架燎孟,Python 的 apscheduler 提供了非常豐富而且方便易用的定時任務(wù)接口失息。本文介紹如何使用 apscheduler 實現(xiàn)你的定時任務(wù)娶眷。

apscheduler 使用起來十分方便建芙。提供了基于日期速缆、固定時間間隔以及crontab 類型的任務(wù),我們可以在主程序的運行過程中快速增加新作業(yè)或刪除舊作業(yè)爆侣,如果把作業(yè)存儲在數(shù)據(jù)庫中萍程,那么作業(yè)的狀態(tài)會被保存,當(dāng)調(diào)度器重啟時累提,不必重新添加作業(yè)尘喝,作業(yè)會恢復(fù)原狀態(tài)繼續(xù)執(zhí)行。apscheduler 可以當(dāng)作一個跨平臺的調(diào)度工具來使用斋陪,可以做為 linux 系統(tǒng)crontab 工具或 windows 計劃任務(wù)程序的替換。注意置吓,apscheduler 不是一個守護(hù)進(jìn)程或服務(wù)无虚,它自身不帶有任何命令行工具。它主要是要在現(xiàn)有的應(yīng)用程序中運行衍锚,也就是說友题,apscheduler 為我們提供了構(gòu)建專用調(diào)度器或調(diào)度服務(wù)的基礎(chǔ)模塊。

安裝

安裝非常簡單戴质,會用 pip 的人都知道

pip install apscheduler

基本概念介紹

觸發(fā)器(triggers):觸發(fā)器包含調(diào)度邏輯度宦,描述一個任務(wù)何時被觸發(fā),按日期或按時間間隔或按 cronjob 表達(dá)式三種方式觸發(fā)告匠。每個作業(yè)都有它自己的觸發(fā)器戈抄,除了初始配置之外,觸發(fā)器是完全無狀態(tài)的后专。

作業(yè)存儲器(job stores):作業(yè)存儲器指定了作業(yè)被存放的位置划鸽,默認(rèn)情況下作業(yè)保存在內(nèi)存,也可將作業(yè)保存在各種數(shù)據(jù)庫中戚哎,當(dāng)作業(yè)被存放在數(shù)據(jù)庫中時裸诽,它會被序列化,當(dāng)被重新加載時會反序列化型凳。作業(yè)存儲器充當(dāng)保存丈冬、加載、更新和查找作業(yè)的中間商甘畅。在調(diào)度器之間不能共享作業(yè)存儲埂蕊。

執(zhí)行器(executors):執(zhí)行器是將指定的作業(yè)(調(diào)用函數(shù))提交到線程池或進(jìn)程池中運行实夹,當(dāng)任務(wù)完成時,執(zhí)行器通知調(diào)度器觸發(fā)相應(yīng)的事件粒梦。

調(diào)度器(schedulers):任務(wù)調(diào)度器亮航,屬于控制角色,通過它配置作業(yè)存儲器匀们、執(zhí)行器和觸發(fā)器缴淋,添加、修改和刪除任務(wù)泄朴。調(diào)度器協(xié)調(diào)觸發(fā)器重抖、作業(yè)存儲器、執(zhí)行器的運行祖灰,通常只有一個調(diào)度程序運行在應(yīng)用程序中钟沛,開發(fā)人員通常不需要直接處理作業(yè)存儲器、執(zhí)行器或觸發(fā)器局扶,配置作業(yè)存儲器和執(zhí)行器是通過調(diào)度器來完成的恨统。

調(diào)度器的工作流程

實例1 -間隔性任務(wù)
# -*- coding: utf-8 -*-
# Time: 2018/10/13 19:01:30
# File Name: ex_interval.py

from datetime import datetime
import os
from apscheduler.schedulers.blocking import BlockingScheduler

def tick():
    print('觸發(fā)!現(xiàn)在時間是: %s' % datetime.now())

if __name__ == '__main__':
    scheduler = BlockingScheduler()
    scheduler.add_job(tick, 'interval', seconds=3)
    print('請按下Ctrl+{0}鍵退出'.format('Break' if os.name == 'nt' else 'C    '))

    try:
        scheduler.start()
    except (KeyboardInterrupt, SystemExit):
        pass

說明
第 1 行代碼聲明文件內(nèi)容以 utf-8 編碼三妈,告訴Python 解釋器以 utf-8 編碼解析源代碼文件畜埋。
導(dǎo)入 datetime 模塊,用于打印當(dāng)前時間畴蒲。導(dǎo)入 os 模塊悠鞍,用于判斷操作系統(tǒng)類型。
導(dǎo)入調(diào)度器模塊 BlockingScheduler模燥,這是最簡單的調(diào)度器咖祭,調(diào)用 start 方阻塞當(dāng)前進(jìn)程,如果你的程序只用于調(diào)度蔫骂,除了調(diào)度進(jìn)程外沒有其他后臺進(jìn)程么翰,那么 BlockingScheduler 非常有用,此時調(diào)度進(jìn)程相當(dāng)于守護(hù)進(jìn)程纠吴。
定義一個函數(shù) tick 代表我們要調(diào)度的作業(yè)程序硬鞍。
實例化一個 BlockingScheduler 類,不帶參數(shù)表明使用默認(rèn)的作業(yè)存儲器-內(nèi)存戴已,默認(rèn)的執(zhí)行器是線程池執(zhí)行器固该,最大并發(fā)線程數(shù)默認(rèn)為 10 個(另一個是進(jìn)程池執(zhí)行器)。
第 11 行添加一個作業(yè) tick糖儡,觸發(fā)器為 interval伐坏,每隔 3 秒執(zhí)行一次,另外的觸發(fā)器為 date握联,cron桦沉。date 按特定時間點觸發(fā)每瞒,cron 則按固定的時間間隔觸發(fā)。
加入捕捉用戶中斷執(zhí)行和解釋器退出異常纯露,pass 關(guān)鍵字剿骨,表示什么也不做。

實例2 - cron 任務(wù)
# -*- coding: utf-8 -*-
# Time: 2018/10/13 19:21:09
# File Name: ex_cron.py


from datetime import datetime
import os
from apscheduler.schedulers.blocking import BlockingScheduler

def tick():
    print('觸發(fā)埠褪!現(xiàn)在時間是: %s' % datetime.now())

if __name__ == '__main__':
    scheduler = BlockingScheduler()
    scheduler.add_job(tick, 'cron', hour=19, minute=23)
    print('請按下Ctrl+{0}鍵退出'.format('Break' if os.name == 'nt' else 'C    '))

    try:
        scheduler.start()
    except (KeyboardInterrupt, SystemExit):
        pass

配置調(diào)度器

調(diào)度器的主循環(huán)其實就是反復(fù)檢查是不是有到時需要執(zhí)行的任務(wù)浓利,分以下幾步進(jìn)行:

詢問自己的每一個作業(yè)存儲器,有沒有到期需要執(zhí)行的任務(wù)钞速,如果有贷掖,計算這些作業(yè)中每個作業(yè)需要運行的時間點,如果時間點有多個渴语,做 coalesce 檢查苹威。
提交給執(zhí)行器按時間點運行。
在配置調(diào)度器前驾凶,我們首先要選取適合我們應(yīng)用環(huán)境場景的調(diào)度器牙甫,存儲器和執(zhí)行器。下面是各調(diào)度器的適用場景:

BlockingScheduler:適用于調(diào)度程序是進(jìn)程中唯一運行的進(jìn)程狭郑,調(diào)用start函數(shù)會阻塞當(dāng)前線程腹暖,不能立即返回。
BackgroundScheduler:適用于調(diào)度程序在應(yīng)用程序的后臺運行翰萨,調(diào)用start后主線程不會阻塞。
AsyncIOScheduler:適用于使用了asyncio模塊的應(yīng)用程序糕殉。
GeventScheduler:適用于使用gevent模塊的應(yīng)用程序亩鬼。
TwistedScheduler:適用于構(gòu)建Twisted的應(yīng)用程序。
QtScheduler:適用于構(gòu)建Qt的應(yīng)用程序阿蝶。

上述調(diào)度器可以滿足我們絕大多數(shù)的應(yīng)用環(huán)境雳锋,本文以兩種調(diào)度器為例說明如何進(jìn)行調(diào)度器配置。
作業(yè)存儲器的選擇有兩種:一是內(nèi)存羡洁,也是默認(rèn)的配置玷过;二是數(shù)據(jù)庫。具體選哪一種看我們的應(yīng)用程序在崩潰時是否重啟整個應(yīng)用程序筑煮,如果重啟整個應(yīng)用程序辛蚊,那么作業(yè)會被重新添加到調(diào)度器中,此時簡單的選取內(nèi)存作為作業(yè)存儲器即簡單又高效真仲。但是袋马,當(dāng)調(diào)度器重啟或應(yīng)用程序崩潰時您需要您的作業(yè)從中斷時恢復(fù)正常運行,那么通常我們選擇將作業(yè)存儲在數(shù)據(jù)庫中秸应,使用哪種數(shù)據(jù)庫通常取決于為在您的編程環(huán)境中使用了什么數(shù)據(jù)庫虑凛。我們可以自由選擇碑宴,PostgreSQL 是推薦的選擇,因為它具有強大的數(shù)據(jù)完整性保護(hù)桑谍。

同樣的延柠,執(zhí)行器的選擇也取決于應(yīng)用場景。通常默認(rèn)的 ThreadPoolExecutor 已經(jīng)足夠好锣披。如果作業(yè)負(fù)載涉及CPU 密集型操作贞间,那么應(yīng)該考慮使用 ProcessPoolExecutor,甚至可以同時使用這兩種執(zhí)行器盈罐,將ProcessPoolExecutor 行器添加為二級執(zhí)行器榜跌。

apscheduler 提供了許多不同的方法來配置調(diào)度器≈逊啵可以使用字典钓葫,也可以使用關(guān)鍵字參數(shù)傳遞。首先實例化調(diào)度程序票顾,添加作業(yè)础浮,然后配置調(diào)度器,獲得最大的靈活性奠骄。

如果調(diào)度程序在應(yīng)用程序的后臺運行豆同,選擇 BackgroundScheduler,并使用默認(rèn)的 jobstore 和默認(rèn)的executor含鳞,則以下配置即可:

from apscheduler.schedulers.background import BackgroundScheduler
scheduler = BackgroundScheduler()

假如我們想配置更多信息:設(shè)置兩個執(zhí)行器影锈、兩個作業(yè)存儲器、調(diào)整新作業(yè)的默認(rèn)值蝉绷,并設(shè)置不同的時區(qū)鸭廷。下述三個方法是完全等同的。

配置需求
配置名為“mongo”的MongoDBJobStore作業(yè)存儲器
配置名為“default”的SQLAlchemyJobStore(使用SQLite)
配置名為“default”的ThreadPoolExecutor熔吗,最大線程數(shù)為20
配置名為“processpool”的ProcessPoolExecutor辆床,最大進(jìn)程數(shù)為5
UTC作為調(diào)度器的時區(qū)
coalesce默認(rèn)情況下關(guān)閉
作業(yè)的默認(rèn)最大運行實例限制為3

方法一
from pytz import utc
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.jobstores.mongodb import MongoDBJobStore
from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore
from apscheduler.executors.pool import ThreadPoolExecutor, ProcessPoolExecutor

jobstores = {
    'mongo': MongoDBJobStore(),
    'default': SQLAlchemyJobStore(url='sqlite:///jobs.sqlite')
}
executors = {
    'default': ThreadPoolExecutor(20),
    'processpool': ProcessPoolExecutor(5)
},
job_defaults = {
    'coalesce': False,
    'max_instances': 3
}
scheduler = BackgroundScheduler(jobstores=jobstores, executors=executors, job_defaults=job_defaults, timezone=utc)
方法二
from apscheduler.schedulers.background import BackgroundScheduler
scheduler = BackgroundScheduler({
    'apscheduler.jobstores.mongo': {
    'type': 'mongodb'
 },
'apscheduler.jobstores.default': {
    'type': 'sqlalchemy',
    'url': 'sqlite:///jobs.sqlite'
},
'apscheduler.executors.default': {
    'class': 'apscheduler.executors.pool:ThreadPoolExecutor',
    'max_workers': '20'
},
'apscheduler.executors.processpool': {
    'type': 'processpool',
    'max_workers': '5'
},
'apscheduler.job_defaults.coalesce': 'false',
    'apscheduler.job_defaults.max_instances': '3',
    'apscheduler.timezone': 'UTC',
})
方法三
from pytz import utc
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore
from apscheduler.executors.pool import ProcessPoolExecutor

jobstores = {
    'mongo': {'type': 'mongodb'},
    'default': SQLAlchemyJobStore(url='sqlite:///jobs.sqlite')
}
executors = {
    'default': {'type': 'threadpool', 'max_workers': 20},
    'processpool': ProcessPoolExecutor(max_workers=5)
}
job_defaults = {
    'coalesce': False,
    'max_instances': 3
}
scheduler = BackgroundScheduler()

以上涵蓋了大多數(shù)情況的調(diào)度器配置,在實際運行時可以試試不同的配置會有怎樣不同的效果桅狠。

啟動調(diào)度器

啟動調(diào)度器前需要先添加作業(yè)讼载,有兩種方法向調(diào)度器添加作業(yè):一是通過接口add_job(),二是通過使用函數(shù)裝飾器中跌,其中 add_job() 返回一個apscheduler.job.Job類的實例咨堤,用于后續(xù)修改或刪除作業(yè)。

我們可以隨時在調(diào)度器上調(diào)度作業(yè)晒他。如果在添加作業(yè)時吱型,調(diào)度器還沒有啟動,那么任務(wù)將不會運行陨仅,并且第一次運行時間在調(diào)度器啟動時計算津滞。
注意:如果使用的是序列化作業(yè)的執(zhí)行器或作業(yè)存儲器铝侵,那么要求被調(diào)用的作業(yè)(函數(shù))必須是全局可訪問的,被調(diào)用的作業(yè)的參數(shù)是可序列化的触徐,作業(yè)存儲器中咪鲜,只有 MemoryJobStore 不會序列化作業(yè)。執(zhí)行器中撞鹉,只有ProcessPoolExecutor 將序列化作業(yè)疟丙。

啟用調(diào)度器只需要調(diào)用調(diào)度器的 start() 方法,下面分別使用不同的作業(yè)存儲器來舉例說明:

方法一:使用默認(rèn)的作業(yè)存儲器
# coding:utf-8
from apscheduler.schedulers.blocking import BlockingScheduler
import datetime
from apscheduler.jobstores.memory import MemoryJobStore
from apscheduler.executors.pool import ThreadPoolExecutor, ProcessPoolExecutor


def my_job(id='my_job'):
    print(id, '-->', datetime.datetime.now())


jobstores = {
    'default': MemoryJobStore()
}

executors = {
    'default': ThreadPoolExecutor(20),
    'processpool': ProcessPoolExecutor(10)
}
job_defaults = {
    'coalesce': False,
    'max_instances': 3
}
scheduler = BlockingScheduler(jobstores=jobstores, executors=executors, job_defaults=job_defaults)
scheduler.add_job(my_job, args=['job_interval', ], id='job_interval', trigger='interval', seconds=5,
                  replace_existing=True)
scheduler.add_job(my_job, args=['job_cron', ], id='job_cron', trigger='cron', month='4-8,11-12', hour='7-11',
                  second='*/10',
                  end_date='2018-05-30')
scheduler.add_job(my_job, args=['job_once_now', ], id='job_once_now')
scheduler.add_job(my_job, args=['job_date_once', ], id='job_date_once', trigger='date', run_date='2018-04-05 07:48:05')
try:
    scheduler.start()
except SystemExit:
    print('exit')
    exit()

運行結(jié)果如下

job_once_now --> 2018-04-05 07:48:00.967391
job_date_once --> 2018-04-05 07:48:05.005532
job_interval --> 2018-04-05 07:48:05.954023
job_cron --> 2018-04-05 07:48:10.004431
job_interval --> 2018-04-05 07:48:10.942542
job_interval --> 2018-04-05 07:48:15.952208
job_cron --> 2018-04-05 07:48:20.007123
job_interval --> 2018-04-05 07:48:20.952202
……

上述代碼使用內(nèi)存作為作業(yè)存儲器鸟雏,操作比較簡單享郊,重啟程序相當(dāng)于第一次運行。

方法二:使用數(shù)據(jù)庫作為存儲器

# coding:utf-8
from apscheduler.schedulers.blocking import BlockingScheduler
import datetime
from apscheduler.jobstores.memory import MemoryJobStore
from apscheduler.executors.pool import ThreadPoolExecutor, ProcessPoolExecutor
from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore


def my_job(id='my_job'):
    print(id, '-->', datetime.datetime.now())


jobstores = {
    'default': SQLAlchemyJobStore(url='sqlite:///jobs.sqlite')
}
executors = {
    'default': ThreadPoolExecutor(20),
    'processpool': ProcessPoolExecutor(10)
}
job_defaults = {
    'coalesce': False,
    'max_instances': 3
}
scheduler = BlockingScheduler(jobstores=jobstores, executors=executors, job_defaults=job_defaults)
scheduler.add_job(my_job, args=['job_interval', ], id='job_interval', trigger='interval', seconds=5,
                  replace_existing=True)
scheduler.add_job(my_job, args=['job_cron', ], id='job_cron', trigger='cron', month='4-8,11-12', hour='7-11',
                  second='*/10', end_date='2018-05-30')
scheduler.add_job(my_job, args=['job_once_now', ], id='job_once_now')
scheduler.add_job(my_job, args=['job_date_once', ], id='job_date_once', trigger='date', run_date='2018-04-05 07:48:05')
try:
    scheduler.start()
except SystemExit:
    print('exit')
    exit()

說明孝鹊,在第 6 行炊琉、第 10 行代碼修改為數(shù)據(jù)庫作為作業(yè)存儲器
運行結(jié)果如下

Run time of job "my_job (trigger: date[2018-04-05 07:48:05 CST], next run at: 2018-04-05 07:48:05 CST)" was missed by 0:18:28.898146
job_once_now --> 2018-04-05 08:06:34.010194
job_interval --> 2018-04-05 08:06:38.445843
job_cron --> 2018-04-05 08:06:40.154978
job_interval --> 2018-04-05 08:06:43.285941
job_interval --> 2018-04-05 08:06:48.334360
job_cron --> 2018-04-05 08:06:50.172968
job_interval --> 2018-04-05 08:06:53.281743
job_interval --> 2018-04-05 08:06:58.309952

提示我們有作業(yè)本應(yīng)在 2018-04-05 07:48:05 運行的作業(yè)沒有運行,因為現(xiàn)在的時間為 2018-04-05 08:06:34又活,錯過了 0:18:28 的時間苔咪。
如果將上述代碼第 21-25 行注釋掉,重新運行本程序柳骄,則四種類型的作業(yè)仍會運行团赏,結(jié)果如下:

Run time of job "my_job (trigger: cron[month='4-8,11-12', hour='7-11', second='*/10'], next run at: 2018-04-05 08:14:40 CST)" was missed by 0:00:23.680603
Run time of job "my_job (trigger: cron[month='4-8,11-12', hour='7-11', second='*/10'], next run at: 2018-04-05 08:14:40 CST)" was missed by 0:00:13.681604
Run time of job "my_job (trigger: cron[month='4-8,11-12', hour='7-11', second='*/10'], next run at: 2018-04-05 08:14:40 CST)" was missed by 0:00:03.681604
……
Run time of job "my_job (trigger: interval[0:00:05], next run at: 2018-04-05 08:14:38 CST)" was missed by 0:00:15.687917
Run time of job "my_job (trigger: interval[0:00:05], next run at: 2018-04-05 08:14:38 CST)" was missed by 0:00:10.687917
Run time of job "my_job (trigger: interval[0:00:05], next run at: 2018-04-05 08:14:38 CST)" was missed by 0:00:05.687917
job_interval --> 2018-04-05 08:14:33.821645
job_interval --> 2018-04-05 08:14:38.529167
job_cron --> 2018-04-05 08:14:40.150080
job_interval --> 2018-04-05 08:14:43.296188
job_interval --> 2018-04-05 08:14:48.327317

作業(yè)仍會運行,說明作業(yè)被添加到數(shù)據(jù)庫中耐薯,程序中斷后重新運行時會自動從數(shù)據(jù)庫讀取作業(yè)信息舔清,而不需要重新再添加到調(diào)度器中,如果不注釋 21-25 行添加作業(yè)的代碼曲初,則作業(yè)會重新添加到數(shù)據(jù)庫中鸠踪,這樣就有了兩個同樣的作業(yè),避免出現(xiàn)這種情況可以在 add_job 的參數(shù)中增加 replace_existing=True复斥,如

scheduler.add_job(my_job, args=['job_interval',],id='job_interval',trigger='interval',seconds=3,replace_existing=True)

如果我們想運行錯過運行的作業(yè),使用 misfire_grace_time械媒,如

scheduler.add_job(my_job,args = ['job_cron',] ,id='job_cron',trigger='cron',month='4-8,11-12',hour='7-11',second='*/15',coalesce=True,misfire_grace_time=30,replace_existing=True,end_date='2018-05-30')

說明:misfire_grace_time目锭,假如一個作業(yè)本來 08:00 有一次執(zhí)行,但是由于某種原因沒有被調(diào)度上纷捞,現(xiàn)在 08:01 了痢虹,這個 08:00 的運行實例被提交時,會檢查它預(yù)訂運行的時間和當(dāng)下時間的差值(這里是1分鐘)主儡,大于我們設(shè)置的 30 秒限制奖唯,那么這個運行實例不會被執(zhí)行。最常見的情形是 scheduler 被 shutdown 后重啟糜值,某個任務(wù)會積攢了好幾次沒執(zhí)行如 5 次丰捷,下次這個作業(yè)被提交給執(zhí)行器時坯墨,執(zhí)行 5 次。設(shè)置 coalesce=True 后病往,只會執(zhí)行一次捣染。
其他操作如下:

scheduler.remove_job(job_id,jobstore=None)#刪除作業(yè)
scheduler.remove_all_jobs(jobstore=None)#刪除所有作業(yè)
scheduler.pause_job(job_id,jobstore=None)#暫停作業(yè)
scheduler.resume_job(job_id,jobstore=None)#恢復(fù)作業(yè)
scheduler.modify_job(job_id, jobstore=None, **changes)#修改單個作業(yè)屬性信息
scheduler.reschedule_job(job_id, jobstore=None, trigger=None,**trigger_args)#修改單個作業(yè)的觸發(fā)器并更新下次運行時間
scheduler.print_jobs(jobstore=None, out=sys.stdout)#輸出作業(yè)信息

調(diào)度器事件監(jiān)聽

scheduler 的基本應(yīng)用,在前面已經(jīng)介紹過了停巷,但仔細(xì)思考一下:如果程序有異常拋出會影響整個調(diào)度任務(wù)嗎耍攘?請看下面的代碼,運行一下看看會發(fā)生什么情況:

# coding:utf-8
from apscheduler.schedulers.blocking import BlockingScheduler
import datetime

def aps_test(x):
    print (1/0)
    print (datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), x)

scheduler = BlockingScheduler()
scheduler.add_job(func=aps_test, args=('定時任務(wù)',), trigger='cron', second='*/5')

scheduler.start()  

運行結(jié)果如下

Job "aps_test (trigger: cron[second='*/5'], next run at: 2018-04-05 12:46:35 CST)" raised an exception
Traceback (most recent call last):
  File "C:\Users\xx\AppData\Local\Programs\python\python36\lib\site-packages\apscheduler\executors\base.py", line 125, in run_job
    retval = job.func(*job.args, **job.kwargs)
  File "C:/Users/xx/PycharmProjects/mysite/staff/test2.py", line 7, in aps_test
    print (1/0)
ZeroDivisionError: division by zero
Job "aps_test (trigger: cron[second='*/5'], next run at: 2018-04-05 12:46:35 CST)" raised an exception
Traceback (most recent call last):
  File "C:\Users\xx\AppData\Local\Programs\python\python36\lib\site-packages\apscheduler\executors\base.py", line 125, in run_job
    retval = job.func(*job.args, **job.kwargs)
  File "C:/Users/xx/PycharmProjects/mysite/staff/test2.py", line 7, in aps_test
    print (1/0)
ZeroDivisionError: division by zero

可能看出每 5 秒拋出一次報錯信息畔勤。任何代碼都可能拋出異常蕾各,關(guān)鍵是,發(fā)生導(dǎo)常事件庆揪,如何第一時間知道式曲,這才是我們最關(guān)心的,apscheduler 已經(jīng)為我們想到了這些嚷硫,提供了事件監(jiān)聽來解決這一問題检访。
將上述代碼稍做調(diào)整,加入日志記錄和事件監(jiān)聽仔掸,如下所示脆贵。

# coding:utf-8
from apscheduler.schedulers.blocking import BlockingScheduler
from apscheduler.events import EVENT_JOB_EXECUTED, EVENT_JOB_ERROR
import datetime
import logging

logging.basicConfig(level=logging.INFO,
                    format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
                    datefmt='%Y-%m-%d %H:%M:%S',
                    filename='log1.txt',
                    filemode='a')

def aps_test(x):
        print (datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), x)


def date_test(x):
    print(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), x)
    print (1/0)

def my_listener(event):
    if event.exception:
        print ('任務(wù)出錯了!F鹉骸B舭薄!8号场筒捺!')
    else:
        print ('任務(wù)照常運行...')

scheduler = BlockingScheduler()
scheduler.add_job(func=date_test, args=('一次性任務(wù),會出錯',), next_run_time=datetime.datetime.now() + datetime.timedelta(seconds=15), id='date_task')
scheduler.add_job(func=aps_test, args=('循環(huán)任務(wù)',), trigger='interval', seconds=3, id='interval_task')
scheduler.add_listener(my_listener, EVENT_JOB_EXECUTED | EVENT_JOB_ERROR)
scheduler._logger = logging

scheduler.start()

說明:
第 7-11 行配置日志記錄信息,日志文件在當(dāng)前路徑纸厉,文件名為 “l(fā)og1.txt”系吭。
第 33 行啟用 scheduler 模塊的日記記錄。
第 23-27 定義一個事件監(jiān)聽颗品,出現(xiàn)意外情況打印相關(guān)信息報警肯尺。
運行結(jié)果如下所示。

2018-04-05 12:59:29 循環(huán)任務(wù)
任務(wù)照常運行...
2018-04-05 12:59:32 循環(huán)任務(wù)
任務(wù)照常運行...
2018-04-05 12:59:35 循環(huán)任務(wù)
任務(wù)照常運行...
2018-04-05 12:59:38 循環(huán)任務(wù)
任務(wù)照常運行...
2018-04-05 12:59:41 一次性任務(wù),會出錯
任務(wù)出錯了G唷T蛞鳌!3濉Cブ佟!
2018-04-05 12:59:41 循環(huán)任務(wù)
任務(wù)照常運行...
2018-04-05 12:59:44 循環(huán)任務(wù)
任務(wù)照常運行...
2018-04-05 12:59:47 循環(huán)任務(wù)
任務(wù)照常運行...

在生產(chǎn)環(huán)境中,可以把出錯信息換成發(fā)送一封郵件或者發(fā)送一個短信敬扛,這樣定時任務(wù)出錯就可以立馬就知道晰洒。

轉(zhuǎn)載于:https://blog.csdn.net/somezz/article/details/83104368

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市舔哪,隨后出現(xiàn)的幾起案子欢顷,更是在濱河造成了極大的恐慌,老刑警劉巖捉蚤,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件抬驴,死亡現(xiàn)場離奇詭異,居然都是意外死亡缆巧,警方通過查閱死者的電腦和手機布持,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來陕悬,“玉大人题暖,你說我怎么就攤上這事∽匠” “怎么了胧卤?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長拼岳。 經(jīng)常有香客問我枝誊,道長,這世上最難降的妖魔是什么惜纸? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任叶撒,我火速辦了婚禮,結(jié)果婚禮上耐版,老公的妹妹穿的比我還像新娘祠够。我一直安慰自己,他們只是感情好粪牲,可當(dāng)我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布古瓤。 她就那樣靜靜地躺著,像睡著了一般腺阳。 火紅的嫁衣襯著肌膚如雪湿滓。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天舌狗,我揣著相機與錄音,去河邊找鬼扔水。 笑死痛侍,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播主届,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼赵哲,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了君丁?” 一聲冷哼從身側(cè)響起枫夺,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎绘闷,沒想到半個月后橡庞,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡印蔗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年扒最,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片华嘹。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡吧趣,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出耙厚,到底是詐尸還是另有隱情强挫,我是刑警寧澤,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布薛躬,位于F島的核電站俯渤,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏泛豪。R本人自食惡果不足惜稠诲,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望诡曙。 院中可真熱鬧臀叙,春花似錦、人聲如沸价卤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽慎璧。三九已至床嫌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間胸私,已是汗流浹背厌处。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留岁疼,地道東北人阔涉。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親瑰排。 傳聞我的和親對象是個殘疾皇子贯要,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,077評論 2 355