Python+Flask框架:一个简单的web-app
我想用python、Flask框架、html去开发一个线性回归预测模型的 web service。version1.0:简单思路如下,前端传递后台数据后端读取数据,调用线性回归函数,完成该数据模型的预测将模型预测的结果-图片返回给前端【图片保存,转化为字符串形式 返回给指定页面,页面跳转】关于flask框架的学习,可以看这个链接:https://www.cnblogs.com/zhaopanpan
我想用python、Flask框架、html去开发一个线性回归预测模型的 web service。
version1.0:简单思路如下,
- 前端传递后台数据
- 后端读取数据,调用线性回归函数,完成该数据模型的预测
- 将模型预测的结果-图片返回给前端【图片保存,转化为字符串形式 返回给指定页面,页面跳转】
关于flask框架的学习,可以看这个链接:https://www.cnblogs.com/zhaopanpan/p/9033100.html
1、创建一个简单的Flask 框架
参考链接:https://www.liaoxuefeng.com/wiki/1016959663602400/1017805733037760
1.1、WSGI接口
一个Web应用的本质就是:
- 浏览器发送一个HTTP请求;
- 服务器收到请求,生成一个HTML文档;
- 服务器把HTML文档作为HTTP响应的Body发送给浏览器;
- 浏览器收到HTTP响应,从HTTP Body取出HTML文档并显示。
使用Python来进行web开发,关于 HTTP请求、发送响应这些,由专门的服务器软件去实现。 我们需要使用python专注于去生成一个 HTML文档。在这里使用 WSGI接口去提供统一的这个http请求、解析服务。
- WSGI:Web Server Gateway Interfac
代码:
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
return [b'<h1>Hello, web!</h1>']
==application()
==函数就是符合WSGI标准的一个HTTP处理函数,它接收两个参数:
- environ:一个包含所有HTTP请求信息的
dict
对象; - start_response:一个发送HTTP响应的函数。
在函数中的 start_response() 就表示发送了HTTP响应, 响应状态码为200
- 通常情况下,都应该把
Content-Type
头发送给浏览器。其他很多常用的HTTP Header也应该发送。 - 然后,函数的返回值
b'<h1>Hello, web!</h1>'
将作为HTTP响应的Body发送给浏览器。
application()函数的调用 是由 WSGI服务器来调用的。
运行WSGI服务
- 我们自己编写的WSGI处理函数:
# hello.py
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
return [b'<h1>Hello, web!</h1>']
# 读取URL路径参数
def applicationPath_INFO(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
body = '<h1>Hello, %s!</h1>' % (environ['PATH_INFO'][1:] or 'web')
return [body.encode('utf-8')]
这里的读取URL路径参数 %s 这些,其实类似于 springMVC中获取http请求参数一样,使用 @PathVariable(“id”) 完成。
- 编写的 server.py,负责启动WSGI服务器,它会去加载运行这个Http应用程序,监听 其中的environ 和 start_response
# server.py
# 从wsgiref模块导入:
from wsgiref.simple_server import make_server
# 导入我们自己编写的application函数:
from hello import application, applicationPath_INFO
# 创建一个服务器,IP地址为空,端口是5000,处理函数是application:
# httpd = make_server('', 5000, application)
httpd = make_server('', 8081, applicationPath_INFO)
print('Serving HTTP on port 8081...')
# 开始监听HTTP请求:
httpd.serve_forever()
运行程序,打开浏览器测试: http://localhost:8081/
小结
对于一个web应用程序,入口其实就是一个 WSGI处理函数。
- HTTP请求的所有输入信息都可以通过
environ
获得 - HTTP响应的输出都可以通过
start_response()
加上函数返回值作为Body。
1.2、使用Web框架
一个Web App,其实就是一个 WSGI的处理函数。它去针对每一个HTTP请求 作处理 并响应。在这里,需要解决的是 如何处理100个不同的URL。
-
每一个URL 会有不同的 http请求方法,请求路径 url path_Info等等。【GET/POST/DELETE/PUT】
-
解决方法可以是 进行适配分析, switch 类似的,一一对应
-
def application(environ, start_response): method = environ['REQUEST_METHOD'] path = environ['PATH_INFO'] if method=='GET' and path=='/': return handle_home(environ, start_response) if method=='POST' and path='/signin': return handle_signin(environ, start_response) ...
-
上面这样,不利于维护,会在原有代码基础上进行增加修改。所以,再次抽象,类似于 工厂模式,适配器模式 创建接口函数
-
-
解决方案是:我们专注于用一个函数处理一个URL,至于URL到函数的映射,就交给Web框架来做
在这里,采用 flask 框架:
$ pip install flask
写一个app.py
,处理3个URL,分别是:
GET /
:首页,返回Home
;GET /signin
:登录页,显示登录表单;POST /signin
:处理登录表单,显示登录结果。
注意噢,同一个URL/signin
分别有GET和POST两种请求,映射到两个处理函数中。
创建框架
from flask import Flask
from flask import request
app = Flask(__name__)
@app.route('/', methods=['GET', 'POST'])
def home():
return '<h1>Home</h1>'
@app.route('/signin', methods=['GET'])
def signin_form():
return '''<form action="/signin" method="post">
<p><input name="username"></p>
<p><input name="password" type="password"></p>
<p><button type="submit">Sign In</button></p>
</form>'''
@app.route('/signin', methods=['POST'])
def signin():
# 需要从request对象读取表单内容:
#x = request.form['username']
#print(x)
if request.form['username']=='admin' and request.form['password']=='password':
return '<h3>Hello, admin! x</h3>'
return '<h3>Bad username or password. </h3>'
if __name__ == '__main__':
app.run()
运行python app.py
,Flask自带的Server在端口5000
上监听:
打开浏览器,输入首页地址http://localhost:5000/
:
首页显示正确!
再在浏览器地址栏输入http://localhost:5000/signin
,会显示登录表单:
输入预设的用户名admin
和口令password
,登录成功:
输入其他错误的用户名和口令,登录失败:
实际的Web App应该拿到用户名和口令后,去数据库查询再比对,来判断用户是否能登录成功。
除了Flask,常见的Python Web框架还有:
小结
有了Web框架,我们在编写Web应用时,注意力就从WSGI处理函数转移到URL+对应的处理函数,这样,编写Web App就更加简单了。
在编写URL处理函数时,除了配置URL外,从HTTP请求拿到用户数据也是非常重要的。Web框架都提供了自己的API来实现这些功能。
Flask通过request.form['name']
来获取表单的内容。
1.3、使用模板开发Web
使用Web 框架,我们不用自己写 WSGI接口对应的每个 url http请求处理函数,去处理URL
- 只要专心写 URL处理函数就行。
Web Service 展现的是 获取 URL 信息和数据后,进行逻辑处理,再展示给用户。函数返回时一般是一个包含 HTML 的字符串。 对于复杂的页面,HTML 不仅要正确还要 进行CSS美化,再加上JavaScript脚本去实现各种交互和动画效果。在python中直接生成HTML页面难度比较大。
使用模板, 我们就是要预先准备一个HTML文档。 在这个文档中,会嵌入一些变量和指令,然后,根据我们传入的数据进行解析,得到最终的HTML页面,发送给用户:
上面的这个就是MVC:Model-View-Controller。
- C:Controller,控制器。Python中处理URL的函数就是控制器,负责业务逻辑。
- V:View,View负责显示逻辑。包含变量
{{ name }}
的模板就是V。View的输出结果就是用户看到的HTML。 - Model:模型。这里的Model就是用来传给View的。这样View在替换变量的时候,就可以直接从Model中取出相应的数据。【业务层、数据层,即对应着java开发中的 service层、dao层】
上面的例子中,Model就是一个dict
:
{ 'name': 'Michael' }
只是因为Python支持关键字参数,很多Web框架允许传入关键字参数,然后,在框架内部组装出一个dict
作为Model。
现在,我们把上次直接输出字符串作为HTML的例子用高端大气上档次的MVC模式改写一下:
# Web MVC 框架
# Flask通过render_template()函数来实现模板的渲染。和Web框架类似,Python的模板也有很多种。Flask默认支持的模板是jinja2,
# 所以我们先直接安装jinja2: $ pip install jinja2
# 然后,开始编写jinja2模板: home.html
from flask import Flask, request, render_template
app = Flask(__name__)
# 首页
@app.route('/', methods=['GET', 'POST'])
def home():
return render_template('home.html')
# 登录表单
@app.route('/signin', methods=['GET'])
def signin_form():
return render_template('form.html')
# 登录成功表单
@app.route('/signin', methods=['POST'])
def signin():
username = request.form['username']
password = request.form['password']
if username=='admin' and password=='password':
return render_template('signin-ok.html', username=username)
return render_template('form.html', message='Bad username or password', username=username)
if __name__ == '__main__':
app.run()
此时就已经使用 **render_template() 函数**来实现模板的渲染。
Flask通过render_template()
函数来实现模板的渲染。和Web框架类似,Python的模板也有很多种。Flask默认支持的模板是jinja2,所以我们先直接安装jinja2:
$ pip install jinja2
然后,开始编写jinja2模板:
home.html
用来显示首页的模板:
<html>
<head>
<title>Home</title>
</head>
<body>
<h1 style="font-style:italic">Home</h1>
</body>
</html>
form.html
用来显示登录表单的模板:
<html>
<head>
<title>Please Sign In</title>
</head>
<body>
{% if message %}
<p style="color:red">{{ message }}</p>
{% endif %}
<form action="/signin" method="post">
<legend>Please sign in:</legend>
<p><input name="username" placeholder="Username" value="{{ username }}"></p>
<p><input name="password" placeholder="Password" type="password"></p>
<p><button type="submit">Sign In</button></p>
</form>
</body>
</html>
signin-ok.html
登录成功的模板:
<html>
<head>
<title>Welcome, {{ username }}</title>
</head>
<body>
<p>Welcome, {{ username }}!</p>
</body>
</html>
登录失败的模板呢?我们在form.html
中加了一点条件判断,把form.html
重用为登录失败的模板。
最后,一定要把模板放到正确的templates
目录下,templates
和app.py
在同级目录下:
运行 程序,http://127.0.0.1:5000/signin
通过MVC,我们在Python代码中处理M:Model和C:Controller,而V:View是通过模板处理的,这样,我们就成功地把Python代码和HTML代码最大限度地分离了。
使用模板的另一大好处是,模板改起来很方便,而且,改完保存后,刷新浏览器就能看到最新的效果,这对于调试HTML、CSS和JavaScript的前端工程师来说实在是太重要了。
在Jinja2模板中,我们用=={{ name }}
==表示一个需要替换的变量。很多时候,还需要循环、条件判断等指令语句,在Jinja2中,用{% ... %}
表示指令。
比如循环输出页码:
{% for i in page_list %}
<a href="/page/{{ i }}">{{ i }}</a>
{% endfor %}
如果page_list
是一个list:[1, 2, 3, 4, 5]
,上面的模板将输出5个超链接。
除了Jinja2,常见的模板还有:
- Mako:用
<% ... %>
和${xxx}
的一个模板; - Cheetah:也是用
<% ... %>
和${xxx}
的一个模板; - Django:Django是一站式框架,内置一个用
{% ... %}
和{{ xxx }}
的模板。
小结
有了MVC,我们就分离了Python代码和HTML代码。HTML代码全部放到模板里,写起来更有效率。
2、线性回归 Web app demo
线性回归 web-app Demo的功能:
- 前端传递 x值,y值
- 后台读取前台数据,然后调用 线性回归函数 LineProcesser()。
- 返回给前端显示页面,展现给用户
2.1、使用Web框架开发
在这里,我首先模仿前面的 Web 框架开发进行代码编写。类似于首页登录,那么我在这里去获取前端传递的参数:
- 获取 x 和 y 的参数值
- x 和y 前端过来的,应该是字符串,我将其转化为 整数列表
- 获得了这两个 x ,y 后,我去调用 线性回归函数 LineProcesser(),进行绘图查看结果
- 先查看能不能获取数据,正确地绘图。
- 绘图展示。 利用了matplotlib 库中的函数
编写 LineDemo03.py 代码,使用 print()输出,是为了测试 看代码可以运行到哪里。
- 线性回归函数 LinearRegression()的例子链接:
- https://blog.csdn.net/qq_38328378/article/details/80775351
- https://www.cnblogs.com/learnbydoing/p/12190168.html 【查看这一部分去解决的 error:Reshape your data either using array.reshape(-1, 1) if your data has a single feature or array.reshape(1, -1) if it contains a single sample.】
# 线性回归 web
from flask import Flask
from flask import request
import string
# 导入线性回归
import LinearRegression
# 线性回归运算
import numpy as np
from pandas import read_csv;
from matplotlib import pyplot as plt;
from sklearn.linear_model import LinearRegression
def LineProcesser(x, y):
print('开始调用了......')
# 从前台获取数据 x 和 y
print("前端传递给的 x 值为:", x)
print("前端传递给的 y 值为:", y)
'''
当 x 具有单个特征的时候,error:Reshape your data either using array.reshape(-1, 1)
if your data has a single feature or array.reshape(1, -1) if it contains a single sample.
'''
X = np.array(x).reshape(-1,1)
#X.reshape(-1, 1)
Y = np.array(y)
# 第二步,画出散点图,求x和y的相关系数
#plt.plot(x, y)
plt.scatter(x, y)
plt.show()
# 第三步,估计模型参数,建立回归模型
#lrModel = LinearRegression()
lrModel = LinearRegression(copy_X=True, fit_intercept=True, n_jobs=1, normalize=False)
# 训练模型
lrModel.fit(X, Y)
# 第四步、对回归模型进行检验
lrModel.score(X, Y)
# 第五步、利用回归模型进行预测
#lrModel.predict([[50], [40], [30]]) # 这里的是线性回归模型进行预测,预测的几个点的值。 但这个是二维数组,所以会报错
'''
ValueError: Expected 2D array, got 1D array instead:
array=[10 20 30].
'''
predicted = lrModel.predict(X) #使用模型预测
# 绘制散点图 参数:x横轴 y纵轴
plt.scatter(X, Y, marker='x')
plt.plot(X, predicted, c = 'r')
# 绘制x轴和y轴坐标
plt.xlabel("x")
plt.ylabel("y")
# 显示图形
plt.show()
app = Flask(__name__)
@app.route('/', methods=['GET', 'POST'])
def home():
return '<h1>Home</h1>'
# 读取前端的一串数字
@app.route('/line', methods=['GET'])
def line_form():
return '''<form action="/line" method="post">
<p><input name="username"></p>
<p><input name="password"></p>
<p><button type="submit">Sign In</button></p>
</form>'''
@app.route('/line', methods=['POST'])
def line():
# 获取前台提供的数据点
x = request.form['username']
y = request.form['password']
#print(x)
# 分割字符串
xStr = x.split(',')
yStr = y.split(',')
# 前台进入的数据全部是字符型,需要转换. 将整数添加到列表中,然后用列表中的数据去进行计算。 将得到的结果返回。 可以将图片转化成流的形式,传递给前端
strX = [];
strY = [];
for i in range(len(xStr)):
strX.append( int(xStr[i]) )
strY.append(int(yStr[i]))
print(strX)
# print(type(strX))
#print(type(strX[0]))
print(strY)
# 调用线性回归
LineProcesser(x = strX, y = strY);
print('调用成功')
# 需要从request对象读取表单内容:
if request.form['username']=='admin' and request.form['password']=='password':
return '<h3>Hello, admin! </h3>'
return '<h3>Bad username or password. </h3>'
if __name__ == '__main__':
app.run()
运行,测试:http://127.0.0.1:5000/
测试,后台能否获取数据,并调用 Line函数进行线性回归拟合 绘图展示 :http://127.0.0.1:5000/line
点击 sigin后,会去运行 @app.route(’/line’, methods=[‘POST’]) 函数,可以看到在后台的数据结果:
[20, 30, 40]
[50, 60, 80]
开始调用了......
前端传递给的 x 值为: [20, 30, 40]
前端传递给的 y 值为: [50, 60, 80]
127.0.0.1 - - [14/Oct/2021 17:00:04] "POST /line HTTP/1.1" 200 -
调用成功
后台输出 传递数据, 调用成功的标记。输出了 调用成功,且图片展示了。
- 此时我想把图片结果展现给前台。 第一个想法就是 把图片保存在 static 静态资源中,然后直接去获取这个 static下的图片文件即可
- 保存图片,并显示给前端。 参考链接:https://www.jianshu.com/p/9aa1b5180c23
- 保存图片到本地。 参考链接:https://blog.csdn.net/qq_42845522/article/details/118604527
- https://www.jianshu.com/p/ddc7c43253b2
python flask将读取的图片返回给web前端
参考链接:https://www.jianshu.com/p/9aa1b5180c23
重点需要注意的地方:
1、open(img_local_path, ‘r’) ,这样不会显示图片,正确的为:open(img_local_path, ‘rb’)
2、然后最关键的是:将这句base64.b64encode(img_stream)后加上.decode(),作用是把格式转为字符串。【应该类似于字节流吧,然后便于传输】
LineDemo03.py 代码的修改:
# 线性回归 web
from flask import Flask, app
from flask import request
from flask import render_template #
import numpy as np
from matplotlib import pyplot as plt;
# 线性回归运算
from sklearn.linear_model import LinearRegression
def LineProcesser(x, y):
print('开始调用了......')
# 从前台获取数据 x 和 y, 并对数据作数组或矩阵处理
print("前端传递给的 x 值为:", x)
print("前端传递给的 y 值为:", y)
X = np.array(x).reshape(-1,1)
Y = np.array(y)
# 第二步,画出散点图,求x和y的相关系数
#plt.plot(x, y)
plt.scatter(x, y)
#plt.savefig('E:\\test01.png')
plt.show()
# 第三步,估计模型参数,建立回归模型
#lrModel = LinearRegression()
lrModel = LinearRegression(copy_X=True, fit_intercept=True, n_jobs=1, normalize=False)
# 训练模型
lrModel.fit(X, Y)
# 第四步、对回归模型进行检验
lrModel.score(X, Y)
# 第五步、利用回归模型进行预测
predicted = lrModel.predict(X) #使用模型预测
# 绘制散点图 参数:x横轴 y纵轴
# plt.figure(figsize=(8, 6), dpi=600) 3这个dpi=600, 影响用户体验
plt.scatter(X, Y, marker='x')
plt.plot(X, predicted, c = 'r')
# 绘制x轴和y轴坐标
plt.xlabel("x")
plt.ylabel("y")
# 保存图片,显示图形
plt.savefig('static/LineDemo03.png')
#plt.savefig('E:\\test02.png')
plt.show()
# Flask读取服务器本地图片,并返回图片流给前端显示
def return_img_stream(img_local_path):
"""
工具函数:
获取本地图片流
:param img_local_path:文件单张图片的本地绝对路径
:return: 图片流
"""
import base64
img_stream = ''
with open(img_local_path, 'rb') as img_f: #
img_stream = img_f.read()
img_stream = base64.b64encode(img_stream).decode()
return img_stream
#
app = Flask(__name__)
@app.route('/', methods=['GET', 'POST'])
def home():
return '<h1>Home</h1>'
# 读取前端的一串数字
@app.route('/line', methods=['GET'])
def line_form():
return '''<form action="/line" method="post">
<p><input name="xValue"></p>
<p><input name="yValue"></p>
<p><button type="submit">Load numbers</button></p>
</form>'''
@app.route('/line', methods=['POST'])
def line():
# 获取前台提供的数据点
x = request.form['xValue']
y = request.form['yValue']
#print(x)
# 分割字符串
xStr = x.split(',')
yStr = y.split(',')
# 前台进入的数据全部是字符型,需要转换. 将整数添加到列表中,然后用列表中的数据去进行计算。 将得到的结果返回。
# 可以将图片转化成流的形式,传递给前端
strX = [];
strY = [];
for i in range(len(xStr)):
strX.append( int(xStr[i]) )
strY.append(int(yStr[i]))
print(strX)
print(strY)
# 调用线性回归
LineProcesser(x = strX, y = strY)
print('调用成功')
img_path = 'static/LineDemo03.png'
img_stream = return_img_stream(img_path)
return render_template('LineFigure.html', img_stream=img_stream)
if __name__ == '__main__':
app.run()
运行,测试:http://127.0.0.1:5000/line
结果:
改进点:
- 初次登录页面的显示
- http://127.0.0.1:5000/line 后调用完后去进行视图跳转,跳转到运行界面
- 其它几个WSGI 的URL处理函数的 模板改进
2.2、使用模板开发、视图跳转
-
使用模板开发: HTML模板,将html显示和 python 控制程序分开。
-
视图跳转,其实就是在这个页面完成某个动作后,去跳转到其它页面,或者进行更新
-
<form action="/lineFigure" method="post"> <legend>Please load numbers:</legend> <p><input name="xValue" placeholder="XValue" value="{{ xValue }}"></p> <p><input name="yValue" placeholder="YValue" value="{{ yValue }}"></p> <p><button type="submit">Load numbers</button></p>
-
这里的就是 Load numbers 组件按钮,动作为 submit时,会去触发 跳转到 /lineFigure 这个URL处理函数中。
-
修改之后的 WSGI 函数:
# 线性回归 web
from flask import Flask, app
from flask import request
from flask import render_template
import numpy as np
from matplotlib import pyplot as plt;
from sklearn.linear_model import LinearRegression # 线性回归运算
def LineProcesser(x, y):
print('开始调用了......')
# 从前台获取数据 x 和 y, 并对数据作数组或矩阵处理
print("前端传递给的 x 值为:", x)
print("前端传递给的 y 值为:", y)
X = np.array(x).reshape(-1,1)
Y = np.array(y)
# 第二步,画出散点图,求x和y的相关系数
plt.scatter(x, y)
plt.show()
# 第三步,估计模型参数,建立回归模型
lrModel = LinearRegression(copy_X=True, fit_intercept=True, n_jobs=1, normalize=False)
# 训练模型
lrModel.fit(X, Y)
# 第四步、对回归模型进行检验
lrModel.score(X, Y)
# 第五步、利用回归模型进行预测
predicted = lrModel.predict(X) #使用模型预测
# 绘制散点图 参数:x横轴 y纵轴
plt.scatter(X, Y, marker='x')
plt.plot(X, predicted, c = 'r')
# 绘制x轴和y轴坐标
plt.xlabel("x")
plt.ylabel("y")
# 保存图片,显示图形
plt.savefig('static/LineDemo03.png')
#plt.savefig('E:\\test02.png')
plt.show()
# Flask读取服务器本地图片,并返回图片流给前端显示
def return_img_stream(img_local_path):
"""
工具函数:
获取本地图片流
:param img_local_path:文件单张图片的本地绝对路径
:return: 图片流
"""
import base64
img_stream = ''
with open(img_local_path, 'rb') as img_f:
img_stream = img_f.read()
img_stream = base64.b64encode(img_stream).decode()
return img_stream
app = Flask(__name__)
@app.route('/', methods=['GET', 'POST'])
def home():
return render_template('lineHome.html')
# 读取前端的一串数字
@app.route('/lineIndex', methods=['GET'])
def line_form():
return render_template('lineIndex.html')
#@app.route('/lineIndex', methods=['POST'])
@app.route('/lineFigure', methods=['POST'])
def line():
# 获取前台提供的数据点
x = request.form['xValue']
y = request.form['yValue']
#print(x)
# 分割字符串
xStr = x.split(',')
yStr = y.split(',')
# 前台进入的数据全部是字符型,需要转换. 将整数添加到列表中,然后用列表中的数据去进行计算。 将得到的结果返回。
strX = [];
strY = [];
for i in range(len(xStr)):
strX.append( int(xStr[i]) )
strY.append(int(yStr[i]))
# 调用线性回归
LineProcesser(x = strX, y = strY)
print('调用成功')
img_path = 'static/LineDemo03.png'
img_stream = return_img_stream(img_path)
return render_template('LineFigure.html', img_stream=img_stream)
if __name__ == '__main__':
app.run()
模板:
lineHome.html:
{#首页表单#}
<html>
<head>
<meta charset="UTF-8">
<title>Home</title>
<body>
<h1 style="font-style:italic">Home</h1>
</body>
</html>
lineIndex.html模板:
{#线性回归上传数据表单#}
<html>
<head>
<meta charset="UTF-8">
<title>Please Load Numbers</title>
</head>
<body>
<form action="/lineFigure" method="post">
<legend>Please load numbers:</legend>
<p><input name="xValue" placeholder="XValue" value="{{ xValue }}"></p>
<p><input name="yValue" placeholder="YValue" value="{{ yValue }}"></p>
<p><button type="submit">Load numbers</button></p>
</form>
</body>
</html>
lineFigure.html模板:
{#展示线性回归的图片结果界面#}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Flask Show Image</title>
</head>
<body>
{# <img style="width:180px" src="data:;base64,{{ img_stream }}">#}
<img src="data:;base64,{{ img_stream }}">
</body>
2.3、上传文件测试
关于上传文件 参考的博客 链接:
https://www.cnblogs.com/nulige/p/13497254.html
https://www.imooc.com/wiki/flasklesson/flaskupload.html
https://www.cnblogs.com/wongbingming/p/6802660.html
自己的大致整体思路:
- 上传文件(规定文件的格式,先假设只能由 dsc文件的 *.csv 类型, 然后再处理 txt类型的数据)
- 文件上传成功之后, 获取指定路径下的信息, 读取数据
- 转换数据形式,调用 后台的 MCBL 代码
- 还应该解决的地方就是: 关于页面跳转的 url 路径设置 名称【这个后续再定义 参考Calfitter他们做的】
- 页面渲染的工作
- 应该有一个例子【例子中的文件,我就直接固定好了指定路径给他, Example】
上传文件测试:
- 上传文件 包括的几个部分有:
- upload.py 一个web app
- 初始化界面模板,进行上传和下载文件
- 上传成功界面
- 保存上传文件的 文件夹
upload.py 代码:
- url 路径 ‘/’ 初始化界面, 视图渲染返回 uploadIndex.html 模板,且把 ./upload 路径 当前路径下的文件和文件夹内容传递给 entries 信息
- url路径 ‘/upload’ 进行文件上传, 获取 request 请求中的 file 文件信息,进行文件保存,且保存的路径是 /upload 下面,且显示文件上传成功信息
- /files/ 完成文件下载功能。 使用 flask框架中的 send_from_directory() 函数
#!/usr/bin/python3
from flask import Flask, render_template, request, send_from_directory
import os
app = Flask(__name__)
@app.route('/')
def index():
entries = os.listdir('./upload')
return render_template('uploadIndex.html', entries = entries)
@app.route('/upload', methods=['POST'])
def upload():
f = request.files['file']
path = os.path.join('./upload', f.filename)
f.save(path)
return render_template('uploadSucess.html')
@app.route('/files/<filename>')
def files(filename):
return send_from_directory('./upload', filename, as_attachment=True)
# 这种的类似于 请求转发。 应该说这就是返回结果,不过是直接调用了 send_from_directory()方法
if __name__ == '__main__':
app.run(debug=True)
关于 send_from_directory() 函数:flask框架中的
- flask.send_from_directory(directory,filename,** options )
- filePath:文件的绝对路径,不包含文件名
- filename:文件名称
- as_attachment:是否显示文件名称as_attachment –设置为True是否要发送带有标题的文件。Content-Disposition: attachment 如果设置为False则浏览器返回文件预览 如果该文件可以被浏览器渲染,例如 pdf 图片 等【return send_from_directory(dirpath, filename, as_attachment=False) # as_attachment=True 一定要写,不然会变成打开,而不是下载】
‘/’ 访问时的初始化界面, 前端渲染模板是:uploadIndex.html:
<html>
<head>
<meta charset="UTF-8">
<title>文件上传</title>
</head>
<body>
<h2>Upload file</h2>
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="file" class="input">
<input type="submit" value="upload" class="input button">
</form>
<h2>Download file</h2>
<ol>
{% for entry in entries %}
<li><a href='/files/{{entry}}'>{{entry}}</a>
{% endfor %}
</ol>
</body>
</html>
界面效果如下:
-
我们在 web app 程序中有 return render_template(‘uploadIndex.html’, entries = entries) 。后端通过 entries 传递给了前端 ,且利用 for entry in entries 循环语句,显示 {{entry}} 即upload文件夹下的文件和文件夹。
-
点击 upload 进行提交, submit 让表单去访问 “/upload” ,完成文件上传的功能。 即访问 @app.route(’/upload’, methods=[‘POST’])
-
从 flask 模块中引入 request 对象,request 对象中的属性 files 记录了上传文件的相关信息。 f = request.files[‘file’]
uploadSucess.html 文件上传界面:
<html>
<head>
<meta charset="UTF-8">
<title>文件上传</title>
</head>
<body>
<h1>上传成功</h1>
<a href='/'>返回主页</a>
</html>
返回主页,则直接调用 ‘/’ 会回到初始界面。
下载文件:
- 访问路径 / 时,处理函数 index 列出目录 upload 下所有的文件名,作为参数传给首页的模板 index.html。
- 用户点击文件名链接后,即可下载相应的文件。这是调用了 @app.route(’/files/’) ,调用这个wed app函数,实行send_from_directory() 去下载文件
文件路径信息有:
2.4、 文件上传后,获取文件数据进行调用
文件上传成功后,跳转到了成功页面,此时便可以进行 读取文件中的数据,进行分析。
我仍然是拿一个 线性回归拟合 去进行测试。
思路:
- 我在成功界面进行分析。 那么我要后端调用这个页面的时候 就应该把文件的名称 路径信息 也传递过来
- 前端读取了这个 文件信息后,去调用一个 web app 完成线性回归,保存图片到指定文件夹,并显示结果(读取文件夹下的图片)。
参考了前后端数据传递的链接:https://blog.csdn.net/weixin_38168694/article/details/88769729
因为有两点:
- upload 上传文件成功后,我想要在 成功界面 去进行 用户的数据集的分析, 【后端传递给前端,在 @app.route(’/’)
def index() 这里面】 - 在成功界面 我要对数据集 进行分析,那么我需要把这个文件的路径 名称信息传递给后端 【前端传递给后端, 在 中】
html模板:
- {{fileName}} 这个是 后端传递给前端的数据。
<html>
<head>
<meta charset="UTF-8">
<title>文件上传</title>
</head>
<body>
<h1>上传成功</h1>
<a href='/'>返回主页</a>
<h1>线性回归模型预测</h1>
<body>
<!--<form id="form1" method="post" action="/linearRegressionPredection">-->
<form action="/analysis/linearRegression" method="post">
<div>
<p style="color:red"><input name="filename" value={{fileName}}></p>
<input type="submit" value="Linear Regression Analysis" class="input button">
</div>
</form>
</body>
</html>
页面结果如下所示:
web app代码:upload.py:这里新加了几个功能
- allowed_file() 函数 去判断 文件的后缀是否合法的, 即 *.csv 、 *.txt等
- index() 函数在进行文件上传时, 后端传递给前端 文件的名字信息。 我暂时只是传递一个文件的 【entries, 这里不严谨。多个的话 使用for循环】
- example() 和 downloadExample() 这两个函数 是我想要展示的一个固定例子。 【给定了指定文件名称。 不用进行上传】
- analysis_LinearRegression() 这个 app 完成 从前端得到 用户上传的文件名称信息,然后在后端去完成 线性回归拟合模型。
import os
from flask import Flask, render_template, send_from_directory, request, jsonify
import time
import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
from sklearn.linear_model import LinearRegression
app = Flask(__name__)
UPLOAD_FOLDER = 'upload'
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER # 设置文件上传的目标文件夹
basedir = os.path.abspath(os.path.dirname(__file__)) # 获取当前项目的绝对路径
ALLOWED_EXTENSIONS = set(['txt', 'csv', 'xls', 'xlsx']) # 允许上传的文件后缀
# 判断文件是否合法
def allowed_file(filename):
return '.' in filename and filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS
# 具有上传功能的页面
@app.route('/')
def index():
entries = os.listdir('./upload')
return render_template('upload.html', entries = entries)
@app.route('/upload', methods=['POST'], strict_slashes=False)
def api_upload():
file_dir = os.path.join(basedir, app.config['UPLOAD_FOLDER']) # 拼接成合法文件夹地址
if not os.path.exists(file_dir):
os.makedirs(file_dir) # 文件夹不存在就创建
f = request.files['myfile'] # 从表单的file字段获取文件,myfile为该表单的name值
if f and allowed_file(f.filename): # 判断是否是允许上传的文件类型
fname = f.filename
ext = fname.rsplit('.', 1)[1] # 获取文件后缀
unix_time = int(time.time())
new_filename = str(unix_time) + '.' + ext # 修改文件名 #??? 为什么要改文件名呢
f.save(os.path.join(file_dir, new_filename)) # 保存文件到upload目录
return render_template('uploadSucess.html',fileName = new_filename)
else:
return jsonify({"errno": 1001, "errmsg": "上传失败"})
# 这个展示所有的文件的信息,是不是可以 省略掉
@app.route('/files/<filename>')
def files(filename):
return send_from_directory('./upload', filename, as_attachment=True) # 这种的类似于 请求转发
################## 线性回归案例 ########################
@app.route("/example/linear")
def example():
#dirpath = os.path.join(app.root_path, 'static\LinearRegression_data.csv') # 默认给的线性回归数据 文件地址
filename = 'LinearRegression_data.csv' # 默认给的线性回归数据 文件地址
LineProcesser(filePre= 'static', fileName= filename)
img_path = 'results/LinearRegression_data.png'
img_stream = return_img_stream(img_path)
return render_template('LineFigure.html', img_stream=img_stream)
# file download
@app.route("/download_example")
def downloadExample():
print("下载案例数据集")
dirpath = os.path.join(app.root_path, 'static') # 这里是下载目录,从工程的根目录写起,比如你要下载static/js里面的js文件,这里就要写“static/js”
# return send_from_directory(dirpath, filename, as_attachment=False) # as_attachment=True 一定要写,不然会变成打开,而不是下载
filename = 'LinearRegression_data.csv'
return send_from_directory(dirpath, filename, as_attachment=True) # as_attachment=True 下载
# 线性回归模型
def LineProcesser(filePre, fileName):
print('开始调用了......')
dirpath = os.path.join(app.root_path, filePre, fileName) # 线性回归数据 文件地址
#print("dirpath", dirpath)
# 从数据集中读取数据
df = pd.read_csv(dirpath, header=None)
df = df.T
x = df.iloc[0, 1:]
x = x.astype(float)
x = np.array(x, dtype=np.float32)
y = df.iloc[1:, 1:]
y = y.astype(float)
y = np.array(y, dtype=np.float32)
# 从前台获取数据 x 和 y, 并对数据作数组或矩阵处理
#print("数据集中的 x 值为:", x)
#print("数据集中的 y 值为:", y)
x = x.reshape(-1,1)
y = y[0]
#print(y)
# 第二步,画出散点图,求x和y的相关系数
plt.scatter(x, y)
plt.show()
# 第三步,估计模型参数,建立回归模型
lrModel = LinearRegression(copy_X=True, fit_intercept=True, n_jobs=1, normalize=False)
# 训练模型
lrModel.fit(x, y)
# 第四步、对回归模型进行检验
lrModel.score(x, y)
# 第五步、利用回归模型进行预测
predicted = lrModel.predict(x) #使用模型预测
# 绘制散点图 参数:x横轴 y纵轴
plt.scatter(x, y, marker='x')
plt.plot(x, predicted, c = 'r')
# 绘制x轴和y轴坐标
plt.xlabel("x")
plt.ylabel("y")
# 保存图片,显示图形
#path = 'static/' + fileName + '.png'
filenamePre = os.path.splitext(fileName)[0]
savepath = os.path.join(app.root_path, 'results', filenamePre) # 线性回归数据 文件地址
img_path = savepath + '.png'
#print(img_path)
#plt.savefig('static/exampleLinearRegression.png')
plt.savefig(img_path)
plt.show()
# Flask读取服务器本地图片,并返回图片流给前端显示
def return_img_stream(img_local_path):
"""
工具函数:
获取本地图片流
:param img_local_path:文件单张图片的本地绝对路径
:return: 图片流
"""
import base64
img_stream = ''
with open(img_local_path, 'rb') as img_f:
img_stream = img_f.read()
img_stream = base64.b64encode(img_stream).decode()
return img_stream
################ 文件上传后,进行数据分析 ###################
@app.route('/analysis/linearRegression', methods=['POST'])
def analysis_LinearRegression():
print("分析用户上传的数据集:start")
fileName = request.form['filename']
#print(fileName)
LineProcesser(filePre = 'upload', fileName = fileName) # 提供的线性回归数据集 文件地址
print('用户分析成功')
filenamePre = os.path.splitext(fileName)[0] # 只获取名称,去掉后缀 .csv .txt等
img_path = os.path.join('results', filenamePre)
img_path_all = img_path + '.png'
img_stream = return_img_stream(img_path_all)
return render_template('LineFigure.html', img_stream=img_stream)
if __name__ == '__main__':
app.run(debug=True)
关于前端模板:
- 一个初始化界面, upload.html。包括文件上传、文件下载列表、线性回归例子、线性回归例子数据集下载。
- 一个上传成功界面。并在这个界面上去完成 线性回归预测分析。
- 图形展示界面。展示分析的结果
upload.html:
<!--上传文件-->
<!DOCTYPE html>
<!--<html lang="en">-->
<html>
<head>
<meta charset="UTF-8">
<title>file upload</title>
</head>
<h1>Upload file</h1>
<body>
<form id="form1" method="post" action="/upload" enctype="multipart/form-data">
<div>
<input id="File1" type="file" name="myfile"/> <!--后台代码中获取文件是通过form的name来标识的-->
<!-- <input type="submit">submit</input>-->
<input type="submit" value="upload" class="input button">
</div>
</form>
<h2>Download file</h2>
<ol>
{% for entry in entries %}
<li><a href='/files/{{entry}}'>{{entry}}</a>
{% endfor %}
</ol>
<h2>Example</h2>
<form action="/example/linear">
<div>
<input type="submit" value="linear regression example" class="input button"/>
</div>
</form>
<h2>Download example data</h2>
<form action="/download_example">
<div>
<input type="submit" value="Download data" class="input button"/>
</div>
</form>
</body>
</html>
uploadSucess.html:
<html>
<head>
<meta charset="UTF-8">
<title>文件上传</title>
</head>
<body>
<h1>上传成功</h1>
<a href='/'>返回主页</a>
<h1>线性回归模型预测</h1>
<body>
<!--<form id="form1" method="post" action="/linearRegressionPredection">-->
<form action="/analysis/linearRegression" method="post">
<div>
<p style="color:red"><input name="filename" value={{fileName}}></p>
<input type="submit" value="Linear Regression Analysis" class="input button">
</div>
</form>
</body>
</html>
LineFigure.html:
<!--{#展示线性回归的图片结果界面#}-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Flask Show Image</title>
</head>
<body>
<!-- <img style="width:180px" src="data:;base64,{{ img_stream }}">-->
<img src="data:;base64,{{ img_stream }}">
</body>
运行结果 如下所示:
仍然还要补足的地方:
- 多个图片的保存和展现
- 上传成功后进行分析的时候,界面逻辑展示 是否需要更改下。【如何简短地 前后端数据传递】
更多推荐
所有评论(0)