Python实现定时任务的八种方案!_python time定时扫描

scheduler 对象主要方法:

  • enter(delay, priority, action, argument),安排一个事件来延迟 delay 个时间单位。
  • cancel(event):从队列中删除事件。如果事件不是当前队列中的事件,则该方法将跑出一个 ValueError。
  • run():运行所有预定的事件。这个函数将等待(使用传递给构造函数的 delayfunc() 函数),然后执行事件,直到不再有预定的事件。

个人点评:比 threading.Timer 更好,不需要循环调用。

利用调度模块 schedule 实现定时任务

schedule[3] 是一个第三方轻量级的任务调度模块,可以按照秒,分,小时,日期或者自定义事件执行时间。schedule[4] 允许用户使用简单、人性化的语法以预定的时间间隔定期运行 Python 函数(或其它可调用函数)。

先来看代码,是不是不看文档就能明白什么意思?

import schedule

import time

def job():

    print("I'm working...")

schedule.every(10).seconds.do(job)

schedule.every(10).minutes.do(job)

schedule.every().hour.do(job)

schedule.every().day.at("10:30").do(job)

schedule.every(5).to(10).minutes.do(job)

schedule.every().monday.do(job)

schedule.every().wednesday.at("13:15").do(job)

schedule.every().minute.at(":17").do(job)

while True:

    schedule.run_pending()

    time.sleep(1)

装饰器:通过 @repeat() 装饰静态方法

import time

from schedule import every, repeat, run_pending

@repeat(every().second)

def job():

    print('working...')

while True:

    run_pending()

    time.sleep(1)

传递参数:

import schedule

def greet(name):

    print('Hello', name)

schedule.every(2).seconds.do(greet, name='Alice')

schedule.every(4).seconds.do(greet, name='Bob')

while True:

    schedule.run_pending()

装饰器同样能传递参数:

from schedule import every, repeat, run_pending

@repeat(every().second, 'World')

@repeat(every().minute, 'Mars')

def hello(planet):

    print('Hello', planet)

while True:

    run_pending()

取消任务:

import schedule

i = 0

def some_task():

    global i

    i += 1

    print(i)

    if i == 10:

        schedule.cancel_job(job)

        print('cancel job')

        exit(0)

job = schedule.every().second.do(some_task)

while True:

    schedule.run_pending()

运行一次任务:

import time

import schedule

def job_that_executes_once():

    print('Hello')

    return schedule.CancelJob

schedule.every().minute.at(':34').do(job_that_executes_once)

while True:

    schedule.run_pending()

    time.sleep(1)

根据标签检索任务:

# 检索所有任务:schedule.get_jobs()

import schedule

def greet(name):

    print('Hello {}'.format(name))

schedule.every().day.do(greet, 'Andrea').tag('daily-tasks', 'friend')

schedule.every().hour.do(greet, 'John').tag('hourly-tasks', 'friend')

schedule.every().hour.do(greet, 'Monica').tag('hourly-tasks', 'customer')

schedule.every().day.do(greet, 'Derek').tag('daily-tasks', 'guest')

friends = schedule.get_jobs('friend')

print(friends)

根据标签取消任务:

# 取消所有任务:schedule.clear()

import schedule

def greet(name):

    print('Hello {}'.format(name))

    if name == 'Cancel':

        schedule.clear('second-tasks')

        print('cancel second-tasks')

schedule.every().second.do(greet, 'Andrea').tag('second-tasks', 'friend')

schedule.every().second.do(greet, 'John').tag('second-tasks', 'friend')

schedule.every().hour.do(greet, 'Monica').tag('hourly-tasks', 'customer')

schedule.every(5).seconds.do(greet, 'Cancel').tag('daily-tasks', 'guest')

while True:

    schedule.run_pending()

运行任务到某时间:

import schedule

from datetime import datetime, timedelta, time

def job():

    print('working...')

schedule.every().second.until('23:59').do(job)  # 今天 23:59 停止

schedule.every().second.until('2030-01-01 18:30').do(job)  # 2030-01-01 18:30 停止

