如何在Python中获得类似于Cron的调度程序?[关闭]


346

我正在寻找在Python库将提供atcron一样的功能。

我很想拥有一个纯Python解决方案,而不是依赖于安装在盒子上的工具;这样,我可以在没有cron的机器上运行。

对于不熟悉的用户,cron您可以根据以下表达式来安排任务:

 0 2 * * 7 /usr/bin/run-backup # run the backups at 0200 on Every Sunday
 0 9-17/2 * * 1-5 /usr/bin/purge-temps # run the purge temps command, every 2 hours between 9am and 5pm on Mondays to Fridays.

cron时间表达式语法不太重要,但是我希望具有这种灵活性。

如果没有任何东西可以立即为我执行此操作,将不胜感激地收到有关构建基块进行类似操作的任何建议。

编辑 我对启动过程不感兴趣,只是“工作”也用Python编写-python函数。必要时,我认为这将是一个不同的线程,但不会出现在不同的过程中。

为此,我正在寻找cron时间表达式的可表达性,但是在Python中。

Cron 已经存在了很多年,但我正在尝试尽可能地便携。我不能依靠它的存在。


1
我也想知道如何做到这一点。拥有跨平台解决方案比依赖于平台特定的组件更为有用。
肖恩

6
这不是题外话,这是一个非常重要和有用的问题
Connor

Answers:


571

如果您正在寻找轻巧的结帐时间表

import schedule
import time

def job():
    print("I'm working...")

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

while 1:
    schedule.run_pending()
    time.sleep(1)

披露:我是那个图书馆的作者。


7
您应该提到自己是的维护者schedule。对我来说效果很好。如果它具有像cron这样的语法和受支持的装饰器,那就更好了(请参阅crython,但不要使用此库,因为它不起作用;调度似乎写得不好)。
蒂姆·卢德温斯基

23
有没有办法将参数传递给作业?我想做这样的事情:schedule.every()。hour.do(job(myParam))
Zen Skunkworx

5
schedule.every()。hour.do(job)是否每时钟小时运行一次?例如01:00、02:00、03:00等?即使开始时间不是一个完整的小时?
swateek's

1
假设此代码在scheduler.py中。这段代码会自动运行吗?
Kishan

25
@ darrel-holt和@ zen-skunkworx:该do()函数将传递给它的其他参数转发给job函数:schedule.readthedocs.io/en/stable/api.html#schedule.Job.do例如,您可以执行此操作:schedule.every().hour.do(job, param1, param2)无需使用lambda。希望这会
有所

65

您可以只使用普通的Python参数传递语法来指定crontab。例如,假设我们定义一个Event类,如下所示:

from datetime import datetime, timedelta
import time

# Some utility classes / functions first
class AllMatch(set):
    """Universal set - match everything"""
    def __contains__(self, item): return True

allMatch = AllMatch()

def conv_to_set(obj):  # Allow single integer to be provided
    if isinstance(obj, (int,long)):
        return set([obj])  # Single item
    if not isinstance(obj, set):
        obj = set(obj)
    return obj

# The actual Event class
class Event(object):
    def __init__(self, action, min=allMatch, hour=allMatch, 
                       day=allMatch, month=allMatch, dow=allMatch, 
                       args=(), kwargs={}):
        self.mins = conv_to_set(min)
        self.hours= conv_to_set(hour)
        self.days = conv_to_set(day)
        self.months = conv_to_set(month)
        self.dow = conv_to_set(dow)
        self.action = action
        self.args = args
        self.kwargs = kwargs

    def matchtime(self, t):
        """Return True if this event should trigger at the specified datetime"""
        return ((t.minute     in self.mins) and
                (t.hour       in self.hours) and
                (t.day        in self.days) and
                (t.month      in self.months) and
                (t.weekday()  in self.dow))

    def check(self, t):
        if self.matchtime(t):
            self.action(*self.args, **self.kwargs)

(注意:未经彻底测试)

然后,可以使用普通的python语法将CronTab指定为:

c = CronTab(
  Event(perform_backup, 0, 2, dow=6 ),
  Event(purge_temps, 0, range(9,18,2), dow=range(0,5))
)

