PythonでCronのようなスケジューラを取得するにはどうすればよいですか?[閉まっている]


348

私は、Pythonでライブラリを提供atcron、機能を提供します。

ボックスにインストールされているツールに依存するのではなく、純粋な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時間式の構文はそれほど重要ではありませんが、この種の柔軟性を備えた何かが欲しいです。

私のためにこれを実行する何かがない場合、このようなものを作成するためのビルディングブロックの提案はありがたく受け取られます。

編集 プロセスの起動には興味がありません。「ジョブ」もPythonで記述されています-python関数。必然的に、これは別のスレッドになると思いますが、別のプロセスではありません。

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

Cron 何年も前から存在しいますが、私はできる限りポータブルにしようとしています。私はその存在に頼ることはできません。


1
これを行う方法についても知りたいです。プラットフォーム固有のコンポーネントに依存するよりも、クロスプラットフォームソリューションを使用する方が便利です。
ショーン

6
これは主題外ではありません。これは非常に重要で有用な質問です
Connor

回答:


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。それは私にはうまくいきました。cronのような構文とサポートされているデコレータがあればさらに便利です(crythonを参照してください。ただし、機能しないため、このライブラリは使用しないでください。スケジュールが適切に記述されていないようです)。
Tim Ludwinski、2016年

23
ジョブにパラメーターを渡す方法はありますか?私はこのようなことをしたいと思います:schedule.every()。hour.do(job(myParam))
Zen

5
schedule.every()。hour.do(job)これは毎時実行されますか?01:00、02:00、03:00などですか?開始時間が1時間でなくても、
swateek 2016年

1
このコードがscheduler.pyにあるとします。このコードは自動的に実行されますか?
Kishan

25
@ darrel-holtおよび@ zen-skunkworx:このdo()関数は、渡された追加の引数をジョブ関数に転送します。schedule.readthedocs.io/en/stable/api.html# schedule.Job.doたとえば、次のようにできます:schedule.every().hour.do(job, param1, param2)ラムダを使用する必要はありません。これがお役に立て
ば幸いです

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構文を使用する場合は、構文解析はそれほど難しくありません。


経験の浅い人のためにいくつかのインポート文を追加したい場合があります。from datetime import * from time import sleepを使用してすべてのクラスを1つのファイルに入れ、time.sleepをsleepに変更しました。素敵でシンプルなエレガントなソリューション。ありがとう。
mavnn 2009年

1
疑問に思って、なぜこれがクロノスよりも好まれているのですか?schedはバギーですか(kronosはschedを使用しているため)?それとも時代遅れですか?
cregox 2010

ブライアンに感謝します。私はあなたのソリューションを生産で使用し、それは非常にうまく機能しています。ただし、他の人が指摘したように、実行コードには微妙なバグがあります。また、私はそれがニーズに対して過度に複雑であることを発見しました。
raph.amiard 2010年

1
これはクールですが、それでも...スラッシュ表記、実行のために毎時間、分、などをサポートしていません
クリスKoston

1
独自のクラスを作成する優れたアイデア。たとえば、サーバーにsudoアクセスがないためにできない場合pip install anything:)
Cometsong


27

私の検索で私が見たものの1つは、schedあなたが探しているようなものかもしれないpythonのモジュールです。


11
schedに絶対的なスケジューリングを行うenterabs()が追加されました。
Jerther 2017

5
schedは現在python2と3 stdlibの一部であり、絶対的なスケジューリングを行うため、これが好ましい答えになると思います。
マイケル

20

「... crontabファイルの読み取りと書き込み、および直接APIを使用したシステムcronへのアクセスのためのCrontabモジュール。...」

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

また、PythonパッケージのAPSchedulerもあります。すでに記述およびデバッグされています。

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


1
OPは、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()

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ですか?Atm私は"定期的に行うこと" に1分を超える時間を"* * * * *"追加するようなスケジュールを使用していtime.sleepますifが、そのifステートメントには常に内容が表示されます。1分以上かかると、whileループが不足しているループの実行をスキップするのがわかります。
TPPZ 2018

@TPPZプロセスが一時停止された可能性があります。クロックは手動またはntpなどによって変更された可能性があります。クロニターはAirflowで使用されており、Crontabモジュールなどよりもフル機能を備えているようです。
dlamblin 2018年

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

Githubのコード


6

Brianによって提案されCronTabクラスのrunメソッドのマイナーな修正があります。

タイミングが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()

4

ソリューションを実行するために他のいくつかのプロセスがpythonを起動する必要があるため、これを行う「純粋なpython」方法はありません。すべてのプラットフォームには、プロセスを起動してその進行状況を監視するための1つまたは20の異なる方法があります。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)

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

1
複数の質問に対するコピーと貼り付けの定型文/逐語的回答を投稿するときは注意してください。これらはコミュニティによって「スパム行為」としてフラグが付けられる傾向があります。これを行っている場合、通常は質問が重複していることを意味するため、代わりにフラグを立てます:stackoverflow.com/a/12360556/419
Kev

1

分散スケジューラーを探している場合は、https://github.com/sherinkurian/maniをチェックしてください。redisが必要なので、探しているものとは異なる場合があります。(私が作成者であることに注意してください)これは、複数のノードでクロックを実行することによってフォールトトレランスを確保するために構築されました。


0

そのようなものがすでに存在しているかどうかはわかりません。時刻、日付時刻、および/またはカレンダーモジュールを使用して独自に記述するのは簡単です。。http://docs.python.org/library/time.htmlを

Pythonソリューションの唯一の懸念は、ジョブが常に実行されている必要があり、再起動後にジョブが自動的に「復活」する必要があることです。このため、システム依存のソリューションに依存する必要があります。


3
あなた自身のロールはオプションです-最良のコードはあなたが書く必要のないコードです。復活、私は考慮する必要があるかもしれないものだと思います。
jamesh 2008

0

PiCloudの[1]クローン[2]をチェックアウトできますが、自分のマシンでジョブが実行されないことに注意してください。また、1か月に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エディターを開く

crontab -e

ステップ3:スケジュール時間を追加する

Crontab書式を使用する

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

このcronは「2分後に」実行されます。


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)

1
コード「print( 'running backup')」は5分間隔で1分間起動されるため、これは良いアイデアではありません。したがって、この場合の遅延は60秒です。
n158

あなたが正しい!ご指摘いただきありがとうございます。
Duffau
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.