Python+Flask框架:一个简单的web-app

我想用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应用的本质就是:

  1. 浏览器发送一个HTTP请求;
  2. 服务器收到请求,生成一个HTML文档;
  3. 服务器把HTML文档作为HTTP响应的Body发送给浏览器;
  4. 浏览器收到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框架还有:

  • Django:全能型Web框架;
  • web.py:一个小巧的Web框架;
  • Bottle:和Flask类似的Web框架;
  • Tornado:Facebook的开源异步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目录下,templatesapp.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 【查看这一部分去解决的 errorReshape 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>

界面效果如下:
在这里插入图片描述

  1. 我们在 web app 程序中有 return render_template(‘uploadIndex.html’, entries = entries) 。后端通过 entries 传递给了前端 ,且利用 for entry in entries 循环语句,显示 {{entry}} 即upload文件夹下的文件和文件夹。

  2. 点击 upload 进行提交, submit 让表单去访问 “/upload” ,完成文件上传的功能。 即访问 @app.route(’/upload’, methods=[‘POST’])

  3. 从 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>

运行结果 如下所示:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

仍然还要补足的地方:

  • 多个图片的保存和展现
  • 上传成功后进行分析的时候,界面逻辑展示 是否需要更改下。【如何简短地 前后端数据传递】
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值