Làm cách nào để có được Cron giống như trình lập lịch biểu trong Python? [đóng cửa]


348

Tôi đang tìm kiếm một thư viện trong Python sẽ cung cấp atcronthích chức năng.

Tôi khá muốn có một giải pháp Python thuần túy, thay vì dựa vào các công cụ được cài đặt trên hộp; Bằng cách này, tôi chạy trên các máy không có cron.

Đối với những người không quen thuộc với cron: bạn có thể lên lịch các tác vụ dựa trên một biểu thức như:

 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.

Cú pháp biểu thức thời gian cron ít quan trọng hơn, nhưng tôi muốn có một cái gì đó với loại linh hoạt này.

Nếu không có thứ gì đó giúp tôi làm điều đó, bất kỳ đề xuất nào cho các khối xây dựng để làm một cái gì đó như thế này sẽ được nhận một cách biết ơn.

Chỉnh sửa Tôi không quan tâm đến việc khởi chạy các quy trình, chỉ "công việc" cũng được viết bằng hàm Python - python. Do sự cần thiết tôi nghĩ rằng đây sẽ là một chủ đề khác nhau, nhưng không phải trong một quy trình khác.

Cuối cùng, tôi đang tìm biểu thức của biểu thức thời gian cron, nhưng bằng Python.

Cron đã xuất hiện trong nhiều năm, nhưng tôi đang cố gắng trở nên di động nhất có thể. Tôi không thể dựa vào sự hiện diện của nó.


1
Tôi cũng muốn biết làm thế nào để làm điều này. Sẽ có ích hơn khi có một giải pháp đa nền tảng hơn là phụ thuộc vào các thành phần cụ thể của nền tảng.
Sean

6
Đây không phải là lạc đề, đây là một câu hỏi rất quan trọng và hữu ích
Connor

Câu trả lời:


571

Nếu bạn đang tìm kiếm một lịch trình thanh toán nhẹ :

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)

Tiết lộ : Tôi là tác giả của thư viện đó.


7
Bạn nên đề cập rằng bạn là người duy trì schedule. Nó làm việc tốt cho tôi. Sẽ còn đẹp hơn nữa nếu nó có cron như cú pháp và các trang trí được hỗ trợ (xem crython nhưng không sử dụng thư viện này vì nó không hoạt động; lập lịch dường như không được viết tốt).
Tim Ludwinski

23
Có cách nào để truyền tham số cho công việc không? Tôi muốn làm một cái gì đó như thế này: calendar.every (). Hour.do (job (myParam))
Zen Skunkworx

5
calendar.every (). hours.do (công việc) này có chạy mỗi giờ không? Như 01:00, 02:00, 03:00, v.v? ngay cả khi thời gian bắt đầu không phải là một giờ đầy đủ?
swateek

1
giả sử mã này nằm trong lịch trình. mã này sẽ tự động chạy chứ?
Kishan

25
@ darrel-holt và @ zen-skunkworx: do()Hàm chuyển tiếp các đối số bổ sung mà bạn truyền cho nó tới hàm công việc: calendar.readthedocs.io/en/ sóng / apes.html # schedule.Job.do Ví dụ: bạn có thể làm điều này : schedule.every().hour.do(job, param1, param2)Không cần sử dụng lambda. Hy vọng điều này sẽ giúp :)
dbader

65

Bạn chỉ có thể sử dụng cú pháp chuyển đối số Python bình thường để chỉ định crontab của bạn. Ví dụ: giả sử chúng ta định nghĩa một lớp Sự kiện như dưới đây:

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)

(Lưu ý: Không được kiểm tra kỹ lưỡng)

Sau đó, CronTab của bạn có thể được chỉ định theo cú pháp python bình thường như:

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

Bằng cách này, bạn có được toàn bộ sức mạnh của cơ chế đối số của Python (trộn các đối số từ khóa và vị trí và có thể sử dụng tên tượng trưng cho tên của tuần và tháng)

Lớp CronTab sẽ được định nghĩa là chỉ đơn giản là ngủ theo gia số phút và gọi kiểm tra () trên mỗi sự kiện. (Có lẽ có một số sự tinh tế với thời gian / thời gian tiết kiệm ánh sáng ban ngày để cảnh giác mặc dù). Đây là một cách thực hiện nhanh chóng:

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)

