Wie bekomme ich einen Cron-ähnlichen Scheduler in Python? [geschlossen]


348

Ich suche nach einer Bibliothek in Python, die Funktionen bietet atund cronmag.

Ich hätte gerne eine reine Python-Lösung, anstatt mich auf Tools zu verlassen, die auf der Box installiert sind. Auf diese Weise laufe ich auf Maschinen ohne Cron.

Für diejenigen, die nicht vertraut sind mit cron: Sie können Aufgaben basierend auf einem Ausdruck planen wie:

 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.

Die Cron-Time-Ausdruckssyntax ist weniger wichtig, aber ich hätte gerne etwas mit dieser Art von Flexibilität.

Wenn es nichts gibt, das dies für mich sofort erledigt, werden Vorschläge für die Bausteine, um so etwas zu machen, dankbar entgegengenommen.

Bearbeiten Ich bin nicht daran interessiert, Prozesse zu starten, sondern nur "Jobs", die auch in Python-Python-Funktionen geschrieben sind. Notwendigerweise denke ich, dass dies ein anderer Thread wäre, aber nicht in einem anderen Prozess.

Zu diesem Zweck suche ich nach der Ausdruckskraft des Cron-Zeitausdrucks, aber in Python.

Cron gibt es schon seit Jahren, aber ich versuche so portabel wie möglich zu sein. Ich kann mich nicht auf seine Anwesenheit verlassen.


1
Ich würde auch gerne wissen, wie das geht. Eine plattformübergreifende Lösung wäre nützlicher, als von plattformspezifischen Komponenten abhängig zu sein.
Sean

6
Dies ist kein Off-Topic, dies ist eine sehr wichtige und nützliche Frage
Connor

Antworten:


571

Wenn Sie nach einem leichten Checkout- Zeitplan suchen :

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)

Offenlegung : Ich bin der Autor dieser Bibliothek.


7
Sie sollten erwähnen, dass Sie der Betreuer von sind schedule. Es hat gut für mich funktioniert. Es wäre noch schöner, wenn es eine Cron-ähnliche Syntax und unterstützte Dekoratoren hätte (siehe Crython, aber benutze diese Bibliothek nicht, weil sie nicht funktioniert; die Planung scheint nicht gut geschrieben zu sein).
Tim Ludwinski

23
Gibt es eine Möglichkeit, einen Parameter an den Job zu übergeben? Ich möchte so etwas tun: Schedule.every (). Hour.do (Job (myParam))
Zen Skunkworx

5
Schedule.every (). hour.do (Job) läuft dies jede Stunde? Wie 01:00, 02:00, 03:00 usw.? auch wenn die Startzeit keine volle Stunde ist?
Swateek

1
Angenommen, dieser Code befindet sich in scheduler.py. Wird dieser Code automatisch ausgeführt?
Kishan

25
@ darrel-holt und @ zen-skunkworx: Die do()Funktion leitet zusätzliche Argumente, die Sie an sie übergeben, an die Jobfunktion weiter : Schedule.readthedocs.io/en/stable/api.html#schedule.Job.do Sie können dies beispielsweise tun : schedule.every().hour.do(job, param1, param2)Kein Lambda erforderlich. Hoffe das hilft :)
dbader

65

Sie können einfach die normale Syntax zur Übergabe von Python-Argumenten verwenden, um Ihre crontab anzugeben. Angenommen, wir definieren eine Ereignisklasse wie folgt:

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)

(Hinweis: Nicht gründlich getestet)

Dann kann Ihr CronTab in der normalen Python-Syntax wie folgt angegeben werden:

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

Auf diese Weise erhalten Sie die volle Leistungsfähigkeit der Argumentationsmechanik von Python (Mischen von Positions- und Schlüsselwortargumenten und Verwenden symbolischer Namen für Namen von Wochen und Monaten).

