Як отримати планувальник Cron у Python? [зачинено]


348

Я шукаю бібліотеку в Python, яка надаватиме atі cronподобається функціонал.

Я б дуже хотів мати чисте рішення Python, а не покладатися на інструменти, встановлені на коробці; таким чином я бігаю на машинах без крона.

Для тих, хто не знає 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.

Синтаксис вираження хронового часу є менш важливим, але я хотів би мати щось із такою гнучкістю.

Якщо не щось, що робить це для мене поза межами, будь-які пропозиції щодо будівельних блоків, щоб зробити щось подібне, були б вдячні.

Редагувати Я не зацікавлений у запуску процесів, просто "завдання" також написані в функціях Python - python. За необхідності я думаю, що це була б інша нитка, але не в іншому процесі.

З цією метою я шукаю виразність вираження хронового часу, але в Python.

Cron вже багато років, але я намагаюся бути максимально портативним. Я не можу покластися на його присутність.


1
Я також хотів би знати, як це зробити. Було б корисніше рішення для крос-платформ, ніж залежати від конкретних компонентів платформи.
Шон

7
Це не тематика, це дуже важливе і корисне питання
Коннор

Відповіді:


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. Це добре спрацювало для мене. Було б навіть приємніше, якби він мав синтаксис, подібний до хронів, та підтримувані декоратори (див. Критон, але не використовуйте цю бібліотеку, оскільки вона не працює; планування, здається, не написано добре).
Тім Людвінський

23
Чи є спосіб передати параметр до завдання? Я хотів би зробити щось подібне: raspored.every (). Hour.do (робота (myParam))
Zen Skunkworx

5
графік.every (). hour.do (робота) це працює щогодини? Як 01:00, 02:00, 03:00 тощо? навіть якщо час початку не повну годину?
swateek

1
припустимо, цей код є у rasporedler.py. чи буде цей код запускатися автоматично?
Кішань

25
@ darrel-holt і @ zen-skunkworx: do()Функція пересилає додаткові аргументи, які ви передаєте їй функцію завдання: raspored.readthedocs.io/en/stable/api.html#schedule.Job.do Наприклад, ви можете це зробити : schedule.every().hour.do(job, param1, param2)Не потрібно використовувати лямбда. Сподіваюсь, це допомагає :)
dbader

65

Ви можете просто використовувати звичайний синтаксис, що передає аргумент Python, щоб вказати свій crontab. Наприклад, припустимо, що ми визначаємо клас події, як показано нижче:

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)

(Примітка: Не ретельно перевірено)

Тоді ваш CronTab може бути вказаний у звичайному синтаксисі python як:

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

Таким чином ви отримуєте всю потужність механіки аргументів Python (змішування позиційних та ключових аргументів, а також можете використовувати символічні назви для назв тижнів і місяців)

Клас 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" стає діапазоном (0,5) - тобто [0,1,2, 3,4]. Якщо ви віддаєте перевагу синтаксису cron, розбір його не повинен бути надто складним.


Ви можете додати деякі заяви про імпорт для недосвідчених. У кінцевому підсумку я помістив усі класи в один файл з імпортуванням дат * з режиму сну імпорту та змінив час на час сну. Приємне, просте елегантне рішення. Дякую.
mavnn

1
Тільки цікаво, чому це віддається перевазі Кроносу? Чи запланований цей баггі (оскільки kronos використовує sched)? Або це просто застаріло?
Крегокс

Дякую Брайан, я використовую ваше рішення у виробництві, і воно працює досить добре. Однак, як зазначали інші, у вашому коді запуску є непомітна помилка. Крім того, я виявив це занадто складним для потреб.
raph.amiard

1
Це круто, але все ще не підтримує позначення слэш для виконання щогодини, хв. Тощо ...
Кріс Костон

1
Відмінна ідея писати власні заняття, наприклад, коли у мене немає доступу до pip install anything
судо


27

Одне, що в своїх пошуках я бачив - це schedмодуль python, який може бути саме тим, що ви шукаєте.


11
Тепер у sched є enterabs (), який робить абсолютне планування.
Джертер

5
Я б очікував, що це буде кращою відповіддю, оскільки планування є частиною python2 та 3 stdlib зараз і робить абсолютне планування.
Майкл

20

"... Модуль Crontab для читання та запису файлів crontab та доступу до системного cron автоматично та просто за допомогою прямого API. ..."

http://pypi.python.org/pypi/python-crontab

а також APScheduler, пакет пітонів. Вже написано та налагоджено.

http://packages.python.org/APScheduler/cronschedule.html


1
ОП спеціально попросила щось, що працюватиме на системах без
крона

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

Жодне із перерахованих рішень навіть не намагається проаналізувати складний рядок розкладу хронів. Отже, ось моя версія з використанням кронітера . Основна суть:

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 хвилини, я просто бачу, як цикл пропускає виконання цього циклу.
TPPZ

@TPPZ Процес міг бути призупинений, годинник можна було змінити вручну або ntp і т.д.
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

У мене є незначне виправлення методу запуску класу 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 також є запуск, який поєднує в собі крон-подібний запуск з функціональністю сторожової собаки, яка може підтримувати ваш процес живим, якщо це те, що ви хочете. Після запуску python ви можете використовувати модуль планування для планування завдань.


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

Рішення Брайана працює досить добре. Однак, як зазначали інші, в коді запуску є непомітна помилка. Крім того, я виявив це занадто складним для потреб.

Ось моя простіша та функціональна альтернатива для запуску коду, якщо комусь це потрібно:

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. Це:

# 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 - він потребує переробки, хоча це може бути не тим, що ви шукаєте. (зауважте, що я автор) це було створено для забезпечення відмовостійкості, якщо годинник працює на більш ніж одному вузлі.


0

Я не знаю, чи існує щось подібне. Буде легко написати свої модулі з модулями часу, дати та / або календаря, див. Http://docs.python.org/library/time.html

Єдина турбота для вирішення пітона є те , що ваші потреби роботу , щоб бути завжди працює , і , можливо , буде автоматично «воскресли» після перезавантаження, що - то , для якого ви робите необхідність покладатися на системи залежних рішень.


3
Згорнути свій власний варіант - хоча найкращий код - це код, який вам не потрібно писати. Воскресіння, я думаю, це те, що, можливо, мені доведеться розглянути.
Джеймс

0

Ви можете перевірити [1] крони PiCloud [2], але зауважте, що ваші завдання не працюватимуть на вашій власній машині. Це також послуга, яку вам доведеться заплатити, якщо ви використовуєте більше 20 годин обчислювального часу на місяць.

[1] http://www.picloud.com

[2] http://docs.picloud.com/cron.html


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

кронтаб -е

Крок 3: Додати час розкладу

Використовуйте форматування Crontab

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

Цей крон запустить «У хвилину 2».


0

Мені подобається, як пакет пікрон вирішує цю проблему.

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 ('працює резервна копія") "буде запускатися цілу хвилину з інтервалом 5s. Тож у цьому випадку затримка повинна становити 60 секунд.
n158

Ти маєш рацію! Дякуємо, що вказали на це.
Duffau
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.