这样,您就可以充分利用Python的参数机制(混合使用位置和关键字args,并且可以将符号名称用于星期和几个月的名称)

将CronTab类定义为仅以分钟为增量休眠,并在每个事件上调用check()。(夏令时/时区可能有一些细微之处,请注意。)这是一个快速实现:

class CronTab(object):
    def __init__(self, *events):
        self.events = events

    def run(self):
        t=datetime(*datetime.now().timetuple()[:5])
        while 1:
            for e in self.events:
                e.check(t)

            t += timedelta(minutes=1)
            while datetime.now() < t:
                time.sleep((t - datetime.now()).seconds)

需要注意的几件事:Python的工作日/月为零索引(与cron不同),并且该范围排除了最后一个元素,因此像“ 1-5”这样的语法变为range(0,5)-即[0,1,2, 3,4]。如果您喜欢cron语法,那么对其进行解析应该不会太困难。


您可能想为没有经验的人添加一些导入语句。最后,我将所有类与datedate import *从time import sleep更改为单个文件,并将time.sleep更改为sleep。尼斯,简单优雅的解决方案。谢谢。
mavnn

1
只是想知道,为什么它比Kronos更受青睐?sched是越野车吗(因为kronos使用sched)?还是刚刚过时?
cregox

谢谢布赖恩,我在生产中使用了您的解决方案,并且效果很好。但是,正如其他人指出的那样,您的运行代码中存在一个细微的错误。我也发现它的需求过于复杂。
raph.amiard

1
这很酷,但仍不支持斜线符号,每小时,每分钟等执行……
Chris Koston 2012年

1
编写自己的类的好主意,例如,当我在服务器上没有sudo访问权限,因此无法pip install anything:)
Cometsong


27

我在搜索中看到的一件事是python的sched模块,这可能是您正在寻找的东西。


11
sched现在具有enterabs()来执行绝对调度。
Jerther

5
我希望这是首选的答案,因为sched现在是python2和3 stdlib的一部分,并且可以进行绝对调度。
迈克尔


11

与上面大致相同,但同时使用gevent :)

"""Gevent based crontab implementation"""

from datetime import datetime, timedelta
import gevent

# Some utility classes / functions first
def conv_to_set(obj):
    """Converts to set allowing single integer to be provided"""

    if isinstance(obj, (int, long)):
        return set([obj])  # Single item
    if not isinstance(obj, set):
        obj = set(obj)
    return obj

class AllMatch(set):
    """Universal set - match everything"""
    def __contains__(self, item): 
        return True

allMatch = AllMatch()

class Event(object):
    """The Actual Event Class"""

    def __init__(self, action, minute=allMatch, hour=allMatch, 
                       day=allMatch, month=allMatch, daysofweek=allMatch, 
                       args=(), kwargs={}):
        self.mins = conv_to_set(minute)
        self.hours = conv_to_set(hour)
        self.days = conv_to_set(day)
        self.months = conv_to_set(month)
        self.daysofweek = conv_to_set(daysofweek)
        self.action = action
        self.args = args
        self.kwargs = kwargs

    def matchtime(self, t1):
        """Return True if this event should trigger at the specified datetime"""
        return ((t1.minute     in self.mins) and
                (t1.hour       in self.hours) and
                (t1.day        in self.days) and
                (t1.month      in self.months) and
                (t1.weekday()  in self.daysofweek))

    def check(self, t):
        """Check and run action if needed"""

        if self.matchtime(t):
            self.action(*self.args, **self.kwargs)

class CronTab(object):
    """The crontab implementation"""

    def __init__(self, *events):
        self.events = events

    def _check(self):
        """Check all events in separate greenlets"""

        t1 = datetime(*datetime.now().timetuple()[:5])
        for event in self.events:
            gevent.spawn(event.check, t1)

        t1 += timedelta(minutes=1)
        s1 = (t1 - datetime.now()).seconds + 1
        print "Checking again in %s seconds" % s1
        job = gevent.spawn_later(s1, self._check)

    def run(self):
        """Run the cron forever"""

        self._check()
        while True:
            gevent.sleep(60)

import os 
def test_task():
    """Just an example that sends a bell and asd to all terminals"""

    os.system('echo asd | wall')  

