Python 从入门到实战(十二):Flask Web 开发(把学生成绩系统变成在线应用)

文章目录
欢迎回到「Python 从入门到实战」系列专栏。上一篇咱们用 Matplotlib 和 Seaborn 实现了学生成绩的可视化,生成了美观的图表报告,但这些功能都局限在本地 —— 只有在自己的电脑上才能运行,老师和学生无法通过浏览器远程访问。
今天咱们要学 Python 的轻量级 Web 框架 ——Flask。它就像一座 “桥梁”,能把咱们之前写的本地学生成绩管理、数据可视化逻辑,快速升级为在线 Web 应用,让多人通过浏览器访问、查询成绩、查看可视化报告。咱们会从 Flask 基础入手,逐步实现 “首页展示→学生列表→成绩查询→可视化报告” 的完整功能,最终打造一个能实际使用的 “在线学生成绩管理系统”。
一、为什么选择 Flask?Web 开发的 “入门钥匙”
在学具体操作前,先搞懂 “为什么要做 Web 应用” 和 “为什么选 Flask”:
- 本地项目的局限:之前的学生系统只能在自己电脑上运行,无法共享;数据和图表都存在本地文件,多人协作不方便;
- Web 应用的优势:只要有浏览器(电脑、手机都可以),就能访问系统;数据集中存储,多人实时查看最新成绩;
- Flask 的特点:轻量级、易上手,不需要复杂配置,适合快速把本地项目升级为 Web 应用;支持整合 Pandas、Matplotlib 等库,完美衔接咱们之前的代码。
简单说:Flask 能以最低的学习成本,让你的本地工具变成 “人人可用的在线服务”。
二、Flask 基础:从 “Hello World” 到 Web 服务
首先,咱们先搞定 Flask 的安装和核心概念,用 3 行代码跑通第一个 Web 服务,建立基础认知。
1. 安装 Flask 与依赖
Flask 是第三方库,需要先安装。另外,咱们还要用到 Pandas(处理数据)、Matplotlib(生成图表),所以一起安装:
bash
# 安装Flask核心库 pip install flask # 安装依赖(数据处理和可视化) pip install pandas matplotlib seaborn 安装完成后,验证是否成功:打开 Python 终端,输入import flask,没有报错就说明安装成功。
2. 核心概念:Flask 的 “三大件”
Flask 有三个核心概念,搞懂它们就能入门 Web 开发:
- 路由(Route):URL 地址与 Python 函数的映射关系。比如访问
http://localhost:5000/(首页),对应执行index()函数; - 视图函数(View Function):处理路由请求的 Python 函数,负责逻辑计算(比如读取数据、生成图表),并返回响应(比如 HTML 页面、字符串);
- 模板(Template):用于渲染 Web 页面的 HTML 文件,通过 Jinja2 模板引擎,能把 Python 变量(比如学生数据)动态插入到 HTML 中,让页面 “活” 起来。
3. 实战 1:跑通第一个 Flask 应用(Hello World)
创建一个名为app.py的文件(Flask 项目的主程序文件),写入以下代码:
python
# app.py(Flask主程序)from flask import Flask # 1. 初始化Flask应用(__name__表示当前文件作为应用入口) app = Flask(__name__)# 2. 定义路由:访问根URL(http://localhost:5000/)时,执行index()函数@app.route('/')defindex():# 3. 视图函数:返回响应内容(这里是简单的字符串)return"<h1>欢迎访问学生成绩管理系统!</h1><p>这是用Flask开发的Web应用</p>"# 4. 启动Web服务(只有直接运行app.py时才执行)if __name__ =='__main__':# debug=True:开启调试模式,修改代码后自动重启服务,方便开发 app.run(debug=True)运行步骤:
- 打开终端,进入
app.py所在的文件夹; - 打开浏览器,访问
http://localhost:5000/,就能看到页面显示:- 大标题 “欢迎访问学生成绩管理系统!”
- 副标题 “这是用 Flask 开发的 Web 应用”
执行命令python app.py,看到以下输出:plaintext
* Serving Flask app 'app' * Debug mode: on * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) 关键说明:
- 调试模式:
debug=True非常重要,开发时开启,修改代码后不用手动重启服务; - URL 与路由:
@app.route('/')对应根 URL,如果你定义@app.route('/student'),访问http://localhost:5000/student才会触发对应的视图函数; - 响应内容:目前返回的是简单 HTML 字符串,后面会用模板返回更复杂的页面。
三、Flask 进阶:整合学生数据与 Web 页面
单纯的字符串响应不够美观,也无法展示复杂的学生数据。接下来,咱们用模板渲染 HTML 页面,把之前的学生 CSV 数据展示成 Web 表格,实现 “本地数据→Web 表格” 的跨越。
1. 项目结构:规范文件组织
随着功能增加,文件需要按规则存放,否则会混乱。创建以下项目结构(跟着手动建文件夹和文件):
plaintext
student_web/ # 项目根文件夹 ├── app.py # 主程序文件(Flask核心逻辑) ├── students_data.csv # 学生数据文件(之前生成的) ├── templates/ # 存放HTML模板的文件夹 │ ├── base.html # 基础模板(所有页面继承这个) │ ├── index.html # 首页 │ └── student_list.html # 学生列表页 └── static/ # 存放静态文件(图片、CSS、JS) └── images/ # 存放可视化图表的文件夹 2. 模板继承:减少重复 HTML 代码
Web 开发中,多个页面(如首页、学生列表页)会有相同的头部(导航栏)和底部(版权信息),用模板继承可以避免重复写这些代码。
步骤 1:创建基础模板templates/base.html
html
<!-- templates/base.html --><!DOCTYPEhtml><htmllang="zh-CN"><head><metacharset="UTF-8"><title>{% block title %}学生成绩管理系统{% endblock %}</title><!-- 引入简单的CSS,让页面更美观(Bootstrap,不用自己写CSS) --><linkhref="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"rel="stylesheet"></head><body><!-- 导航栏(所有页面都有) --><navclass="navbar navbar-expand-lg navbar-dark bg-primary"><divclass="container"><aclass="navbar-brand"href="/">学生成绩系统</a><divclass="collapse navbar-collapse"><ulclass="navbar-nav"><liclass="nav-item"><aclass="nav-link"href="/">首页</a></li><liclass="nav-item"><aclass="nav-link"href="/student/list">学生列表</a></li><liclass="nav-item"><aclass="nav-link"href="/report">可视化报告</a></li></ul></div></div></nav><!-- 内容区域(子页面在这里填充不同内容) --><divclass="container mt-4"> {% block content %}{% endblock %} </div><!-- 底部信息(所有页面都有) --><footerclass="mt-5 py-3 bg-light text-center"><divclass="container"><pclass="mb-0">© 2024 学生成绩管理系统 | 用Flask开发</p></div></footer><!-- Bootstrap的JS(可选,用于交互) --><scriptsrc="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script></body></html>模板继承说明:
{% block title %}:子页面可以替换这里的标题;{% block content %}:子页面的核心内容放在这里;- 引入 Bootstrap:这是一个免费的 CSS/JS 库,不用自己写样式就能让页面美观,新手友好。
3. 首页开发:templates/index.html
首页继承base.html,展示系统简介和快速入口:
html
<!-- templates/index.html --> {% extends "base.html" %} <!-- 替换标题 --> {% block title %}首页 - 学生成绩管理系统{% endblock %} <!-- 填充内容区域 --> {% block content %} <divclass="jumbotron bg-light p-5 rounded-3"><h1class="display-5 fw-bold">欢迎使用学生成绩管理系统</h1><pclass="lead mt-3">本系统基于Python Flask开发,整合Pandas数据处理和Matplotlib可视化,支持以下功能:</p><ulclass="list-group list-group-flush mt-3 w-50"><liclass="list-group-item">查看所有学生的成绩列表</li><liclass="list-group-item">查询单个学生的详细成绩</li><liclass="list-group-item">查看各科成绩的可视化报告</li></ul><divclass="mt-4"><ahref="/student/list"class="btn btn-primary me-3">进入学生列表</a><ahref="/report"class="btn btn-success">查看可视化报告</a></div></div> {% endblock %} 4. 学生列表页:展示 Pandas 处理的数据
接下来,在app.py中添加路由和视图函数,用 Pandas 读取students_data.csv,传递到模板,渲染成 Web 表格。
步骤 1:修改app.py,添加学生列表路由
python
# app.py(新增代码,放在index路由后面)import pandas as pd from flask import Flask, render_template # 新增render_template,用于渲染模板 app = Flask(__name__)# 首页路由(之前的代码)@app.route('/')defindex():# 渲染index.html模板,不再返回字符串return render_template('index.html')# 新增:学生列表路由@app.route('/student/list')defstudent_list():# 1. 用Pandas读取学生数据(和之前本地处理一样) df = pd.read_csv('students_data.csv', encoding='utf-8', dtype={'course_score':int})# 添加成绩等级列(复用之前的逻辑) df['grade']= df['course_score'].apply(lambda x:'A级(90+)'if x >=90else('B级(80-89)'if x >=80else'C级(<80)'))# 2. 将DataFrame转换为字典列表,方便模板渲染(Flask不支持直接传递DataFrame) student_data = df.to_dict('records')# 每一行是一个字典,键是列名# 3. 计算统计信息(传递到模板展示) total_students = df['name'].nunique()# 总学生数 total_courses = df['course_name'].nunique()# 总课程数# 4. 渲染student_list.html,传递数据到模板return render_template('student_list.html', students=student_data,# 学生数据列表 total_students=total_students,# 总学生数 total_courses=total_courses # 总课程数)# 启动服务(之前的代码)if __name__ =='__main__': app.run(debug=True)步骤 2:创建学生列表模板templates/student_list.html
html
<!-- templates/student_list.html --> {% extends "base.html" %} {% block title %}学生列表 - 学生成绩管理系统{% endblock %} {% block content %} <divclass="d-flex justify-content-between align-items-center mb-4"><h2>学生成绩列表</h2><pclass="text-muted">总学生数:{{ total_students }} 人 | 总课程数:{{ total_courses }} 门</p></div><!-- 学生成绩表格 --><tableclass="table table-striped table-hover"><theadclass="table-dark"><tr><th>学生姓名</th><th>年龄</th><th>课程名称</th><th>成绩(分)</th><th>成绩等级</th></tr></thead><tbody><!-- 循环渲染学生数据 --> {% for student in students %} <tr><td>{{ student.name }}</td><td>{{ student.age }}</td><td>{{ student.course_name }}</td><td>{{ student.course_score }}</td><td><!-- 根据等级显示不同颜色 --> {% if student.grade == 'A级(90+)' %} <spanclass="badge bg-success">{{ student.grade }}</span> {% elif student.grade == 'B级(80-89)' %} <spanclass="badge bg-warning">{{ student.grade }}</span> {% else %} <spanclass="badge bg-danger">{{ student.grade }}</span> {% endif %} </td></tr> {% endfor %} </tbody></table> {% endblock %} 运行测试:
- 确保
students_data.csv在项目根目录; - 运行
app.py,访问http://localhost:5000/student/list,就能看到:- 顶部显示总学生数和课程数;
- 美观的表格,展示每个学生的姓名、年龄、课程、成绩;
- 成绩等级用不同颜色的标签显示(A 级绿色、B 级黄色、C 级红色)。
四、Flask + 可视化:Web 页面展示图表
上一篇咱们生成了柱状图、饼图,但只能存在本地文件。现在要让这些图表在 Web 页面展示,步骤是:
- 在 Flask 中生成图表,保存到
static/images/文件夹; - 在模板中引用静态文件夹的图片路径,渲染到页面。
1. 修改app.py,添加可视化报告路由
python
# app.py(新增代码,放在student_list路由后面)import matplotlib.pyplot as plt import seaborn as sns import os # 配置中文字体(避免图表中文乱码) plt.rcParams['font.sans-serif']=['SimHei','PingFang SC'] plt.rcParams['axes.unicode_minus']=False sns.set_style("whitegrid")# 确保static/images文件夹存在(不存在则创建)ifnot os.path.exists('static/images'): os.makedirs('static/images')# 新增:可视化报告路由@app.route('/report')defreport():# 1. 读取数据(复用之前的逻辑) df = pd.read_csv('students_data.csv', encoding='utf-8', dtype={'course_score':int}) df['grade']= df['course_score'].apply(lambda x:'A级(90+)'if x >=90else('B级(80-89)'if x >=80else'C级(<80)'))# 2. 生成各科平均分柱状图 course_avg = df.groupby('course_name')['course_score'].mean().round(1) fig, ax = plt.subplots(figsize=(6,4), dpi=100) bars = ax.bar(course_avg.index, course_avg.values, color='skyblue', edgecolor='black') ax.set_title('各科平均分对比', fontsize=12) ax.set_xlabel('课程名称') ax.set_ylabel('平均分(分)') ax.set_ylim(80,90)# 添加数值标签for bar in bars: height = bar.get_height() ax.text(bar.get_x()+bar.get_width()/2, height+0.5,str(height), ha='center', va='bottom')# 保存图表到static/images plt.savefig('static/images/course_avg.png', bbox_inches='tight') plt.close()# 关闭图表,释放内存# 3. 生成成绩等级分布饼图 grade_count = df['grade'].value_counts() fig, ax = plt.subplots(figsize=(6,6), dpi=100) colors =['#FF6B6B','#4ECDC4','#45B7D1'] wedges, texts, autotexts = ax.pie( grade_count.values, labels=grade_count.index, autopct='%1.1f%%', startangle=90, colors=colors, explode=(0.05,0,0)) ax.set_title('成绩等级分布', fontsize=12)for autotext in autotexts: autotext.set_color('white') plt.savefig('static/images/grade_pie.png', bbox_inches='tight') plt.close()# 4. 渲染报告模板,传递图表路径return render_template('report.html')2. 创建可视化报告模板templates/report.html
html
<!-- templates/report.html --> {% extends "base.html" %} {% block title %}可视化报告 - 学生成绩管理系统{% endblock %} {% block content %} <h2>学生成绩可视化报告</h2><pclass="text-muted mb-4">基于学生成绩数据生成的图表分析</p><!-- 图表展示(2行1列,响应式布局) --><divclass="row g-4"><!-- 柱状图:各科平均分 --><divclass="col-md-6"><divclass="card shadow-sm"><divclass="card-body"><h5class="card-title">各科平均分对比</h5><!-- 引用静态文件夹的图片:url_for('static', filename='图片路径') --><imgsrc="{{ url_for('static', filename='images/course_avg.png') }}"alt="各科平均分柱状图"class="img-fluid"></div></div></div><!-- 饼图:成绩等级分布 --><divclass="col-md-6"><divclass="card shadow-sm"><divclass="card-body"><h5class="card-title">成绩等级分布</h5><imgsrc="{{ url_for('static', filename='images/grade_pie.png') }}"alt="成绩等级饼图"class="img-fluid"></div></div></div></div> {% endblock %} 关键说明:
url_for('static', filename='images/course_avg.png'):Flask 的辅助函数,自动生成静态文件的 URL,避免手动写路径出错;img-fluid:Bootstrap 的类,让图片自适应页面宽度,在手机上也能正常显示;card shadow-sm:给图表加卡片样式和阴影,让页面更有层次感。
运行测试:
访问http://localhost:5000/report,就能看到两个图表在 Web 页面上展示,和之前本地生成的一样,但现在任何人都能通过浏览器访问。
五、交互功能:学生成绩查询
只展示还不够,还要支持 “用户输入姓名,查询该学生的成绩”。这需要用到 Flask 的表单处理,接收用户输入的姓名,查询数据后返回结果。
1. 修改app.py,添加查询路由
python
# app.py(新增代码,放在report路由后面)from flask import request # 新增request对象,用于接收请求数据# 新增:学生查询路由(支持GET和POST请求)@app.route('/student/search', methods=['GET','POST'])defstudent_search():# 如果是GET请求(用户刚访问页面),返回空结果的表单if request.method =='GET':return render_template('student_search.html')# 如果是POST请求(用户提交表单),处理查询elif request.method =='POST':# 1. 获取用户输入的姓名(从表单中取name为'student_name'的值) student_name = request.form.get('student_name','').strip()# 2. 验证输入(不能为空)ifnot student_name:return render_template('student_search.html', error='请输入学生姓名!')# 3. 读取数据并查询 df = pd.read_csv('students_data.csv', encoding='utf-8', dtype={'course_score':int}) df['grade']= df['course_score'].apply(lambda x:'A级(90+)'if x >=90else('B级(80-89)'if x >=80else'C级(<80)'))# 筛选该学生的数据 student_df = df[df['name']== student_name]# 4. 处理查询结果if student_df.empty:# 没有找到该学生return render_template('student_search.html', error=f'未找到名为“{student_name}”的学生', input_name=student_name # 回显用户输入的姓名)else:# 找到学生,转换为字典列表 student_data = student_df.to_dict('records')# 计算该学生的总分和平均分 total_score = student_df['course_score'].sum() avg_score = student_df['course_score'].mean().round(1)return render_template('student_search.html', student=student_data,# 学生成绩数据 total_score=total_score,# 总分 avg_score=avg_score,# 平均分 input_name=student_name # 回显用户输入的姓名)2. 创建查询模板templates/student_search.html
html
<!-- templates/student_search.html --> {% extends "base.html" %} {% block title %}学生查询 - 学生成绩管理系统{% endblock %} {% block content %} <divclass="row justify-content-center"><divclass="col-md-8"><h2>学生成绩查询</h2><!-- 成绩查询表单 --><formmethod="POST"class="mt-4"><divclass="input-group mb-3"><inputtype="text"name="student_name"class="form-control"placeholder="请输入学生姓名(如:小明)"value="{{ input_name if input_name else '' }}"><!-- 回显输入 --><buttonclass="btn btn-primary"type="submit">查询</button></div><!-- 显示错误信息(如果有) --> {% if error %} <divclass="alert alert-danger"role="alert"> {{ error }} </div> {% endif %} </form><!-- 显示查询结果(如果有) --> {% if student %} <divclass="card mt-4"><divclass="card-header bg-primary text-white"><h5class="mb-0"> {{ input_name }} 的成绩详情 <spanclass="float-end">总分:{{ total_score }} | 平均分:{{ avg_score }}</span></h5></div><divclass="card-body"><tableclass="table table-sm mb-0"><thead><tr><th>课程名称</th><th>成绩(分)</th><th>成绩等级</th></tr></thead><tbody> {% for item in student %} <tr><td>{{ item.course_name }}</td><td>{{ item.course_score }}</td><td> {% if item.grade == 'A级(90+)' %} <spanclass="badge bg-success">{{ item.grade }}</span> {% elif item.grade == 'B级(80-89)' %} <spanclass="badge bg-warning">{{ item.grade }}</span> {% else %} <spanclass="badge bg-danger">{{ item.grade }}</span> {% endif %} </td></tr> {% endfor %} </tbody></table></div></div> {% endif %} </div></div> {% endblock %} 关键功能说明:
- 请求方法:
methods=['GET', 'POST']表示该路由支持两种请求:GET(访问页面)、POST(提交表单); - 表单回显:
value="{{ input_name if input_name else '' }}"让用户查询失败时,输入框保留之前的内容,不用重新输入; - 错误提示:用 Bootstrap 的
alert-danger类显示错误信息(如 “未找到学生”),用户体验更好。
运行测试:
- 访问
http://localhost:5000/student/search; - 输入 “小明”,点击查询,会显示小明的课程、成绩、总分和平均分;
- 输入 “不存在的名字”,会显示错误提示 “未找到该学生”。
六、完整项目实战:在线学生成绩管理系统
现在,咱们的系统已经有了 4 个核心功能:首页、学生列表、可视化报告、成绩查询。最后,把导航栏和所有功能串联起来,确保用户能通过导航在各个页面间切换(之前的base.html已经做好了导航栏)。
最终项目结构回顾
plaintext
student_web/ ├── app.py # 主程序(路由、数据处理、图表生成) ├── students_data.csv # 学生数据(数据源) ├── templates/ │ ├── base.html # 基础模板(导航栏、底部) │ ├── index.html # 首页(系统简介) │ ├── student_list.html # 学生列表(所有成绩表格) │ ├── student_search.html # 成绩查询(交互功能) │ └── report.html # 可视化报告(图表展示) └── static/ └── images/ ├── course_avg.png # 柱状图(自动生成) └── grade_pie.png # 饼图(自动生成) 运行完整系统的步骤
- 确保所有文件按上述结构存放;
- 安装所有依赖(flask、pandas、matplotlib、seaborn);
- 运行
python app.py,打开浏览器访问http://localhost:5000/; - 通过导航栏切换 “首页→学生列表→成绩查询→可视化报告”,测试所有功能。
七、新手必踩的 5 个坑:避坑指南
Flask 开发虽然简单,但新手容易在 “路径”“请求处理”“静态文件” 上踩坑,总结如下:
坑 1:模板路径错误(找不到 HTML 文件)
python
# 错误示例:模板不在templates文件夹,或文件名写错return render_template('studentlist.html')# 错误:正确文件名是student_list.html解决:
- 模板必须放在
templates文件夹(名字不能错,全小写); - 渲染时文件名必须和实际一致,包括下划线、大小写(Flask 区分大小写)。
坑 2:静态文件引用错误(图表不显示)
html
<!-- 错误示例:直接写本地路径,Flask无法识别 --><imgsrc="static/images/course_avg.png">解决:用url_for生成静态文件 URL:
html
<imgsrc="{{ url_for('static', filename='images/course_avg.png') }}">坑 3:请求方法错误(表单提交没反应)
python
# 错误示例:路由只支持GET,无法处理POST请求@app.route('/student/search')# 默认methods=['GET']defstudent_search():if request.method =='POST':# 永远不会执行pass解决:路由必须显式指定支持 POST:
python
@app.route('/student/search', methods=['GET','POST'])坑 4:数据读取路径错误(找不到 CSV 文件)
python
# 错误示例:CSV文件不在项目根目录,Pandas找不到 df = pd.read_csv('data/students_data.csv')# 错误:路径不对解决:
- 确保
students_data.csv在app.py所在的根目录;
或用绝对路径(不推荐,换电脑会失效):python
import os csv_path = os.path.join(os.path.dirname(__file__),'students_data.csv') df = pd.read_csv(csv_path)坑 5:调试模式关闭(修改代码不生效)
python
# 错误示例:debug=False,修改代码后需要手动重启服务 app.run(debug=False)解决:开发时务必开启调试模式:
python
app.run(debug=True)注意:生产环境(给别人正式使用时)要关闭debug=True,否则有安全风险。
八、小结与下一篇预告
这篇你学到了什么?
- Flask 基础:理解路由、视图函数、模板的核心概念,跑通 Web 服务;
- 模板开发:用 Jinja2 模板继承减少重复代码,引入 Bootstrap 美化页面;
- 数据整合:把 Pandas 本地数据处理逻辑迁移到 Flask,渲染成 Web 表格;
- 可视化 Web 化:在 Flask 中生成图表,保存到静态文件夹,在页面展示;
- 交互功能:用 request 处理表单提交,实现学生成绩查询,提升用户体验;
- 项目实战:构建完整的在线学生成绩管理系统,掌握 Web 项目的文件结构和开发流程。
下一篇预告
今天的 Flask 应用已经能通过浏览器访问,但数据还是存在本地 CSV 文件中,多人同时修改会冲突,而且无法持久化存储新数据(比如新增学生)。下一篇咱们会学 Python 的数据库操作,用SQLite(轻量级数据库,无需安装)替换 CSV 文件,实现数据的增删改查,让 Web 应用真正支持多人协作,成为生产级别的系统。
如果这篇内容帮你搭建了第一个 Flask 应用,欢迎在评论区分享你的系统截图或遇到的问题,咱们一起交流进步~