schedule.every().second.until(timedelta(hours=8)).do(job)  # 8 小时后停止

schedule.every().second.until(time(23, 59, 59)).do(job)  # 今天 23:59:59 停止

schedule.every().second.until(datetime(2030, 1, 1, 18, 30, 0)).do(job)  # 2030-01-01 18:30 停止

while True:

    schedule.run_pending()

马上运行所有任务(主要用于测试):

import schedule

def job():

    print('working...')

def job1():

    print('Hello...')

schedule.every().monday.at('12:40').do(job)

schedule.every().tuesday.at('16:40').do(job1)

schedule.run_all()

schedule.run_all(delay_seconds=3)  # 任务间延迟 3 秒

并行运行:使用 Python 内置队列实现:

import threading

import time

import schedule

def job1():

    print("I'm running on thread %s" % threading.current_thread())

def job2():

    print("I'm running on thread %s" % threading.current_thread())

def job3():

    print("I'm running on thread %s" % threading.current_thread())

def run_threaded(job_func):

    job_thread = threading.Thread(target=job_func)

    job_thread.start()

schedule.every(10).seconds.do(run_threaded, job1)

schedule.every(10).seconds.do(run_threaded, job2)

schedule.every(10).seconds.do(run_threaded, job3)

while True:

    schedule.run_pending()

    time.sleep(1)

利用任务框架 APScheduler 实现定时任务

APScheduler[5](advanceded python scheduler)基于 Quartz 的一个 Python 定时任务框架,实现了 Quartz 的所有功能,使用起来十分方便。提供了基于日期、固定时间间隔以及 crontab 类型的任务,并且可以持久化任务。基于这些功能,我们可以很方便的实现一个 Python 定时任务系统。

它有以下三个特点:

  • 类似于 Liunx Cron 的调度程序(可选的开始/结束时间)
  • 基于时间间隔的执行调度(周期性调度,可选的开始/结束时间)
  • 一次性执行任务(在设定的日期/时间运行一次任务)

APScheduler 有四种组成部分:

  • 触发器 (trigger) 包含调度逻辑,每一个作业有它自己的触发器,用于决定接下来哪一个作业会运行。除了他们自己初始配置意外,触发器完全是无状态的。
  • 作业存储 (job store) 存储被调度的作业,默认的作业存储是简单地把作业保存在内存中,其他的作业存储是将作业保存在数据库中。一个作业的数据讲在保存在持久化作业存储时被序列化,并在加载时被反序列化。调度器不能分享同一个作业存储。
  • 执行器 (executor) 处理作业的运行,他们通常通过在作业中提交制定的可调用对象到一个线程或者进城池来进行。当作业完成时,执行器将会通知调度器。
  • 调度器 (scheduler) 是其他的组成部分。你通常在应用只有一个调度器,应用的开发者通常不会直接处理作业存储、调度器和触发器,相反,调度器提供了处理这些的合适的接口。配置作业存储和执行器可以在调度器中完成,例如添加、修改和移除作业。通过配置 executor、jobstore、trigger,使用线程池 (ThreadPoolExecutor 默认值 20) 或进程池 (ProcessPoolExecutor 默认值 5) 并且默认最多 3 个 (max_instances) 任务实例同时运行,实现对 job 的增删改查等调度控制

示例代码:

from apscheduler.schedulers.blocking import BlockingScheduler

from datetime import datetime

# 输出时间

def job():

    print(datetime.now().strftime("%Y-%m-%d %H:%M:%S"))

# BlockingScheduler

sched = BlockingScheduler()

sched.add_job(my_job, 'interval', seconds=5, id='my_job_id')

sched.start()

APScheduler 中的重要概念
Job 作业

Job 作为 APScheduler 最小执行单位。创建 Job 时指定执行的函数,函数中所需参数,Job 执行时的一些设置信息。