cron = CronTab(
  Event(test_task, 22, 1 ),
  Event(test_task, 0, range(9,18,2), daysofweek=range(0,5)),
)
cron.run()

请注意,datetime.timetuple()将以年,月,日等开头...
Trey Stout

9

列出的解决方案均未尝试解析复杂的cron计划字符串。所以,这是我的版本,使用croniter。基本要点:

schedule = "*/5 * * * *" # Run every five minutes

nextRunTime = getNextCronRunTime(schedule)
while True:
     roundedDownTime = roundDownTime()
     if (roundedDownTime == nextRunTime):
         ####################################
         ### Do your periodic thing here. ###
         ####################################
         nextRunTime = getNextCronRunTime(schedule)
     elif (roundedDownTime > nextRunTime):
         # We missed an execution. Error. Re initialize.
         nextRunTime = getNextCronRunTime(schedule)
     sleepTillTopOfNextMinute()

辅助程序:

from croniter import croniter
from datetime import datetime, timedelta

# Round time down to the top of the previous minute
def roundDownTime(dt=None, dateDelta=timedelta(minutes=1)):
    roundTo = dateDelta.total_seconds()
    if dt == None : dt = datetime.now()
    seconds = (dt - dt.min).seconds
    rounding = (seconds+roundTo/2) // roundTo * roundTo
    return dt + timedelta(0,rounding-seconds,-dt.microsecond)

# Get next run time from now, based on schedule specified by cron string
def getNextCronRunTime(schedule):
    return croniter(schedule, datetime.now()).get_next(datetime)

# Sleep till the top of the next minute
def sleepTillTopOfNextMinute():
    t = datetime.utcnow()
    sleeptime = 60 - (t.second + t.microsecond/1000000.0)
    time.sleep(sleeptime)

有人怎么会陷入“错过执行” elif?Atm我正在使用一个计划,例如在“做定期的事情”中"* * * * *"添加time.sleep大于1分钟if的内容,但是我总是在if语句中看到这些内容。当花费超过1分钟时,我只会看到while循环跳过了缺少的循环执行。
TPPZ

@TPPZ该过程可能已暂停,时钟可能已手动或通过ntp等更改,等等。Airflow中使用了Croniter,它似乎比Crontab模块及其他模块功能更强大。
dlamblin

7

我已经修改了脚本。

  1. 易于使用:

    cron = Cron()
    cron.add('* * * * *'   , minute_task) # every minute
    cron.add('33 * * * *'  , day_task)    # every hour
    cron.add('34 18 * * *' , day_task)    # every day
    cron.run()
  2. 尝试在一分钟的第一秒开始任务。

Github上的代码


6

我有一个小的修复 Brian建议 CronTab类运行方法

计时时间为一秒钟,导致每分钟结束时出现一秒钟的硬循环。

class CronTab(object):
    def __init__(self, *events):
        self.events = events

    def run(self):
        t=datetime(*datetime.now().timetuple()[:5])
        while 1:
            for e in self.events:
                e.check(t)

            t += timedelta(minutes=1)
            n = datetime.now()
            while n < t:
                s = (t - n).seconds + 1
                time.sleep(s)
                n = datetime.now()

4

没有“纯python”方法可以执行此操作,因为某些其他过程将必须启动python才能运行您的解决方案。每个平台都有一种或二十种不同的方式来启动流程和监视其进度。在UNIX平台上,cron是旧标准。在Mac OS X上,还启动了该功能,它将类似cron的启动与看门狗功能结合在一起,如果需要的话,可以使您的进程保持活动状态。python运行后,即可使用sched模块安排任务。


4

我知道有很多答案,但是另一个解决方案可能是与装饰器搭配使用。这是每天在特定时间重复执行功能的示例。关于使用这种方式的很酷的想法是,您只需要向要计划的功能添加语法糖

@repeatEveryDay(hour=6, minutes=30)
def sayHello(name):
    print(f"Hello {name}")

sayHello("Bob") # Now this function will be invoked every day at 6.30 a.m

装饰器将如下所示:

