如何安排功能在Flask上每小时运行一次?


98

我有一个Flask虚拟主机,无法访问 cron命令。

我如何每小时执行一些Python函数?

Answers:


104

您可以BackgroundScheduler()APScheduler软件包(v3.5.3)中使用:

import time
import atexit

from apscheduler.schedulers.background import BackgroundScheduler


def print_date_time():
    print(time.strftime("%A, %d. %B %Y %I:%M:%S %p"))


scheduler = BackgroundScheduler()
scheduler.add_job(func=print_date_time, trigger="interval", seconds=3)
scheduler.start()

# Shut down the scheduler when exiting the app
atexit.register(lambda: scheduler.shutdown())

请注意,当Flask处于调试模式时,将启动其中两个调度程序。有关更多信息,请查看问题。


1
@ user5547025时间表如何工作,假设我已将内容放入schedule.py中,它将如何自动运行?
Kishan Mehta'3

2
我认为user5547025建议的计划是针对可以阻止主线程的同步任务的。您将需要启动一个工作线程以使其不会阻塞。
西蒙

1
如果flask有一个,App.runonce或者App.runForNseconds您可以schedule在烧瓶流道之间切换,但是事实并非如此,所以目前唯一的方法是使用它
lurscher 18-4-11

谢谢你!如果在name __ ==“ __ main ” 下,我将在哪里插入此函数?我们也可以用我们的函数替换print_date_time函数,对吗?
Ambleu

如何每天运行一次调度程序?
arun kumar

56

您可以APScheduler在Flask应用程序中使用它并通过其界面运行作业:

import atexit

# v2.x version - see https://stackoverflow.com/a/38501429/135978
# for the 3.x version
from apscheduler.scheduler import Scheduler
from flask import Flask

app = Flask(__name__)

cron = Scheduler(daemon=True)
# Explicitly kick off the background thread
cron.start()

@cron.interval_schedule(hours=1)
def job_function():
    # Do your work here


# Shutdown your cron thread if the web process is stopped
atexit.register(lambda: cron.shutdown(wait=False))

if __name__ == '__main__':
    app.run()

1
我可以问一个新手问题吗?为什么会出现lambdaatexit.register
皮格马利翁19'Dec

2
因为atexit.register需要一个函数调用。如果我们只是通过了,那么cron.shutdown(wait=False)我们将传递调用的结果cron.shutdown(可能是None)。因此,我们传递了一个零参数函数,而不是给它命名和使用一条语句 def shutdown(): cron.shutdown(wait=False)atexit.register(shutdown)而是将其内联注册lambda:(这是一个零参数函数表达式。)
Sean Vieira

谢谢。所以问题是,如果我理解正确,我们想将参数传递给该函数。
皮格马利翁

49

我对应用程序调度程序的概念有些陌生,但是我在这里找到的APScheduler v3.3.1有点不同。我相信对于最新版本,软件包的结构,类名等已经发生了变化,因此,我在这里提出了我最近制作的,与基本Flask应用程序集成的新解决方案:

#!/usr/bin/python3
""" Demonstrating Flask, using APScheduler. """

from apscheduler.schedulers.background import BackgroundScheduler
from flask import Flask

def sensor():
    """ Function for test purposes. """
    print("Scheduler is alive!")

sched = BackgroundScheduler(daemon=True)
sched.add_job(sensor,'interval',minutes=60)
sched.start()

app = Flask(__name__)

@app.route("/home")
def home():
    """ Function for test purposes. """
    return "Welcome Home :) !"

if __name__ == "__main__":
    app.run()

如果有人对此示例的更新感兴趣,我还将把这个要点留在这里

以下是一些参考资料,供以后阅读:


2
效果很好,希望随着更多人看到此主题,它会获得最高投票。
Mwspencer

1
您是否尝试过在网络上的应用程序(例如PythonAnywhere等)上使用它?
Mwspencer

1
谢谢,@ Mwspencer。是的,我已经使用过并且它可以正常工作:),尽管我建议您探索由提供的更多选项apscheduler.schedulers.background,因为您可能会遇到针对您的应用程序的其他有用方案。问候。
ivanleoncz

2
当应用存在时,别忘了关闭调度程序
Hanynowsky '19

1
你好!在有多名枪械工人的情况下,您能提供一些建议吗?我的意思是,调度程序将每个工人执行一次吗?
ElPapi42

13

您可以尝试使用APScheduler的BackgroundScheduler将间隔作业集成到Flask应用程序中。以下是使用蓝图和应用程序工厂(init .py)的示例:

from datetime import datetime

# import BackgroundScheduler
from apscheduler.schedulers.background import BackgroundScheduler
from flask import Flask

from webapp.models.main import db 
from webapp.controllers.main import main_blueprint    

# define the job
def hello_job():
    print('Hello Job! The time is: %s' % datetime.now())

