Bagaimana cara saya mendapatkan scheduler seperti Cron di Python? [Tutup]


348

Saya mencari perpustakaan dengan Python yang akan menyediakan atdan cronmenyukai fungsionalitas.

Saya cukup suka memiliki solusi Python murni, daripada mengandalkan alat yang diinstal pada kotak; dengan cara ini saya menjalankan mesin tanpa cron.

Bagi mereka yang tidak terbiasa dengan cron: Anda dapat menjadwalkan tugas berdasarkan pada ekspresi seperti:

 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.

Sintaks ekspresi waktu cron kurang penting, tetapi saya ingin memiliki sesuatu dengan fleksibilitas semacam ini.

Jika tidak ada sesuatu yang melakukan ini untuk saya di luar kotak, saran untuk blok bangunan untuk membuat sesuatu seperti ini akan diterima dengan penuh syukur.

Sunting Saya tidak tertarik dalam meluncurkan proses, hanya "pekerjaan" juga ditulis dengan fungsi Python - python. Menurut kebutuhan saya pikir ini akan menjadi utas yang berbeda, tetapi tidak dalam proses yang berbeda.

Untuk tujuan ini, saya mencari ekspresifitas ekspresi waktu cron, tetapi dengan Python.

Cron telah ada selama bertahun-tahun, tetapi saya mencoba untuk menjadi se portable mungkin. Saya tidak bisa mengandalkan kehadirannya.


1
Saya juga ingin tahu bagaimana melakukan ini. Akan lebih bermanfaat untuk memiliki solusi lintas platform daripada bergantung pada komponen platform spesifik.
Sean

6
Ini bukan di luar topik, ini adalah pertanyaan yang sangat penting dan berguna
Connor

Jawaban:


571

Jika Anda mencari jadwal checkout yang ringan :

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)

Pengungkapan : Saya penulis perpustakaan itu.


7
Anda harus menyebutkan bahwa Anda adalah pengelola schedule. Itu bekerja dengan baik untuk saya. Akan lebih baik jika memiliki cron seperti sintaks dan dekorator yang didukung (lihat crython tetapi tidak menggunakan perpustakaan ini karena tidak berfungsi; penjadwalan sepertinya tidak ditulis dengan baik).
Tim Ludwinski

23
Apakah ada cara untuk mengirimkan parameter ke pekerjaan? Saya ingin melakukan sesuatu seperti ini: schedule.every (). Hour.do (pekerjaan (myParam))
Zen Skunkworx

5
schedule.every (). hour.do (pekerjaan) apakah ini berjalan setiap jam? Seperti 01:00, 02:00, 03:00, dll.? bahkan jika waktu mulai bukan satu jam penuh?
swateek

1
misalkan kode ini ada di scheduler.py. akankah kode ini berjalan secara otomatis?
Kishan

25
@ darrel-holt dan @ zen-skunkworx: do()Fungsi meneruskan argumen tambahan yang Anda berikan ke fungsi pekerjaan: schedule.readthedocs.io/en/stable/api.html#schedule.Job.do Misalnya, Anda dapat melakukan ini : schedule.every().hour.do(job, param1, param2)Tidak perlu menggunakan lambda. Semoga ini bisa membantu :)
dbader

65

Anda bisa menggunakan argumen Python normal lewat sintaks untuk menentukan crontab Anda. Sebagai contoh, misalkan kita mendefinisikan kelas Peristiwa seperti di bawah ini:

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)

(Catatan: Tidak diuji secara menyeluruh)

Maka CronTab Anda dapat ditentukan dalam sintaks python normal sebagai:

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

Dengan cara ini Anda mendapatkan kekuatan penuh dari mekanisme argumen Python (mencampur argumen posisi dan kata kunci, dan dapat menggunakan nama simbolik untuk nama minggu dan bulan)

Kelas CronTab akan didefinisikan sebagai hanya tidur dengan penambahan menit, dan memanggil cek () pada setiap acara. (Mungkin ada beberapa seluk-beluk dengan waktu / zona waktu siang hari untuk diwaspadai) Berikut ini adalah implementasi cepat:

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)

Beberapa hal yang perlu diperhatikan: Hari kerja / bulan Python tidak diindeks nol (tidak seperti cron), dan rentang itu tidak termasuk elemen terakhir, maka sintaksis seperti "1-5" menjadi rentang (0,5) - yaitu [0,1,2, 3,4]. Jika Anda lebih suka sintaks cron, parsing itu seharusnya tidak terlalu sulit.


Anda mungkin ingin menambahkan beberapa pernyataan impor untuk yang belum berpengalaman. Saya akhirnya meletakkan semua kelas dalam satu file dengan dari impor datetime * dari waktu import sleep dan mengubah time.sleep ke sleep. Bagus, solusi elegan sederhana. Terima kasih.
mavnn

1
Hanya ingin tahu, mengapa ini lebih disukai daripada Kronos? Apakah sched itu buggy (karena kronos menggunakan sched)? Atau ini sudah ketinggalan zaman?
cregox

Terima kasih brian, saya menggunakan solusi Anda dalam produksi dan itu berfungsi dengan baik. Namun, seperti yang orang lain tunjukkan, ada bug halus di kode jalankan Anda. Juga saya merasa terlalu rumit untuk kebutuhan.
raph.amiard

1
Ini keren, tapi masih tidak mendukung notasi slash, untuk eksekusi setiap jam, min, dll ...
Chris Koston

1
Ide bagus untuk menulis kelas Anda sendiri, misalnya ketika saya tidak memiliki akses sudo pada server dan karenanya tidak bisa pip install anything:)
Cometsong


27

