Как мне получить Cron-подобный планировщик в Python? [закрыто]

348

Я ищу библиотеку в Python, которая будет предоставлять atи cronнравится функциональность.

Мне бы очень хотелось иметь чисто 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 менее важен, но я хотел бы иметь что-то с такой гибкостью.

Если бы не было чего-то, что могло бы сделать это для меня из коробки, с благодарностью были бы приняты любые предложения для строительных блоков, чтобы сделать что-то подобное.

Edit Я не заинтересован в запуске процессов, просто "рабочие места" также написаны на Python - функции Python. По необходимости я думаю, что это будет другой поток, но не в другом процессе.

Для этого я ищу выразительность выражения времени cron, но в Python.

Крон существует уже много лет, но я стараюсь быть максимально портативным. Я не могу положиться на его присутствие.

jamesh
источник
1
Я также хотел бы знать, как это сделать. Было бы более полезно иметь кроссплатформенное решение, чем зависеть от конкретных компонентов платформы.
Шон
7
Это не не по теме, это очень важный и полезный вопрос
Коннор

Ответы:

572

Если вы ищете что-то легкое расписание оформления заказа :

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)

Раскрытие информации : я автор этой библиотеки.

dbader
источник
8
Вы должны упомянуть, что вы поддерживаете 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
1
Предположим, этот код находится в scheduler.py. этот код будет работать автоматически?
Кишан
26
@ darrel-holt и @ zen-skunkworx: do()функция перенаправляет дополнительные аргументы, которые вы передаете ей, в функцию job: schedule.readthedocs.io/en/stable/api.html#schedule.Job.do Например, вы можете сделать это : schedule.every().hour.do(job, param1, param2)Нет необходимости использовать лямбду. Надеюсь, это поможет :)
dbader
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)

(Примечание: не полностью проверено)

Тогда ваш 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», становится range (0,5) - то есть [0,1,2, 3,4]. Если вы предпочитаете синтаксис cron, его синтаксический анализ не должен быть слишком сложным.

Брайан
источник
Возможно, вы захотите добавить некоторые операторы импорта для неопытных. Я закончил тем, что поместил все классы в один файл с датой и временем импорта * со времени импорта сна и изменил time.sleep на сон. Хорошее, простое элегантное решение. Спасибо.
mavnn
1
Просто интересно, почему это предпочитают Кроносу? Это глючит sched (так как kronos использует sched)? Или это просто устарело?
Cregox
Спасибо, Брайан, я использую ваше решение в производстве, и оно работает довольно хорошо. Однако, как отмечали другие, в вашем коде выполнения есть небольшая ошибка. Также я нашел это слишком сложным для нужд.
raph.amiard
1
Это круто, но все еще не поддерживает слэш-нотацию, для выполнения каждый час, мин и т. Д.
Крис Костон
1
Отличная идея написать свои собственные классы, например, когда у меня нет доступа к sudo на сервере, и поэтому я не могу pip install anything:)
Cometsong
46

возможно, это возникло только после того, как был задан вопрос; Я думал, что просто упомяну это для полноты: https://apscheduler.readthedocs.org/en/latest/

ССК
источник
Apscheduler чрезвычайно полезен и прост в использовании.
Хатшепсут
27

Одна вещь, которую я видел в моих поисках, это schedмодуль Python, который может быть тем, что вы ищете.

Шон
источник
11
sched теперь имеет enterabs (), которая выполняет абсолютное планирование.
Джертер
5
Я ожидаю, что это будет предпочтительным ответом, так как sched сейчас является частью python2 и 3 stdlib и выполняет абсолютное планирование.
Майкл
20

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

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

а также APScheduler, пакет для Python. Уже написано и отлажено.

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

Bootload
источник
1
ОП специально попросил что-то, что могло бы работать на системах без cron
Hamy
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()
Hackeron
источник
Просто обратите внимание, что 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? Я использую расписание, например "* * * * *"добавляю time.sleepболее 1 минуты в «Делай свои периодические действия» if, но я всегда вижу вещи в этом операторе if. Когда это занимает более 1 минуты, я просто вижу, как цикл while пропускает выполнение отсутствующего цикла.
ТППЗ
@TPPZ Процесс мог быть приостановлен, часы могли быть изменены вручную или с помощью ntp и т. Д. Croniter используется в Airflow и кажется более полнофункциональным, чем модуль Crontab и другие.
Даламблин
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()
BENC
источник
4

Для этого нет «чистого Python», потому что какой-то другой процесс должен запустить Python, чтобы запустить ваше решение. У каждой платформы будет один или двадцать различных способов запуска процессов и отслеживания их прогресса. На платформах Unix cron - это старый стандарт. В Mac OS X также есть launchd, который сочетает в себе 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

Решение Брайана работает довольно хорошо. Однако, как отмечали другие, в коде выполнения есть небольшая ошибка. Также я нашел это слишком сложным для нужд.

Вот моя более простая и функциональная альтернатива для запуска кода на случай, если это кому-нибудь понадобится:

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)
raph.amiard
источник
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
FDB
источник
1
Будьте осторожны при публикации копируй и вставляй шаблонные / дословные ответы на несколько вопросов, они, как правило, помечаются сообществом как «спам». Если вы делаете это, то это обычно означает, что вопросы являются дубликатами, поэтому пометьте их как таковые: stackoverflow.com/a/12360556/419
Kev
1

Если вы ищете распределенный планировщик, вы можете проверить https://github.com/sherinkurian/mani - он действительно нуждается в повторном редактировании, хотя, возможно, это не то, что вы ищете. (обратите внимание, что я являюсь автором) это было построено для обеспечения отказоустойчивости за счет запуска часов более чем на одном узле.

shrnkrn
источник
0

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

Единственная забота для решения питона является то , что ваши потребности работу , чтобы быть всегда работает , и , возможно , будет автоматически «воскресли» после перезагрузки, что - то , для которого вы делаете необходимость полагаться на системы зависимых решений.

Davide
источник
3
Бросить свой собственный вариант - хотя лучший код - это код, который вам не нужно писать. Воскресение, я полагаю, это то, что мне нужно рассмотреть.
Jamesh
0

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

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

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

BrainCore
источник
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 будет работать «На второй минуте».

Shaurya Uppal
источник
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)
Duffau
источник
1
Это не очень хорошая идея, потому что ваш код "print ('running backup')" будет запускаться целую минуту с интервалом 5 с. Так что в этом случае задержка должна составлять 60 секунд.
n158
Ты прав! Спасибо что подметил это.
Duffau