Die CronTab-Klasse wird so definiert, dass sie einfach in Minutenschritten schläft und bei jedem Ereignis check () aufruft. (Es gibt wahrscheinlich einige Feinheiten mit Sommerzeit / Zeitzonen, auf die Sie achten sollten). Hier ist eine schnelle Implementierung:

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)

Ein paar Dinge, die zu beachten sind: Pythons Wochentage / Monate sind (im Gegensatz zu cron) mit Null indiziert, und dieser Bereich schließt das letzte Element aus, daher wird Syntax wie "1-5" zu Bereich (0,5) - dh [0,1,2, 3,4]. Wenn Sie die Cron-Syntax bevorzugen, sollte das Parsen jedoch nicht zu schwierig sein.


Möglicherweise möchten Sie einige Importanweisungen für Unerfahrene hinzufügen. Am Ende habe ich alle Klassen in einer einzigen Datei mit dem Datum-Uhrzeit-Import * vom Zeit-Import-Ruhezustand zusammengefasst und time.sleep in den Ruhezustand geändert. Schöne, einfache, elegante Lösung. Vielen Dank.
Uhr

1
Ich frage mich nur, warum dies Kronos vorgezogen wird. Ist Sched so fehlerhaft (da Kronen Sched verwendet)? Oder ist das nur veraltet?
Cregox

Danke Brian, ich benutze deine Lösung in der Produktion und sie funktioniert ganz gut. Wie andere bereits betont haben, gibt es jedoch einen subtilen Fehler in Ihrem Ausführungscode. Ich fand es auch zu kompliziert für die Bedürfnisse.
raph.amiard

1
Dies ist cool, unterstützt aber immer noch keine Schrägstrichnotation für die Ausführung jede Stunde, Minute usw.
Chris Koston

1
Ausgezeichnete Idee, eigene Klassen zu schreiben, zB wenn ich keinen Sudo-Zugriff auf einem Server habe und daher nicht kann pip install anything:)
Cometsong


27

Eine Sache, die ich bei meinen Suchen gesehen habe, ist das Python- schedModul, nach dem Sie vielleicht suchen.


11
sched hat jetzt enterabs (), das die absolute Planung durchführt.
Jerther

5
Ich würde erwarten, dass dies die bevorzugte Antwort ist, da sched jetzt Teil von python2 und 3 stdlib ist und absolute Planung durchführt.
Michael


11

Mehr oder weniger wie oben, aber gleichzeitig mit 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()

Nur eine Anmerkung, dass datetime.timetuple () mit Jahr, Monat, Tag ... etc ... beginnt
Trey Stout

9

Keine der aufgelisteten Lösungen versucht, eine komplexe Cron-Zeitplanzeichenfolge zu analysieren. Also, hier ist meine Version mit Croniter . Grundlegendes:

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()

Hilfsroutinen:

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)

Wie könnte jemand in die "verpasste Hinrichtung" gehen elif? Atm Ich verwende einen Zeitplan wie das "* * * * *"Hinzufügen von time.sleepmehr als 1 Minute in der "Do your periodic thing" if, aber ich sehe immer das Zeug in dieser if-Anweisung. Wenn es länger als 1 Minute dauert, sehe ich nur, wie die while-Schleife diese fehlende Schleifenausführung überspringt.
TPPZ

@TPPZ Der Prozess könnte angehalten worden sein, die Uhr könnte manuell oder durch NTP usw. usw. geändert worden sein. Croniter wird in Airflow verwendet und scheint umfassender zu sein als das Crontab-Modul und andere.
Dlamblin

7

Ich habe das Skript geändert.

  1. Einfach zu verwenden:

    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. Versuchen Sie, die Aufgabe in der ersten Sekunde einer Minute zu starten.

Code auf Github


6

Ich habe eine kleinere Korrektur für die von Brian vorgeschlagene CronTab-Klassenlaufmethode .

Das Timing war um eine Sekunde verschoben, was am Ende jeder Minute zu einer 1-Sekunden-Schleife führte.

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