Satu hal yang saya cari dalam pencarian saya adalah schedmodul python yang mungkin merupakan jenis yang Anda cari.


11
sched sekarang memiliki enterabs () yang melakukan penjadwalan absolut.
Jerther

5
Saya berharap ini menjadi jawaban yang disukai karena sched adalah bagian dari python2 dan 3 stdlib sekarang dan melakukan penjadwalan absolut.
Michael


11

Lebih atau kurang sama dengan di atas tetapi bersamaan menggunakan 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()

Hanya catatan bahwa datetime.timetuple () akan dimulai dengan tahun, bulan, hari ... dll ...
Trey Stout

9

Tak satu pun dari solusi yang terdaftar bahkan berupaya mengurai string jadwal cron yang kompleks. Jadi, ini versi saya, menggunakan croniter . Inti dasar:

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

Rutinitas pembantu:

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)

Bagaimana seseorang bisa masuk ke "melewatkan eksekusi" elif? Atm Saya menggunakan jadwal seperti "* * * * *"kemudian menambahkan beberapa time.sleeplebih besar dari 1 menit di "Lakukan hal periodik Anda" if, tapi saya selalu melihat hal-hal dalam pernyataan itu jika. Ketika dibutuhkan lebih dari 1 menit, saya hanya melihat loop sementara melewatkan eksekusi loop yang hilang.
TPPZ

@TPPZ Prosesnya bisa ditunda, jam bisa diubah secara manual atau dengan ntp dll. Croniter digunakan dalam Airflow dan tampaknya lebih berfitur lengkap daripada modul Crontab dan lainnya.
dlamblin

7

Saya telah memodifikasi skrip.

  1. Mudah digunakan:

    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. Cobalah untuk memulai tugas di detik pertama.

Kode pada Github


6

Saya memiliki perbaikan kecil untuk metode menjalankan kelas CronTab yang disarankan oleh Brian .

Waktunya habis oleh satu detik yang mengarah ke satu detik, putaran keras pada akhir setiap menit.

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

Tidak ada cara "python murni" untuk melakukan ini karena beberapa proses lain harus meluncurkan python untuk menjalankan solusi Anda. Setiap platform akan memiliki satu atau dua puluh cara berbeda untuk meluncurkan proses dan memantau perkembangannya. Pada platform unix, cron adalah standar lama. Di Mac OS X ada juga launchd, yang menggabungkan peluncuran mirip cron dengan fungsionalitas watchdog yang dapat menjaga proses Anda tetap hidup jika itu yang Anda inginkan. Setelah python berjalan, maka Anda dapat menggunakan modul sched untuk menjadwalkan tugas.


4

Saya tahu ada banyak jawaban, tetapi solusi lain adalah dengan dekorator . Ini adalah contoh untuk mengulangi fungsi setiap hari pada waktu tertentu. Pikiran keren tentang menggunakan cara ini adalah Anda hanya perlu menambahkan Gula Sintaksis ke fungsi yang ingin Anda jadwalkan:

@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

Dan dekorator akan terlihat seperti:

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

Solusi Brian bekerja dengan cukup baik. Namun, seperti yang orang lain tunjukkan, ada bug yang tidak kentara dalam menjalankan kode. Juga saya merasa terlalu rumit untuk kebutuhan.

Berikut ini adalah alternatif yang lebih sederhana dan fungsional untuk menjalankan kode jika ada yang membutuhkannya:

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

Solusi sepele lainnya adalah:

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)

Dan aqcron kelas. Itu adalah:

# 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
Hati-hati saat memposting salin dan tempel jawaban boilerplate / kata demi kata untuk beberapa pertanyaan, ini cenderung ditandai sebagai "spam" oleh komunitas. Jika Anda melakukan ini, biasanya itu berarti pertanyaannya adalah duplikat, jadi tandailah sebagai berikut: stackoverflow.com/a/12360556/419
Kev

1

Jika Anda mencari penjadwal terdistribusi, Anda dapat melihat https://github.com/sherinkurian/mani - itu memang perlu redis, jadi mungkin bukan yang Anda cari. (perhatikan bahwa saya penulisnya) ini dibuat untuk memastikan toleransi kesalahan dengan menjalankan jam di lebih dari satu simpul.


0

Saya tidak tahu apakah sesuatu seperti itu sudah ada. Akan mudah untuk menulis sendiri dengan waktu, modul waktu dan / atau modul kalender, lihat http://docs.python.org/library/time.html

Satu-satunya kekhawatiran untuk solusi python adalah bahwa kebutuhan tugas Anda untuk selalu berjalan dan mungkin secara otomatis "dibangkitkan" setelah reboot, sesuatu yang Anda lakukan perlu mengandalkan sistem solusi tergantung.


3
Gulung sendiri adalah sebuah opsi - meskipun kode terbaik adalah kode yang tidak harus Anda tulis. Kebangkitan, saya kira adalah sesuatu yang mungkin perlu saya pertimbangkan.
jamesh


0

Metode Crontab di Server.

Nama file python hello.py

Langkah1: Buat file sh, beri nama s.sh

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

Langkah2: Buka Crontab Editor

crontab -e

Langkah 3: Tambahkan Jadwal Waktu

Gunakan Pemformatan Crontab

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

Cron ini akan menjalankan "Pada menit 2."


0

Saya suka bagaimana paket pycron menyelesaikan masalah ini.

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
Ini bukan ide yang baik, karena kode Anda "print ('running backup')" akan meluncurkan seluruh menit dengan interval 5s. Jadi dalam hal ini keterlambatan harus 60 detik.
n158

Kamu benar! Terima kasih telah menunjukkannya.
Duffau
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.