Một vài điều cần lưu ý: các ngày trong tuần / tháng của Python không được lập chỉ mục (không giống như cron) và phạm vi đó loại trừ phần tử cuối cùng, do đó cú pháp như "1-5" trở thành phạm vi (0,5) - tức là [0,1,2, 3,4]. Tuy nhiên, nếu bạn thích cú pháp cron, việc phân tích cú pháp không nên quá khó.


Bạn có thể muốn thêm một số báo cáo nhập khẩu cho người thiếu kinh nghiệm. Cuối cùng tôi đã đặt tất cả các lớp vào một tệp duy nhất từ ​​nhập datetime * từ thời gian nhập ngủ và thay đổi thời gian ngủ sang ngủ. Giải pháp thanh lịch đẹp, đơn giản. Cảm ơn.
mavnn

1
Chỉ cần tự hỏi, tại sao điều này được ưa thích hơn Kronos? Là lịch trình mà lỗi (vì kronos sử dụng lịch trình)? Hay đây chỉ là lỗi thời?
cregox

Cảm ơn brian, tôi sử dụng giải pháp của bạn trong sản xuất và nó hoạt động khá tốt. Tuy nhiên, như những người khác đã chỉ ra, có một lỗi tinh vi trong mã chạy của bạn. Ngoài ra tôi thấy nó quá phức tạp cho các nhu cầu.
raph.amiard

1
Điều này thật tuyệt, nhưng vẫn không hỗ trợ ký hiệu gạch chéo, để thực hiện mỗi giờ, phút, v.v ...
Chris Koston

1
Ý tưởng tuyệt vời để viết các lớp của riêng bạn, ví dụ như khi tôi không có quyền truy cập sudo trên máy chủ và do đó không thể pip install anything :)
Cometsong


27

Một điều mà trong các tìm kiếm của tôi, tôi đã thấy là schedmô-đun của python có thể là thứ bạn đang tìm kiếm.


11
lịch trình bây giờ có enterabs () mà lập lịch tuyệt đối.
Jerther

5
Tôi hy vọng đây sẽ là câu trả lời ưa thích vì lịch trình là một phần của python2 và 3 stdlib bây giờ và thực hiện lập kế hoạch tuyệt đối.
Michael


11

Nhiều hơn hoặc ít hơn như trên nhưng đồng thời sử dụng 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()

Chỉ cần một lưu ý rằng datetime.timetuple () sẽ bắt đầu bằng năm, tháng, ngày ... vv ...
Trey Stout

9

Không có giải pháp nào được liệt kê thậm chí cố gắng phân tích chuỗi lịch trình cron phức tạp. Vì vậy, đây là phiên bản của tôi, sử dụng croniter . Ý chính

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

Thói quen của người trợ giúp:

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)

Làm thế nào một người nào đó có thể đi vào "bỏ lỡ một cuộc hành quyết" elif? Atm Tôi đang sử dụng một lịch trình như "* * * * *"sau đó thêm một số time.sleeplớn hơn 1 phút trong phần "Thực hiện định kỳ của bạn" if, nhưng tôi luôn thấy nội dung trong câu lệnh if đó. Khi mất hơn 1 phút tôi chỉ thấy vòng lặp while bỏ qua việc thực hiện vòng lặp bị thiếu đó.
TPPZ

@TPPZ Quá trình có thể bị đình chỉ, đồng hồ có thể được thay đổi bằng tay hoặc bởi ntp, v.v. Croniter được sử dụng trong Airflow và nó có vẻ đầy đủ hơn so với mô-đun Crontab và các loại khác.
dlamblin

7

Tôi đã sửa đổi kịch bản.

  1. Dễ sử dụng:

    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. Cố gắng bắt đầu nhiệm vụ trong giây đầu tiên của một phút.

Mã trên Github


6

Tôi có một sửa chữa nhỏ cho phương thức chạy lớp CronTab được đề xuất bởi Brian .

Thời gian đã hết một giây dẫn đến một vòng lặp cứng, một giây vào cuối mỗi phút.

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