构建说明:

  • id:指定作业的唯一 ID
  • name:指定作业的名字
  • trigger:apscheduler 定义的触发器,用于确定 Job 的执行时间,根据设置的 trigger 规则,计算得到下次执行此 job 的时间, 满足时将会执行
  • executor:apscheduler 定义的执行器,job 创建时设置执行器的名字,根据字符串你名字到 scheduler 获取到执行此 job 的 执行器,执行 job 指定的函数
  • max_instances:执行此 job 的最大实例数,executor 执行 job 时,根据 job 的 id 来计算执行次数,根据设置的最大实例数来确定是否可执行
  • next_run_time:Job 下次的执行时间,创建 Job 时可以指定一个时间 [datetime], 不指定的话则默认根据 trigger 获取触发时间
  • misfire_grace_time:Job 的延迟执行时间,例如 Job 的计划执行时间是 21:00:00,但因服务重启或其他原因导致 21:00:31 才执行,如果设置此 key 为 40, 则该 job 会继续执行,否则将会丢弃此 job
  • coalesce:Job 是否合并执行,是一个 bool 值。例如 scheduler 停止 20s 后重启启动,而 job 的触发器设置为 5s 执行一次,因此此 job 错过了 4 个执行时间,如果设置为是,则会合并到一次执行,否则会逐个执行
  • func:Job 执行的函数
  • args:Job 执行函数需要的位置参数
  • kwargs:Job 执行函数需要的关键字参数
Trigger 触发器

Trigger 绑定到 Job,在 scheduler 调度筛选 Job 时,根据触发器的规则计算出 Job 的触发时间,然后与当前时间比较确定此 Job 是否会被执行,总之就是根据 trigger 规则计算出下一个执行时间。

目前 APScheduler 支持触发器:

  • 指定时间的 DateTrigger
  • 指定间隔时间的 IntervalTrigger
  • 像 Linux 的 crontab 一样的 CronTrigger。
触发器参数:date

date 定时,作业只执行一次。

  • run_date (datetime|str) – the date/time to run the job at
  • timezone (datetime.tzinfo|str) – time zone for run_date if it doesn’t have one already
sched.add_job(my_job, 'date', run_date=date(2009, 11, 6), args=['text'])

sched.add_job(my_job, 'date', run_date=datetime(2019, 7, 6, 16, 30, 5), args=['text'])

触发器参数:interval

interval 间隔调度

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

触发器参数:cron

cron 调度

  • (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) -(表示时区取值)

CronTrigger 可用的表达式:

表达式参数类型描述
*所有通配符。例:minutes=*即每分钟触发
* / a所有每隔时长 a 执行一次。例:minutes=”* / 3″ 即每隔 3 分钟执行一次
a – b所有a – b 的范围内触发。例:minutes=“2-5”。即 2 到 5 分钟内每分钟执行一次
a – b / c所有a – b 范围内,每隔时长 c 执行一次。
xth y第几个星期几触发。x 为第几个,y 为星期几
last x一个月中,最后一个星期的星期几触发
last一个月中的最后一天触发
x, y, z所有组合表达式,可以组合确定值或上述表达式
# 6-8,11-12 月第三个周五 00:00, 01:00, 02:00, 03:00 运行

sched.add_job(job_function, 'cron', month='6-8,11-12', day='3rd fri', hour='0-3')

# 每周一到周五运行 直到 2024-05-30 00:00:00

