web-dev-qa-db-ja.com

PythonでCronのようなスケジューラを取得する方法

私はatcronのような機能を提供するPythonのライブラリを探しています。

箱にインストールされているツールに頼るのではなく、純粋な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.

クーロン時間式の構文はそれほど重要ではありませんが、この種の柔軟性を備えたものが欲しいのです。

箱から出してすぐに使えるようなことがない場合は、このようなものを作成するためのビルディングブロックに対する提案があればありがたく思います。

編集プロセスの起動には興味がありません。Pythonで書かれた「ジョブ」 - python関数だけです。必要に応じて、これは別のスレッドになると思いますが、別のプロセスにはありません。

この目的のために、私はcron時間式の表現力を探していますが、それはPythonです。

Cronは何年も前から存在していますが、できるだけ移植性があるようにしています。私はその存在に頼ることはできません。

286
jamesh

あなたが何か軽量チェックアウトを探しているなら スケジュール

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)

公開:私はその図書館の作者です。

450
dbader

Crontabを指定するには、通常のPythonの引数渡し構文を使用することができます。たとえば、次のように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構文を好めば、構文解析はそれほど難しくないはずです。

61
Brian

たぶん、これは質問がされた後に初めて現れました。私は私がちょうど完全性のためにそれを言及すると思った: https://apscheduler.readthedocs.org/en/latest/

45
ssc

チェックアウト Celery 、彼らはcronのような定期的なタスクを持っています。

28
Vishal

"... crontabファイルを読み書きし、システムのcronに自動的に直接アクセスするためのCrontabモジュール..."

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

また、PythonパッケージのAPSchedulerもあります。すでに書かれていてデバッグされています。

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

19
bootload

私の検索で見たことの1つは、Pythonの sched モジュールです。これは、あなたが探している種類のものかもしれません。

16
Sean

上記とほぼ同じですが、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()
10
Hackeron

TurboGears に基づいてスケジュールされたタスク機能が付属しています Kronos

私はKronosを直接使ったことは一度もありませんが、TGでのスケジューリングにはまともな機能があり、しっかりしています。

9
James Brady

列挙された解決策のどれも複雑な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)
9
rouble

スクリプトを修正しました。

  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. 1分の1秒以内にタスクを開始してみてください。

Githubのコード

7
ning

Luigiをチェックしてください( https://github.com/spotify/luigi )。それはpythonで書かれており、タスクを監視するための素晴らしいWeb UIを持っています。依存グラフもあります。あなたが必要とするもののためにやり過ぎるかもしれません、しかしそれはおそらくトリックをするでしょう。

6
amwinter

私は Brianが提案したCronTabクラスの実行方法 をマイナーに修正しました。

時間が1秒ずれて、1分ごとに1秒のハードループが発生しました。

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

他のプロセスでソリューションを実行するにはpythonを起動する必要があるため、これを行うための "純粋なpython"方法はありません。どのプラットフォームにも、プロセスを起動してその進捗を監視するための1つまたは20の異なる方法があります。 UNIXプラットフォームでは、cronが古い標準です。 Mac OS Xには、cronに似た起動方法とウォッチドッグ機能を組み合わせたプロセスもあります。 Pythonが起動したら、 schedモジュール を使ってタスクをスケジュールすることができます。

4
Nick

私はたくさんの答えがあることを知っています、しかし別の解決策はデコレータと一緒に行くことかもしれません。これは毎日特定の時間に関数を繰り返す例です。この方法を使うことについてのクールな考えは、あなたがスケジュールしたい機能に構文糖を追加するだけでよいということです:

@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
Damia Fuentes

もう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
fdb

あなたが分散スケジューラを探しているなら、あなたはチェックアウトすることができます https://github.com/sherinkurian/mani - それはあなたが探しているものではないかもしれませんがそれはredisを必要としません。 (私は作者です)これは複数のノードでクロックを走らせることでフォールトトレランスを保証するために作られました。

1
shrnkrn

ブライアンの解決策 はとてもうまくいっています。しかし、他の人が指摘しているように、実行コードには微妙なバグがあります。また、私はそれが必要性のために過度に複雑であるとわかりました。

誰かがそれを必要とする場合に備えて、これは実行コードのための私のより簡単で機能的な代替手段です:

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
raph.amiard

念のために、あなたが窓を使用しているなら、パイクロンが存在します。チェックアウト http://sourceforge.net/projects/pycron/ 。 Linuxの場合は、cronまたはschedで行きます。

1
JV.

私はBrianの解決法を取り、いくつかの変更を加え、標準のcrontabファイルパーサーの始まりを追加し、それを https://bitbucket.org/dbenamy/devcron に置きました。

0
Dan Benamy

そのようなものが既に存在するかどうか私は知りません。時間、日付、カレンダーモジュールを使って自分で書くのは簡単でしょう。 http://docs.python.org/library/time.html を参照してください。

Pythonソリューションの唯一の懸念は、あなたの仕事は常に実行されている必要があるということです、そしておそらくあなたはdoあなたはシステム依存の解決策に頼る必要があります。

0
Davide

あなたはPiCloudの[1] Crons [2]をチェックすることができるが、あなたの仕事はあなた自身のマシンで実行されないことに注意してください。月に20時間以上の計算時間を費やす場合にも支払う必要があるサービスです。

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

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

0
BrainCore

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(5)
0
Duffau

実行するスクリプトがWebベースの場合は、 crono などのサードパーティサービスを使用してプログラムでジョブを設定することを検討できます。

0
gduverger

サーバー上の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

このクーロンは「At minute 2」で走ります。

0
shaurya uppal