def create_app(object_name):
    app = Flask(__name__)
    app.config.from_object(object_name)
    db.init_app(app)
    app.register_blueprint(main_blueprint)
    # init BackgroundScheduler job
    scheduler = BackgroundScheduler()
    # in your case you could change seconds to hours
    scheduler.add_job(hello_job, trigger='interval', seconds=3)
    scheduler.start()

    try:
        # To keep the main thread alive
        return app
    except:
        # shutdown if app occurs except 
        scheduler.shutdown()

希望能帮助到你 :)

参考:

  1. https://github.com/agronholm/apscheduler/blob/master/examples/schedulers/background.py

1
我确信返回声明永远不会引发异常
Tamas Hegedus

11

对于一个简单的解决方案,您可以添加一条路线,例如

@app.route("/cron/do_the_thing", methods=['POST'])
def do_the_thing():
    logging.info("Did the thing")
    return "OK", 200

然后添加一个unix cron作业,该作业定期发布到此端点。例如,每分钟运行一次,在终端类型中crontab -e添加以下行:

* * * * * /opt/local/bin/curl -X POST https://YOUR_APP/cron/do_the_thing

(请注意,curl的路径必须是完整的,因为在作业运行时它将没有您的PATH。您可以通过以下方式找到系统上curl的完整路径: which curl

我之所以喜欢它,是因为手动测试很容易,它没有额外的依赖关系,并且因为没有什么特别的事情而易于理解。

安全

如果您想用密码保护您的cron作业,可以打开pip install Flask-BasicAuth,然后将凭据添加到您的应用配置中:

app = Flask(__name__)
app.config['BASIC_AUTH_REALM'] = 'realm'
app.config['BASIC_AUTH_USERNAME'] = 'falken'
app.config['BASIC_AUTH_PASSWORD'] = 'joshua'

密码保护作业端点:

from flask_basicauth import BasicAuth
basic_auth = BasicAuth(app)

@app.route("/cron/do_the_thing", methods=['POST'])
@basic_auth.required
def do_the_thing():
    logging.info("Did the thing a bit more securely")
    return "OK", 200

然后从您的cron作业中调用它:

* * * * * /opt/local/bin/curl -X POST https://falken:joshua@YOUR_APP/cron/do_the_thing

1
你真是个天才!非常方便的提示。
Sharl Sherif


4

一个完整的示例,使用调度和多重处理,对on_off控制和参数run_job()进行了简化,返回码得以简化,时间间隔设置为10秒,更改every(2).hour.do()为2小时。日程安排非常令人印象深刻,它不会漂移,而且我从未在安排日程时看到超过100毫秒的时间。使用多处理而不是线程,因为它具有终止方法。

#!/usr/bin/env python3

import schedule
import time
import datetime
import uuid

from flask import Flask, request
from multiprocessing import Process

app = Flask(__name__)
t = None
job_timer = None

def run_job(id):
    """ sample job with parameter """
    global job_timer
    print("timer job id={}".format(id))
    print("timer: {:.4f}sec".format(time.time() - job_timer))
    job_timer = time.time()

def run_schedule():
    """ infinite loop for schedule """
    global job_timer
    job_timer = time.time()
    while 1:
        schedule.run_pending()
        time.sleep(1)

@app.route('/timer/<string:status>')
def mytimer(status, nsec=10):
    global t, job_timer
    if status=='on' and not t:
        schedule.every(nsec).seconds.do(run_job, str(uuid.uuid4()))
        t = Process(target=run_schedule)
        t.start()
        return "timer on with interval:{}sec\n".format(nsec)
    elif status=='off' and t:
        if t:
            t.terminate()
            t = None
            schedule.clear()
        return "timer off\n"
    return "timer status not changed\n"

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

您可以通过以下方式对此进行测试:

$ curl http://127.0.0.1:5000/timer/on
timer on with interval:10sec
$ curl http://127.0.0.1:5000/timer/on
timer status not changed
$ curl http://127.0.0.1:5000/timer/off
timer off
$ curl http://127.0.0.1:5000/timer/off
timer status not changed

计时器每隔10秒就会向控制台发出一个计时器消息:

127.0.0.1 - - [18/Sep/2018 21:20:14] "GET /timer/on HTTP/1.1" 200 -
timer job id=b64ed165-911f-4b47-beed-0d023ead0a33
timer: 10.0117sec
timer job id=b64ed165-911f-4b47-beed-0d023ead0a33
timer: 10.0102sec

我不是多处理专家,但是如果您使用它,很可能会出现泡菜错误。
Patrick Mutuku

@PatrickMutuku,我看到的与数字序列化(cookies,临时文件)有关的唯一问题是异步和websockets,但是Flask不是您的api,请查看github.com/kennethreitz/responder。在纯REST上有烧瓶excell,在Apache wsgi上有一个简单的前端。
MortenB

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.