sched.add_job(job_function, 'cron', day_of_week='mon-fri', hour=5, minute=30, end_date='2024-05-30'

Executor 执行器

Executor 在 scheduler 中初始化,另外也可通过 scheduler 的 add_executor 动态添加 Executor。每个 executor 都会绑定一个 alias,这个作为唯一标识绑定到 Job,在实际执行时会根据 Job 绑定的 executor 找到实际的执行器对象,然后根据执行器对象执行 Job。

Executor 的种类会根据不同的调度来选择,如果选择 AsyncIO 作为调度的库,那么选择 AsyncIOExecutor,如果选择 tornado 作为调度的库,选择 TornadoExecutor,如果选择启动进程作为调度,选择 ThreadPoolExecutor 或者 ProcessPoolExecutor 都可以。

Executor 的选择需要根据实际的 scheduler 来选择不同的执行器。目前 APScheduler 支持的 Executor:

  • executors.asyncio:同步 io,阻塞
  • executors.gevent:io 多路复 用,非阻塞
  • executors.pool: 线程 ThreadPoolExecutor 和进程 ProcessPoolExecutor
  • executors.twisted:基于事件驱动
Jobstore 作业存储

Jobstore 在 scheduler 中初始化,另外也可通过 scheduler 的 add_jobstore 动态添加 Jobstore。每个 jobstore 都会绑定一个 alias,scheduler 在 Add Job 时,根据指定的 jobstore 在 scheduler 中找到相应的 jobstore,并将 job 添加到 jobstore 中。作业存储器决定任务的保存方式, 默认存储在内存中(MemoryJobStore),重启后就没有了。APScheduler 支持的任务存储器有:

  • jobstores.memory:内存
  • jobstores.mongodb:存储在 mongodb
  • jobstores.redis:存储在 redis
  • jobstores.rethinkdb:存储在 rethinkdb
  • jobstores.sqlalchemy:支持 sqlalchemy 的数据库如 mysql,sqlite 等
  • jobstores.zookeeper:zookeeper

不同的任务存储器可以在调度器的配置中进行配置(见调度器)

Event 事件

Event 是 APScheduler 在进行某些操作时触发相应的事件,用户可以自定义一些函数来监听这些事件,当触发某些 Event 时,做一些具体的操作。常见的比如。Job 执行异常事件 EVENT_JOB_ERROR。Job 执行时间错过事件 EVENT_JOB_MISSED。

目前 APScheduler 定义的 Event:

  • EVENT_SCHEDULER_STARTED
  • EVENT_SCHEDULER_START
  • EVENT_SCHEDULER_SHUTDOWN
  • EVENT_SCHEDULER_PAUSED
  • EVENT_SCHEDULER_RESUMED
  • EVENT_EXECUTOR_ADDED
  • EVENT_EXECUTOR_REMOVED
  • EVENT_JOBSTORE_ADDED
  • EVENT_JOBSTORE_REMOVED
  • EVENT_ALL_JOBS_REMOVED
  • EVENT_JOB_ADDED
  • EVENT_JOB_REMOVED
  • EVENT_JOB_MODIFIED
  • EVENT_JOB_EXECUTED
  • EVENT_JOB_ERROR
  • EVENT_JOB_MISSED
  • EVENT_JOB_SUBMITTED
  • EVENT_JOB_MAX_INSTANCES

Listener 表示用户自定义监听的一些 Event,比如当 Job 触发了 EVENT_JOB_MISSED 事件时可以根据需求做一些其他处理。

调度器

Scheduler 是 APScheduler 的核心,所有相关组件通过其定义。scheduler 启动之后,将开始按照配置的任务进行调度。除了依据所有定义 Job 的 trigger 生成的将要调度时间唤醒调度之外。当发生 Job 信息变更时也会触发调度。

APScheduler 支持的调度器方式如下,比较常用的为 BlockingScheduler 和 BackgroundScheduler

  • BlockingScheduler:适用于调度程序是进程中唯一运行的进程,调用 start 函数会阻塞当前线程,不能立即返回。
  • BackgroundScheduler:适用于调度程序在应用程序的后台运行,调用 start 后主线程不会阻塞。
  • AsyncIOScheduler:适用于使用了 asyncio 模块的应用程序。
  • GeventScheduler:适用于使用 gevent 模块的应用程序。
  • TwistedScheduler:适用于构建 Twisted 的应用程序。
  • QtScheduler:适用于构建 Qt 的应用程序。
Scheduler 的工作流程

Scheduler 添加 job 流程:

图片

Scheduler 调度流程:

图片

使用分布式消息系统 Celery 实现定时任务

Celery[6] 是一个简单,灵活,可靠的分布式系统,用于处理大量消息,同时为操作提供维护此类系统所需的工具,也可用于任务调度。Celery 的配置比较麻烦,如果你只是需要一个轻量级的调度工具,Celery 不会是一个好选择。

Celery 是一个强大的分布式任务队列,它可以让任务的执行完全脱离主程序,甚至可以被分配到其他主机上运行。我们通常使用它来实现异步任务(async task)和定时任务(crontab)。异步任务比如是发送邮件、或者文件上传,图像处理等等一些比较耗时的操作 ,定时任务是需要在特定时间执行的任务。

需要注意,celery 本身并不具备任务的存储功能,在调度任务的时候肯定是要把任务存起来的,因此在使用 celery 的时候还需要搭配一些具备存储、访问功能的工具,比如:消息队列、Redis 缓存、数据库等。官方推荐的是消息队列 RabbitMQ,有些时候使用 Redis 也是不错的选择。

它的架构组成如下图:

图片

Celery 架构,它采用典型的生产者-消费者模式,主要由以下部分组成:

  • Celery Beat,任务调度器,Beat 进程会读取配置文件的内容,周期性地将配置中到期需要执行的任务发送给任务队列。
  • Producer:需要在队列中进行的任务,一般由用户、触发器或其他操作将任务入队,然后交由 workers 进行处理。调用了 Celery 提供的 API、函数或者装饰器而产生任务并交给任务队列处理的都是任务生产者。
  • Broker,即消息中间件,在这指任务队列本身,Celery 扮演生产者和消费者的角色,brokers 就是生产者和消费者存放/获取产品的地方(队列)。
  • Celery Worker,执行任务的消费者,从队列中取出任务并执行。通常会在多台服务器运行多个消费者来提高执行效率。
  • Result Backend:任务处理完后保存状态信息和结果,以供查询。Celery 默认已支持 Redis、RabbitMQ、MongoDB、Django ORM、SQLAlchemy 等方式。

实际应用中,用户从 Web 前端发起一个请求,我们只需要将请求所要处理的任务丢入任务队列 broker 中,由空闲的 worker 去处理任务即可,处理的结果会暂存在后台数据库 backend 中。我们可以在一台机器或多台机器上同时起多个 worker 进程来实现分布式地并行处理任务。

Celery 定时任务实例:

  • Python Celery & RabbitMQ Tutorial[7]
  • Celery 配置实践笔记[8]

使用数据流****工具 Apache Airflow 实现定时任务

Apache Airflow[9] 是 Airbnb 开源的一款数据流程工具,目前是 Apache 孵化项目。以非常灵活的方式来支持数据的 ETL 过程,同时还支持非常多的插件来完成诸如 HDFS 监控、邮件通知等功能。Airflow 支持单机和分布式两种模式,支持 Master-Slave 模式,支持 Mesos 等资源调度,有非常好的扩展性。被大量公司采用。

Airflow 使用 Python 开发,它通过 DAGs(Directed Acyclic Graph, 有向无环图)来表达一个工作流中所要执行的任务,以及任务之间的关系和依赖。比如,如下的工作流中,任务 T1 执行完成,T2 和 T3 才能开始执行,T2 和 T3 都执行完成,T4 才能开始执行。

图片

Airflow 提供了各种 Operator 实现,可以完成各种任务实现:

  • BashOperator – 执行 bash 命令或脚本。
    学好 Python 不论是就业还是做副业赚钱都不错,但要学会 Python 还是要有一个学习规划。最后大家分享一份全套的 Python 学习资料,给那些想学习 Python 的小伙伴们一点帮助!

一、Python所有方向的学习路线

Python所有方向路线就是把Python常用的技术点做整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。

二、学习软件

工欲善其事必先利其器。学习Python常用的开发软件都在这里了,给大家节省了很多时间。

三、全套PDF电子书

书籍的好处就在于权威和体系健全,刚开始学习的时候你可以只看视频或者听某个人讲课,但等你学完之后,你觉得你掌握了,这时候建议还是得去看一下书籍,看权威技术书籍也是每个程序员必经之路。

四、入门学习视频

我们在看视频学习的时候,不能光动眼动脑不动手,比较科学的学习方法是在理解之后运用它们,这时候练手项目就很适合了。

五、实战案例

光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。

六、面试资料

我们学习Python必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有阿里大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。

  • 11
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值