第一种办法是最简单又最暴力。那就是在一个死循环中,使用线程睡眠函数 sleep()。
缺点:占CPU内存,死循环 + 阻塞线程
def doSth():
# 把爬虫程序放在这个类里
print(u'这个程序要开始疯狂的运转啦')
# 一般网站都是1:00点更新数据,所以每天凌晨一点启动
def main(h=1, m=0):
while True:
now = datetime.datetime.now()
print(now.hour, now.minute)
if now.hour == h and now.minute == m:
break
# 每隔60秒检测一次
time.sleep(60)
doSth()
Python 标准库 threading 中有个 Timer 类。它会新启动一个线程来执行定时任务,所以它是非阻塞函式。
每2秒循环调用
from threading import Timer
def printHello():
print('TimeNow:%s' % (datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')))
t = Timer(2, printHello)
t.start()
if __name__ == "__main__":
printHello()
2秒后调用一次结束
from threading import Timer
def task():
print(datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
def printHello():
t = Timer(2, task)
t.start()
if __name__ == "__main__":
printHello()
通过 scheduler 类来调度事件,从而达到定时执行任务的效果。
首先安装包: pip install schedule
每个事件都在同一线程中运行,所以如果一个事件需要更长的时间如(函数加上time.sleep(3)),延迟事件将会有重叠。为了不丢失事件,延迟事件将会在之前事件运行完再被执行,但一些延迟事件可能会晚于原本计划的事件。
代码
import schedule
def job1():
print('Job1:每隔10秒执行一次的任务,每次执行2秒')
print('Job1-startTime:%s' % (datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')))
time.sleep(2)
print('Job1-endTime:%s' % (datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')))
print('------------------------------------------------------------------------')
def job2():
print('Job2:每隔30秒执行一次,每次执行5秒')
print('Job2-startTime:%s' % (datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')))
time.sleep(5)
print('Job2-endTime:%s' % (datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')))
print('------------------------------------------------------------------------')
def job3():
print('Job3:每隔1分钟执行一次,每次执行10秒')
print('Job3-startTime:%s' % (datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')))
time.sleep(10)
print('Job3-endTime:%s' % (datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')))
print('------------------------------------------------------------------------')
def job4():
print('Job4:每天下午17:49执行一次,每次执行20秒')
print('Job4-startTime:%s' % (datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')))
time.sleep(20)
print('Job4-endTime:%s' % (datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')))
print('------------------------------------------------------------------------')
def job5():
print('Job5:每隔5秒到10秒执行一次,每次执行3秒')
print('Job5-startTime:%s' % (datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')))
time.sleep(3)
print('Job5-endTime:%s' % (datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')))
print('------------------------------------------------------------------------')
def job6():
exit()
if __name__ == '__main__':
# schedule.every(10).seconds.do(job1)
# schedule.every(30).seconds.do(job2)
# schedule.every(1).minutes.do(job3)
# schedule.every().day.at('09:29').do(job4)
schedule.every().day.at('16:54').do(job6)
schedule.every(5).to(10).seconds.do(job5)
while True:
schedule.run_pending()
结果:
------------------------------------------------------------------------
Job1:每隔10秒执行一次的任务,每次执行2秒
Job1-startTime:2019-11-12 14:36:01
Job1-endTime:2019-11-12 14:36:03
------------------------------------------------------------------------
Job2:每隔30秒执行一次,每次执行5秒
Job2-startTime:2019-11-12 14:36:09
Job2-endTime:2019-11-12 14:36:14
------------------------------------------------------------------------
Job1:每隔10秒执行一次的任务,每次执行2秒
Job1-startTime:2019-11-12 14:36:14
Job1-endTime:2019-11-12 14:36:16
------------------------------------------------------------------------
不加上时间阻碍相当于多线程执行
代码
import time
import schedule
def job():
print("I'm working")
worker_main()
def worker_main():
print('Job5-startTime:%s' % (time.time()))
# datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
schedule.every(10).seconds.do(job)
schedule.every(10).seconds.do(job)
schedule.every(10).seconds.do(job)
schedule.every(10).seconds.do(job)
schedule.every(10).seconds.do(job)
while 1:
schedule.run_pending()
结果:
I'm working
Job5-startTime:1573541137.3658931
I'm working
Job5-startTime:1573541137.3658931
I'm working
Job5-startTime:1573541137.3658931
I'm working
Job5-startTime:1573541137.3658931
I'm working
Job5-startTime:1573541137.3658931
sched——通用时间调度器
sched模块实现了一个通用事件调度器,在调度器类使用一个延迟函数等待特定的时间,执行任务。同时支持多线程应用程序,在每个任务执行后会立刻调用延时函数,以确保其他线程也能执行。
代码
import sched
import time
#生成调度器
scheduler = sched.scheduler(time.time, time.sleep)
def print_event(name):
print ('EVENT:', time.time(), name)
print ('START:', time.time())
#分别设置在执行后2秒、3秒之后执行调用函数
scheduler.enter(2, 1, print_event, ('first',))
scheduler.enter(3, 1, print_event, ('second',))
#运行调度器
scheduler.run()
+++++++++++++++++++++++++++++++++++++++++++++++
以上就是时间定时器,但是都无法做到循环执行定时任务。因此,需要一个能够担当此重任的库。它就是APScheduler
APScheduler的全称是Advanced Python Scheduler。它是一个轻量级的 Python 定时任务调度框架。APScheduler 调度器(scheduler)任务控制器:通过配置executor、jobstore、trigger,使用线程池(ThreadPoolExecutor默认值20)或进程池(ProcessPoolExecutor 默认值5)并且默认最多3个(max_instances)任务实例同时运行,实现对job的增删改查等调度控制
你需要选择合适的调度器,这取决于你的应用环境和你使用APScheduler的目的。通常最常用的两个:
BlockingScheduler:当调度器是你应用中唯一要运行的东西时使用。
BackgroundScheduler:当你不运行任何其他框架时使用,并希望调度器在你应用的后台执行。
作业存储
支持4中作业存储,分别是:MemoryJobStore(存储在内存中)、sqlalchemy(关系型数据库)、mongodb(文档数据库)、redis(内存型键值对数据库)
触发方式
date:固定日期触发器:任务只运行一次,运行完毕自动清除;若错过指定运行时间,任务不会被创建
interval:时间间隔触发器,每个一定时间间隔执行一次。
cron:cron风格的任务触发。
简单实用:
代码:
def tick(s):
print("Tick! The time is: %s" % datetime.now(), s)
scheduler = BlockingScheduler()
# ----------------------------------interval间隔:触发任务运行的时间间隔-------------------------
# 触发器为 interval,每隔 3 秒执行一次
# jitter振动参数,给每次触发添加一个随机浮动秒数,一般适用于多服务器,避免同时运行造成服务拥堵。
scheduler.add_job(tick, 'interval', seconds=3, jitter=10, id="test", args=['text'])
# 在 2019-04-15 17:00:00 ~ 2019-12-31 24:00:00 之间, 每隔两分钟执行一次 job_func 方法
a = scheduler.add_job(tick, 'interval', seconds=3, id="test1", start_date='2019-11-12 11:13:00',
end_date='2019-11-12 11:14:00')
# 每2小时触发
scheduler.add_job(tick, 'interval', hours=2)
jitter振动参数,给每次触发添加一个随机浮动秒数,一般适用于多服务器,避免同时运行造成服务拥堵。
# 每小时(上下浮动120秒区间内)运行`job_function`
sched.add_job(job_function, 'interval', hours=1, jitter=120)
# -----------------------------------cron周期:触发任务运行的周期--------------------------------
# hour =19 ,minute =23 这里表示每天的19:23 分执行任务
scheduler.add_job(tick, 'cron', hour=11, minute=50)
# 任务会在6月、7月、8月、11月和12月的第三个周五,00:00、01:00、02:00和03:00触发
scheduler.add_job(tick, 'cron', month='6-8,11-12', day='3rd fri', hour='0-3')
# 在2014-05-30 00:00:00前,每周一到每周五 5:30运行
scheduler.add_job(tick, 'cron', day_of_week='mon-fri', hour=5, minute=30, end_date='2014-05-30')
# ---------------------------------date日期:触发任务运行的具体日期------------------------------
# 其中run_date参数可以是date类型、datetime类型或文本类型,只在2010 - 2 - 25 19: 05:06执行一次,args传递一个text参数。
scheduler.add_job(tick, 'date', run_date=datetime(2019, 11, 12, 13, 56, 25), args=['text1'], id='job1')
scheduler.start()
另一种是以装饰器形式
代码:
from datetime import datetime
from apscheduler.schedulers.blocking import BlockingScheduler
scheduler = BlockingScheduler()
@scheduler.scheduled_job('cron', id='my_job_id', day='last sun')
def some_decorated_task():
print("I am printed at 00:00:00 on the last Sunday of every month!")
# 每天每天凌晨1点30分50秒执行一次scheduled_job()装饰器实现
@scheduler.scheduled_job('cron', day_of_week='*', hour=1, minute='30', second='00')
def tick():
print("Tick! The time is: %s" % datetime.now())
try:
scheduler.start()
print('定时任务成功执行')
except Exception as e:
scheduler.shutdown()
print('定时任务执行失败')
finally:
exit()
移除任务
- 调用remove_job(),参数为:任务ID,任务储存器名称
- 在通过add_job()创建的任务实例上调用remove()方法
第二种方式更方便,但前提必须在创建任务实例时,实例被保存在变量中。对于通过scheduled_job()创建的任务,只能选择第一种方式。
当任务调度结束时(比如,某个任务的触发器不再产生下次运行的时间),任务就会自动移除
job = scheduler.add_job(myfunc, 'interval', minutes=2)
job.remove()
# 根据id
scheduler.add_job(myfunc, 'interval', minutes=2, id='my_job_id')
scheduler.remove_job('my_job_id')
暂停和恢复任务
通过任务实例或调度器,就能暂停和恢复任务。如果一个任务被暂停了,那么该任务的下一次运行时间就会被移除。在恢复任务前,运行次数计数也不会被统计。
暂停任务,有以下两个方法:
apscheduler.job.Job.pause()
apscheduler.schedulers.base.BaseScheduler.pause_job()
恢复任务:
apscheduler.job.Job.resume()
apscheduler.schedulers.base.BaseScheduler.resume_job()
关闭调度器
关闭方法如下:
scheduler.shutdown()
# 默认情况下,调度器会先把正在执行的任务处理完,再关闭任务储存器和执行器。但是,如果你就直接关闭,你可以添加参数:
scheduler.shutdown(wait=False)
暂停、恢复任务进程
调度器可以暂停正在执行的任务:
scheduler.pause()
也可以恢复任务:
scheduler.resume()
同时,也可以在调度器启动时,默认所有任务设为暂停状态
scheduler.start(paused=True)
夏令时问题
有些timezone时区可能会有夏令时的问题。这个可能导致令时切换时,任务不执行或任务执行两次。避免这个问题,可以使用UTC时间,或提前预知并规划好执行的问题。
# 在Europe/Helsinki时区, 在三月最后一个周一就不会触发;在十月最后一个周一会触发两次
sched.add_job(job_function, 'cron', hour=3, minute=30)
加日志,当程序执行完才会显示
from apscheduler.events import EVENT_JOB_EXECUTED, EVENT_JOB_ERROR
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.log',
filemode='a',
)
logging.debug("调试信息")
logging.info("普通信息")
logging.warning("警告信息")
logging.error("错误信息")
logging.critical("严重错误信息")
def aps_test(x):
print(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), x)
print(1 / 0)
def date_test(x):
print(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), x)
def my_listener(event):
if event.exception:
a = '任务出错了'
print(a)
else:
a = '任务照常运行'
print(a)
scheduler = BlockingScheduler()
scheduler.add_job(func=aps_test, args=('一次性任务,会出错',), next_run_time=datetime.now() + timedelta(seconds=13),
id='date_task')
scheduler.add_job(func=date_test, args=('循环任务',), trigger='interval', seconds=5, id='interval_task')
scheduler.add_listener(my_listener, EVENT_JOB_EXECUTED | EVENT_JOB_ERROR)
scheduler._logger = logging
scheduler.start()
谢谢阅览!!!