如何在网页上制作pandas操作的进度条
How to make a progress bar on a web page for pandas operation
我已经在谷歌上搜索了一段时间,但无法找到一种方法来做到这一点。我有一个简单的 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
添加一个进度条,它本质上是一个 pandas 应用操作。我发现 tqdm
现在可以与 pandas 一起使用,并且它有一个 progress_apply
方法而不是 apply
。但是我不确定它是否与在网页上制作进度条有关。我想应该是因为它适用于 Jupyter 笔记本。如何在此处添加 pd_convert()
的进度条?
我想要的最终结果是:
- 用户点击上传,select 来自他们的文件系统的 CSV 文件
- 用户点击提交
- 进度条开始运行
- 一旦进度条达到 100%,就会触发下载
1 和 2 现已完成。那么接下来的问题就是如何触发下载。现在,我的 convert
函数可以毫无问题地触发下载,因为响应是由一个文件组成的。如果我想呈现页面,我会使用 return render_template(...)
形成响应。由于我只能有一个响应,是否可以只调用一次 /convert
?
来获得 3 和 4
不是 Web 开发人员,仍在学习基础知识。提前致谢!
====编辑====
我尝试了示例 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 竞争条件?如果多个用户同时发出请求,则每个用户的进度应该是独立的。我正在考虑为每个
submit
事件提供一个随机的 job_id
并使其成为 Redis 中的关键。由于我不需要每项工作完成后的条目,所以我会在完成后删除条目。
我觉得我缺少的部分是对text/event-stream
的理解。感觉我接近一个可行的解决方案。请分享您对 "proper" 方法的看法。我只是在猜测并尝试组合一些对我的理解非常有限的东西。
好的,我缩小了我遗漏的问题并解决了。我需要的概念包括
后端
- Redis 作为 key-value 数据库存储进度,端点
/progress
可以查询 事件流 (HTML5)
- Server-Sent 事件 (SSE) 用于流式传输进度:
text/event-stream
MIME 类型响应
- Python SSE 的 Flask 应用中的生成器
- 将 Pandas 数据帧上的 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>
示例后端 Flask 代码
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')
剩下的就是Pandas循环写入Redis的代码
我拼凑了几个小时的谷歌搜索结果,所以我觉得最好在这里为那些也需要这个基本功能的人做文档:在 Flask 网络应用程序中为 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?
我已经在谷歌上搜索了一段时间,但无法找到一种方法来做到这一点。我有一个简单的 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
添加一个进度条,它本质上是一个 pandas 应用操作。我发现 tqdm
现在可以与 pandas 一起使用,并且它有一个 progress_apply
方法而不是 apply
。但是我不确定它是否与在网页上制作进度条有关。我想应该是因为它适用于 Jupyter 笔记本。如何在此处添加 pd_convert()
的进度条?
我想要的最终结果是:
- 用户点击上传,select 来自他们的文件系统的 CSV 文件
- 用户点击提交
- 进度条开始运行
- 一旦进度条达到 100%,就会触发下载
1 和 2 现已完成。那么接下来的问题就是如何触发下载。现在,我的 convert
函数可以毫无问题地触发下载,因为响应是由一个文件组成的。如果我想呈现页面,我会使用 return render_template(...)
形成响应。由于我只能有一个响应,是否可以只调用一次 /convert
?
不是 Web 开发人员,仍在学习基础知识。提前致谢!
====编辑====
我尝试了示例 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 竞争条件?如果多个用户同时发出请求,则每个用户的进度应该是独立的。我正在考虑为每个
submit
事件提供一个随机的job_id
并使其成为 Redis 中的关键。由于我不需要每项工作完成后的条目,所以我会在完成后删除条目。
我觉得我缺少的部分是对text/event-stream
的理解。感觉我接近一个可行的解决方案。请分享您对 "proper" 方法的看法。我只是在猜测并尝试组合一些对我的理解非常有限的东西。
好的,我缩小了我遗漏的问题并解决了。我需要的概念包括
后端
- Redis 作为 key-value 数据库存储进度,端点
/progress
可以查询 事件流 (HTML5) - Server-Sent 事件 (SSE) 用于流式传输进度:
text/event-stream
MIME 类型响应 - Python SSE 的 Flask 应用中的生成器
- 将 Pandas 数据帧上的 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>
示例后端 Flask 代码
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')
剩下的就是Pandas循环写入Redis的代码
我拼凑了几个小时的谷歌搜索结果,所以我觉得最好在这里为那些也需要这个基本功能的人做文档:在 Flask 网络应用程序中为 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?