Es gibt keine "reine Python" -Methode, da ein anderer Prozess Python starten müsste, um Ihre Lösung auszuführen. Jede Plattform bietet eine oder zwanzig verschiedene Möglichkeiten, um Prozesse zu starten und ihren Fortschritt zu überwachen. Auf Unix-Plattformen ist cron der alte Standard. Unter Mac OS X gibt es auch launchd, das das Cron-ähnliche Starten mit Watchdog-Funktionen kombiniert, die Ihren Prozess am Leben erhalten, wenn Sie dies wünschen. Sobald Python ausgeführt wird, können Sie das Planungsmodul verwenden, um Aufgaben zu planen.


4

Ich weiß, dass es viele Antworten gibt, aber eine andere Lösung könnte darin bestehen Dekorateuren zusammenzuarbeiten . Dies ist ein Beispiel, um eine Funktion jeden Tag zu einer bestimmten Zeit zu wiederholen. Der coole Gedanke bei dieser Verwendung ist, dass Sie nur den Syntaktischen Zucker zu der Funktion hinzufügen müssen , die Sie planen möchten:

@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

Und der Dekorateur wird aussehen wie:

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

Brians Lösung funktioniert ganz gut. Wie andere bereits betont haben, gibt es jedoch einen subtilen Fehler im Ausführungscode. Ich fand es auch zu kompliziert für die Bedürfnisse.

Hier ist meine einfachere und funktionalere Alternative für den Ausführungscode, falls jemand ihn benötigt:

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

Eine andere triviale Lösung wäre:

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)

Und die Klasse aqcron.At ist:

# 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
Seien Sie vorsichtig, wenn Sie Antworten auf mehrere Fragen kopieren und einfügen. Diese werden von der Community in der Regel als "Spam" gekennzeichnet. Wenn Sie dies tun, bedeutet dies normalerweise, dass die Fragen Duplikate sind. Kennzeichnen Sie sie stattdessen als solche: stackoverflow.com/a/12360556/419
Kev

1

Wenn Sie nach einem verteilten Planer suchen, können Sie https://github.com/sherinkurian/mani besuchen. Er muss jedoch überarbeitet werden und ist möglicherweise nicht das, wonach Sie suchen. (Beachten Sie, dass ich der Autor bin) Dies wurde erstellt, um Fehlertoleranz zu gewährleisten, indem die Uhr auf mehr als einem Knoten ausgeführt wird.


0

Ich weiß nicht, ob so etwas schon existiert. Es wäre einfach, eigene Zeit-, Datums- und / oder Kalendermodule zu schreiben, siehe http://docs.python.org/library/time.html

Die einzige Sorge für eine Python - Lösung ist , dass Ihre Arbeit Bedürfnisse immer zu laufen und möglicherweise automatisch „wiederbelebt“ werden nach einem Neustart, etwas , für das Sie tun müssen auf systemabhängige Lösungen angewiesen.


3
Roll your own ist eine Option - obwohl der beste Code Code ist, den Sie nicht schreiben müssen. Ich nehme an, Auferstehung ist etwas, über das ich nachdenken muss.
Jamesh


0

Methode von Crontab auf dem Server.

Python-Dateiname hello.py

Schritt 1: Erstellen Sie eine sh-Datei mit dem Namen s.sh.

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

Schritt 2: Öffnen Sie den Crontab Editor

crontab -e

Schritt 3: Zeitplan hinzufügen

Verwenden Sie die Crontab-Formatierung

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

Dieser Cron läuft "In Minute 2".


0

Mir gefällt, wie das Pycron- Paket dieses Problem löst.

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
Dies ist keine gute Idee, da Ihr Code "print ('running backup')" die gesamte Minute im Abstand von 5 Sekunden startet. In diesem Fall sollte die Verzögerung also 60 Sekunden betragen.
n158

Du hast recht! Vielen Dank für den Hinweis.
Duffau
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.