Không có cách nào "trăn thuần" để làm điều này bởi vì một số quy trình khác sẽ phải khởi chạy python để chạy giải pháp của bạn. Mỗi nền tảng sẽ có một hoặc hai mươi cách khác nhau để khởi chạy các quy trình và theo dõi tiến trình của chúng. Trên nền tảng unix, cron là tiêu chuẩn cũ. Trên Mac OS X cũng có launchd, kết hợp khởi chạy giống như cron với chức năng watchdog có thể giữ cho quá trình của bạn tồn tại nếu đó là những gì bạn muốn. Khi python đang chạy, sau đó bạn có thể sử dụng mô-đun lịch trình để lên lịch các tác vụ.


4

Tôi biết có rất nhiều câu trả lời, nhưng một giải pháp khác có thể là đi với các nhà trang trí . Đây là một ví dụ để lặp lại một chức năng hàng ngày tại một thời điểm cụ thể. Suy nghĩ tuyệt vời về việc sử dụng cách này là bạn chỉ cần thêm Đường cú pháp vào chức năng bạn muốn lên lịch:

@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

Và trang trí sẽ trông như:

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

Giải pháp của Brian đang hoạt động khá tốt. Tuy nhiên, như những người khác đã chỉ ra, có một lỗi tinh vi trong mã chạy. Ngoài ra tôi thấy nó quá phức tạp cho các nhu cầu.

Đây là sự thay thế đơn giản và chức năng của tôi cho mã chạy trong trường hợp có ai cần nó:

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

Một giải pháp tầm thường khác sẽ là:

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)

Và lớp aqcron.At là:

# 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
Hãy cẩn thận khi đăng bản sao và dán câu trả lời soạn sẵn / nguyên văn cho nhiều câu hỏi, những câu hỏi này có xu hướng bị cộng đồng đánh dấu là "spam". Nếu bạn đang làm điều này thì điều đó thường có nghĩa là các câu hỏi là trùng lặp, vì vậy hãy gắn cờ chúng như vậy: stackoverflow.com/a/12360556/419
Kev

1

Nếu bạn đang tìm kiếm một bộ lập lịch phân tán, bạn có thể xem https://github.com/sherinkurian/mani - nó không cần redis mặc dù vậy có thể không phải là thứ bạn đang tìm kiếm. (lưu ý rằng tôi là tác giả) điều này được xây dựng để đảm bảo khả năng chịu lỗi bằng cách chạy đồng hồ trên nhiều nút.


0

Tôi không biết nếu một cái gì đó như thế đã tồn tại. Thật dễ dàng để viết riêng của bạn với các mô-đun thời gian, thời gian và / hoặc lịch, xem http://docs.python.org/l Library / time.html

Mối quan tâm duy nhất cho một giải pháp trăn là nhu cầu công việc của bạn là luôn luôn chạy và có thể được tự động "hồi sinh" sau khi khởi động lại, một cái gì đó mà bạn làm cần phải dựa vào các giải pháp phụ thuộc hệ thống.


3
Cuộn của riêng bạn là một tùy chọn - mặc dù mã tốt nhất là mã bạn không phải viết. Phục sinh, tôi cho rằng là một cái gì đó tôi có thể cần phải xem xét.
jamesh

0

Bạn có thể kiểm tra [1] Crons [2] của PiCloud, nhưng lưu ý rằng công việc của bạn sẽ không chạy trên máy của chính bạn. Đây cũng là một dịch vụ mà bạn sẽ phải trả tiền nếu bạn sử dụng hơn 20 giờ tính toán mỗi tháng.

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

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


0

Phương pháp Crontab trên máy chủ.

Tên tệp Python hello.py

Bước 1: Tạo tập tin sh hãy đặt tên s.sh

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

Bước 2: Mở Trình chỉnh sửa Crontab

crontab -e

Bước 3: Thêm thời gian biểu

Sử dụng định dạng Crontab

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

Cron này sẽ chạy vào phút thứ 2.


0

Tôi thích cách gói pycron giải quyết vấn đề này.

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
Đây không phải là một ý tưởng hay, bởi vì mã của bạn "in ('chạy sao lưu')" sẽ khởi chạy toàn bộ phút với khoảng thời gian 5s. Vì vậy, trong trường hợp này độ trễ nên là 60 giây.
n158

Bạn đúng! Cảm ơn đã chỉ ra rằng.
Duffau
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.