How do I get a Cron like scheduler in Python?
我正在寻找一个用python编写的库,它将提供类似于
我非常希望有一个纯粹的Python解决方案,而不是依赖安装在盒子上的工具;这样我就可以在没有cron的机器上运行。
对于不熟悉
1 2 | 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时间表达式语法不那么重要,但我希望有这种灵活性。
如果没有什么东西对我来说是开箱即用的,那么对于构建块做出这样的东西的任何建议都将得到感激。
编辑我对启动进程不感兴趣,只是"jobs"也用python-python函数编写。必要时,我认为这是一条不同的线,但不是在一个不同的过程中。
为此,我在寻找cron-time表达式的表现力,但在python中。
Cron已经存在多年了,但我正在尽可能的便携。我不能依靠它的存在。
如果您正在寻找轻量级的结账时间表:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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) |
披露:我是那个图书馆的作者。
您可以使用普通的python参数传递语法来指定crontab。例如,假设我们定义一个事件类如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | 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指定为:
1 2 3 4 | c = CronTab( Event(perform_backup, 0, 2, dow=6 ), Event(purge_temps, 0, range(9,18,2), dow=range(0,5)) ) |
这样,您就可以充分利用Python的参数机制(混合位置参数和关键字参数,并可以使用符号名称表示周和月的名称)。
crontab类将被定义为简单地以分钟为增量睡眠,并对每个事件调用check()。(不过,夏令时/时区可能有一些微妙之处需要注意)。下面是一个快速实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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语法,那么解析它不应该太困难。
也许这个问题是在被问到之后才出现的;我想我只是为了完整起见才提到它:https://apscheduler.readthedocs.org/en/latest/
看看芹菜,它们有像cron这样的周期性任务。
"……"crontab模块,用于读取和写入crontab文件,并使用直接API自动访问系统cron。……"
http://pypi.python.org/pypi/python-crontab
还有apscheduler,一个python包。已写入并调试。
http://packages.python.org/apscheduler/cronschedule.html
在我的搜索中,我看到的一件事是python的
或多或少与上述相同,但同时使用gevent:)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | """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() |
基于Kronos的具有预定任务能力的涡轮齿轮船
我从来没有直接使用过Kronos,但是TG中的调度有一套很好的特性,而且是可靠的。
所有列出的解决方案都没有尝试解析复杂的cron调度字符串。所以,这里是我的版本,使用croniter。基本要旨:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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() |
帮助程序例程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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) |
我已经修改了脚本。
易于使用:
1 2 3 4 5 | 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() |
尝试在一分钟的第一秒钟内开始任务。
GITHUB代码
查看luigi(https://github.com/spotify/luigi)。它是用python编写的,有一个很好的用于监视任务的Web UI。它也有一个依赖关系图。可能会因为你需要的东西而被过度杀戮,但它可能会起作用。
我对Brian建议的crontab类run方法有一个小修复。
计时结束了一秒钟,进入了一秒钟,每分钟结束时都会有一个硬循环。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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() |
没有一种"纯Python"方法可以做到这一点,因为其他一些进程必须启动Python才能运行您的解决方案。每个平台将有一种或二十种不同的方法来启动流程并监控其进度。在UNIX平台上,cron是旧标准。在MacOSX上还有launchd,它结合了类似cron的启动和看门狗的功能,如果你想要的话,可以让你的进程保持活动。一旦python运行了,就可以使用sched模块来调度任务。
以防万一,如果您使用的是Windows,就存在一个pycron。查看http://sourceforge.net/projects/pycron/。对于Linux,我将使用cron或sched。
布赖恩的解决方案很有效。然而,正如其他人指出的,运行代码中有一个微妙的错误。而且我发现它对于需求来说过于复杂。
以下是我对运行代码的简单和实用的替代方案,以防有人需要它:
1 2 3 4 5 6 7 | 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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类是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | # 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 |
如果您正在寻找一个分布式调度程序,您可以查看https://github.com/sherinkurian/mani-它确实需要redis,所以可能不是您要找的。(注意我是作者)这是为了通过在多个节点上运行时钟来确保容错性而构建的。
我不知道这样的东西是否已经存在。使用时间、日期时间和/或日历模块很容易编写自己的文档,请参见http://docs.python.org/library/time.html。
对Python解决方案唯一的关注是,您的作业需要始终运行,并且可能在重新启动后自动"恢复",对于这种情况,您确实需要依赖于系统相关的解决方案。
我知道有很多答案,但另一个解决办法可能是和装饰师一起。这是一个在特定时间每天重复一个函数的例子。使用这种方式的一个很酷的想法是,您只需要向您想要调度的函数添加语法糖分:
1 2 3 4 5 | @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 |
装饰师看起来像:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | 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 |
您可以查看picloud的[1]crons[2],但请注意,您的作业不会在您自己的计算机上运行。如果你一个月使用超过20小时的计算时间,这也是一项你需要支付的服务。
[1]网址:http://www.picloud.com
[2]网址:http://docs.picloud.com/cron.html
如果要运行的脚本基于Web,可以考虑使用第三方服务(如crono)以编程方式设置作业。
我采用了布莱恩的解决方案,做了一些修改,添加了标准crontab文件解析器的开头,并将其放在https://bitback.org/dbename/devcron上。