def repeatEveryDay(hour, minutes=0, seconds=0):
    """
    Decorator that will run the decorated function everyday at that hour, minutes and seconds.
    :param hour: 0-24
    :param minutes: 0-60 (Optional)
    :param seconds: 0-60 (Optional)
    """
    def decoratorRepeat(func):

        @functools.wraps(func)
        def wrapperRepeat(*args, **kwargs):

            def getLocalTime():
                return datetime.datetime.fromtimestamp(time.mktime(time.localtime()))

            # Get the datetime of the first function call
            td = datetime.timedelta(seconds=15)
            if wrapperRepeat.nextSent == None:
                now = getLocalTime()
                wrapperRepeat.nextSent = datetime.datetime(now.year, now.month, now.day, hour, minutes, seconds)
                if wrapperRepeat.nextSent < now:
                    wrapperRepeat.nextSent += td

            # Waiting till next day
            while getLocalTime() < wrapperRepeat.nextSent:
                time.sleep(1)

            # Call the function
            func(*args, **kwargs)

            # Get the datetime of the next function call
            wrapperRepeat.nextSent += td
            wrapperRepeat(*args, **kwargs)

        wrapperRepeat.nextSent = None
        return wrapperRepeat

    return decoratorRepeat

1

Brian的解决方案运行良好。但是,正如其他人指出的那样,运行代码中存在一个细微的错误。我也发现它的需求过于复杂。

如果有人需要,这是我对运行代码更简单,更实用的替代方法:

def run(self):
    while 1:
        t = datetime.now()
        for e in self.events:
            e.check(t)

        time.sleep(60 - t.second - t.microsecond / 1000000.0)

1

另一个简单的解决方案是:

from aqcron import At
from time import sleep
from datetime import datetime

# Event scheduling
event_1 = At( second=5 )
event_2 = At( second=[0,20,40] )

while True:
    now = datetime.now()

    # Event check
    if now in event_1: print "event_1"
    if now in event_2: print "event_2"

    sleep(1)

而类aqcron.At是:

# aqcron.py

class At(object):
    def __init__(self, year=None,    month=None,
                 day=None,     weekday=None,
                 hour=None,    minute=None,
                 second=None):
        loc = locals()
        loc.pop("self")
        self.at = dict((k, v) for k, v in loc.iteritems() if v != None)

    def __contains__(self, now):
        for k in self.at.keys():
            try:
                if not getattr(now, k) in self.at[k]: return False
            except TypeError:
                if self.at[k] != getattr(now, k): return False
        return True

1
在发布复制和粘贴多个问题的样板/惯用答案时要小心,这些问题通常会被社区标记为“垃圾邮件”。如果您这样做,通常意味着问题是重复的,因此应将其标记为:stackoverflow.com/a/12360556/419
Kev

1

如果您正在寻找分布式调度程序,则可以查看https://github.com/sherinkurian/mani-尽管确实需要redis,所以可能不是您想要的。(请注意,我是作者)这是通过使时钟在多个节点上运行来确保容错的。


0

我不知道是否已经存在类似的东西。使用时间,日期时间和/或日历模块轻松编写自己的代码,请参见http://docs.python.org/library/time.html

python解决方案的唯一问题是您的工作需要始终运行,并且可能在重新启动后自动“恢复”,而您确实需要依赖于系统的解决方案。


3
自己动手是一个选择-尽管最好的代码是您不必编写的代码。我想复活是我可能需要考虑的事情。
jamesh


0

服务器上Crontab的方法。

Python文件名hello.py

步骤1:创建一个sh文件,让其命名为s.sh

python3 /home/ubuntu/Shaurya/Folder/hello.py> /home/ubuntu/Shaurya/Folder/log.txt 2>&1

第2步:打开Crontab编辑器

crontab -e

步骤3:添加计划时间

使用Crontab格式

2 * * * * sudo sh /home/ubuntu/Shaurya/Folder/s.sh

该cron将在“第2分钟”运行。


0

我喜欢pycron软件包如何解决此问题。

import pycron
import time

while True:
    if pycron.is_now('0 2 * * 0'):   # True Every Sunday at 02:00
        print('running backup')
    time.sleep(60)

1
这不是一个好主意,因为您的代码“ print('running backup')”将每隔5秒启动一分钟。因此,在这种情况下,延迟应为60秒。
n158

你是对的!感谢您指出了这一点。
杜夫
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.