如何在网页上制作进度栏以进行熊猫操作

时间:2019-04-12 19:43:31

标签: python ajax pandas flask tqdm

我已经搜索了一段时间,却找不到解决方法。我有一个简单的Flask应用,该应用需要一个CSV文件,将其读入Pandas数据框,将其转换并输出为新的CSV文件。我已成功上传并成功使用HTML进行了转换

<div class="container">
  <form method="POST" action="/convert" enctype="multipart/form-data">
    <div class="form-group">
      <br />
      <input type="file" name="file">
      <input type="submit" name="upload"/>
    </div>
  </form>
</div>

点击提交后,它将在后台运行转换一段时间,并在完成后自动触发下载。接受result_df并触发下载的代码如下:

@app.route('/convert', methods=["POST"])
def convert(
  if request.method == 'POST':
    # Read uploaded file to df
    input_csv_f = request.files['file']
    input_df = pd.read_csv(input_csv_f)
    # TODO: Add progress bar for pd_convert
    result_df = pd_convert(input_df)
    if result_df is not None:
      resp = make_response(result_df.to_csv())
      resp.headers["Content-Disposition"] = "attachment; filename=export.csv"
      resp.headers["Content-Type"] = "text/csv"
      return resp

我想在pd_convert上添加一个进度条,这实际上是一个熊猫应用操作。我发现tqdm现在可用于熊猫,并且它具有progress_apply方法而不是apply。但是我不确定是否与在网页上制作进度条有关。我猜应该是因为它可以在Jupyter笔记本上使用。如何在此处为pd_convert()添加进度条?

我想要的最终结果是:

  1. 用户点击上传,从其文件系统中选择CSV文件
  2. 用户点击提交
  3. 进度条开始运行
  4. 进度条达到100%后,就会触发下载

1和2现在完成。接下来的问题是如何触发下载。现在,我的convert函数可以毫无问题地触发下载,因为响应是由文件形成的。如果要渲染页面,则使用return render_template(...)进行响应。由于我只能收到一个回复​​,因此仅通过一次致电/convert就可以有3和4吗?

不是Web开发人员,仍在学习基础知识。预先感谢!


==== EDIT ====

我尝试对示例here进行了一些修改。我从数据帧上for循环的行索引中获取进度,并将其放入Redis中。客户端通过询问此新端点/progress从流中获得Redis的进度。像

@app.route('/progress')
def progress():
  """Get percentage progress for the dataframe process"""
  r = redis.StrictRedis(
    host=redis_host, port=redis_port, password=redis_password, decode_responses=True)
  r.set("progress", str(0))
  # TODO: Problem, 2nd submit doesn't clear progress to 0%. How to make independent progress for each client and clear to 0% on each submit
  def get_progress():

    p = int(r.get("progress"))
    while p <= 100:
      p = int(r.get("progress"))
      p_msg = "data:" + str(p) + "\n\n"
      yield p_msg
      logging.info(p_msg)
      if p == 100:
        r.set("progress", str(0))
      time.sleep(1)

  return Response(get_progress(), mimetype='text/event-stream')

它目前正在运行,但存在一些问题。原因肯定是我对这种解决方案缺乏了解。

问题:

  • 我需要每次按下submit按钮时将进度重置为0。我尝试了几个地方将其重置为0,但尚未找到有效的版本。这与我对流的工作原理缺乏了解有关。现在,只有在刷新页面时它才会重置。
  • 如何处理并发请求(也称为Redis竞争条件)?如果多个用户同时发出请求,则每个用户的进度应独立。我正在考虑为每个job_id事件提供一个随机submit并将其作为Redis的密钥。由于完成每项工作后都不需要该条目,因此我将在完成后删除该条目。

我觉得我缺少的部分是对text/event-stream的理解。感觉我已经接近可行的解决方案。请分享您对执行此操作的“正确”方法的看法。我只是在猜测并试图将一些我无法理解的东西组合在一起。

1 个答案:

答案 0 :(得分:6)

好的,我缩小了我所缺少的问题,并弄清了。我需要的概念包括

后端

  • Redis作为键值数据库来存储进度,终结点/progress可以查询事件流(HTML5)
  • 服务器发送事件(SSE),用于流式传输进度:text/event-stream MIME类型响应
  • Flask应用程序中用于SSE的Python生成器
  • 将熊猫数据帧上for循环的进度(正在处理行索引)写入Redis

前端

  • 打开事件流:通过 HTML按钮
  • 从客户端触发SSE
  • 关闭事件流:一旦事件数据达到100%
  • 使用jQuery动态更新事件流的进度条

示例HTML

  <script>
  function getProgress() {
    var source = new EventSource("/progress");
    source.onmessage = function(event) {
      $('.progress-bar').css('width', event.data+'%').attr('aria-valuenow', event.data);
      $('.progress-bar-label').text(event.data+'%');

      // Event source closed after hitting 100%
      if(event.data == 100){
        source.close()
      }
    }
  }
  </script>

  <body>
    <div class="container">
      ...
      <form method="POST" action="/autoattr" enctype="multipart/form-data">
        <div class="form-group">
        ...
          <input type="file" name="file">
          <input type="submit" name="upload" onclick="getProgress()" />
        </div>
      </form>

      <div class="progress" style="width: 80%; margin: 50px;">
        <div class="progress-bar progress-bar-striped active"
          role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%">
          <span class="progress-bar-label">0%</span>
        </div>
      </div>
    </div>
  </body>

示例后端烧瓶代码

redis_host = "localhost"
redis_port = 6379
redis_password = ""
r = redis.StrictRedis(
  host=redis_host, port=redis_port, password=redis_password, decode_responses=True)

@app.route('/progress')
def progress():
  """Get percentage progress for auto attribute process"""
  r.set("progress", str(0))
  def progress_stream():
    p = int(r.get("progress"))
    while p < 100:
      p = int(r.get("progress"))
      p_msg = "data:" + str(p) + "\n\n"
      yield p_msg
      # Client closes EventSource on 100%, gets reopened when `submit` is pressed
      if p == 100:
        r.set("progress", str(0))
      time.sleep(1)

  return Response(progress_stream(), mimetype='text/event-stream')

其余是熊猫的代码,用于循环写入Redis。

我搜集了数小时的Google搜索结果,因此对于那些也需要此基本功能的人来说,最好在此处进行记录:在Flask Web应用程序中添加进度条以处理Pandas数据框。

一些有用的参考

https://medium.com/code-zen/python-generator-and-html-server-sent-events-3cdf14140e56

https://codeburst.io/polling-vs-sse-vs-websocket-how-to-choose-the-right-one-1859e4e13bd9

What are Long-Polling, Websockets, Server-Sent Events (SSE) and Comet?

相关问题