资讯动态

Python Flask MySQL Pyecharts:构建豆瓣电影数据可视化看板全流程

发布时间:2026/9/12 7:19:56 来源:尧图企业网站定制
简介一套基于PythonFlaskMySQLPyecharts构建的豆瓣电影数据可视化系统面向高校计算机专业毕业设计、课程设计以及初入数据可视化方向的开发者完整覆盖数据抓取、存储管理、后端接口与图表展示等环节。压缩包共209个文件大小约1.44MB包含12个Python核心源码、3个SQL数据库脚本、24个HTML页面、配套CSS/JS前端资源、CSV原始数据以及GIF演示动画目录结构清晰便于按模块查阅与二次开发。目前已有37人学习下载属于中等难度但完成度较高的实战项目。系统经过本地编译测试在学术评审中评分超过95分具备规范的工程结构与文档说明可直接运行体验也可作为毕业设计说明书、答辩演示和功能扩展的参考基础适合用来快速理解FlaskMySQLPyecharts的技术整合思路。1. 豆瓣数据可视化不只是画几张图而是一条完整的数据管道做课程设计或毕业设计时很多人拿到的豆瓣电影可视化项目打开源码才发现只有几个HTML模板和CSV文件后端逻辑全靠Flask硬编码。这套基于PythonFlaskMySQLPyecharts的系统好就好在把“爬取数据→存入MySQL→按维度聚合→Pyecharts渲染→Layui前端展示”这条链路完整打通了并且能直接编译运行。它解决的核心问题是如何用最小的代码量把豆瓣TOP250和若干影片信息变成可交互的图表看板而不是一台装了Python的裸机。适合有Python基础、需要快速完成毕设或想系统了解FlaskMySQL数据可视化套路的人。下面从数据库设计开始拆解你会发现它的技术难度其实集中在数据的组织方式上。2. 数据链路设计Flask路由、MySQL表结构与Pyecharts渲染方式2.1 为什么选FlaskMySQLPyecharts这个组合这套组合在高校毕业设计中使用率极高原因是“各司其职便于答辩”。Flask作为轻量级Web框架只用几个装饰器就能定义路由比Django更好解释每个请求的流向MySQL负责持久化老师一问“数据放哪里”你直接导出表结构即可不用像SQLite那样觉得“太玩具”Pyecharts的优势在于它生成的图表是JavaScript版的ECharts交互效果好且Pyecharts 1.x之后的版本支持在Python端直接拼装HTML片段不需要自己写一堆前后端交互代码。Layui在这里扮演的角色是UI框架。它的layui.css和weadmin.css提供后台管理看板的布局风格laydate用于日期筛选控件。整个系统没有复杂的前后端分离架构而是用Flask的render_template把数据渲染到HTML模板中Pyecharts生成的图表代码通过模板变量注入页面。这种“服务端渲染前端微交互”的模式对初学者非常友好出问题也好定位。2.2 数据库表怎么设计电影表、评分表、TOP250表从资源包中的豆瓣电影链接.csv和豆瓣电影TOP250链接.csv可以推断系统至少需要三张表电影基本信息表、榜单关联表区分是普通榜单还是TOP250、以及用于存储评分分布或时间趋势的统计表。常见的设计是CREATE TABLE movie ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(255) NOT NULL COMMENT 电影名称, rating FLOAT COMMENT 豆瓣评分, genres VARCHAR(255) COMMENT 类型如 剧情/犯罪, release_year INT COMMENT 上映年份, director VARCHAR(255), actors TEXT, url VARCHAR(500), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE top250 ( id INT PRIMARY KEY AUTO_INCREMENT, movie_id INT NOT NULL, rank_no INT COMMENT 榜单排名, rating_count INT COMMENT 评分人数, FOREIGN KEY (movie_id) REFERENCES movie(id) ); CREATE TABLE rating_stats ( id INT PRIMARY KEY AUTO_INCREMENT, rating_range VARCHAR(20) COMMENT 评分区间如 9.0-10, movie_count INT, update_time DATE );第一张表存电影的固有属性第二张表存TOP250的榜单特征第三张表用来支撑“评分区间分布”这类聚合图表。注意这里故意把评分和榜单拆开是为了你能分别按rating和rank_no做排序不会互相干扰。字段类型上release_year用INT而不是DATE因为豆瓣数据往往只到年份用DATE反而要处理“1900-01-01”这类脏数据。2.3 后端查询接口与JSON输出格式这里的“接口”不是RESTful API而是Flask视图函数里构造数据集合并传给模板。但为了让代码更清晰通常会先写一个db.py封装MySQL连接import pymysql def get_db(): conn pymysql.connect( host127.0.0.1, userroot, passwordyour_password, databasedouban_movie, charsetutf8mb4, cursorclasspymysql.cursors.DictCursor ) return conn视图函数里的典型写法是from flask import Flask, render_template from pyecharts.charts import Bar from pyecharts import options as opts app Flask(__name__) app.route(/) def index(): conn get_db() cursor conn.cursor() sql SELECT release_year, COUNT(*) AS cnt FROM movie WHERE release_year 2000 GROUP BY release_year ORDER BY release_year cursor.execute(sql) rows cursor.fetchall() cursor.close() conn.close() years [str(r[release_year]) for r in rows] counts [r[cnt] for r in rows] bar Bar() bar.add_xaxis(years) bar.add_yaxis(上映数量, counts) bar.set_global_opts(title_optsopts.TitleOpts(title豆瓣电影年度上映数量)) return render_template(index.html, chart_htmlbar.render_embed())注意render_embed()会输出完整的div和script标签在模板中用{{ chart_html|safe }}插入即可。这个模式下MySQL查询结果直接作为图表的数据源不需要额外构造JSON接口。如果你想让前端主动刷新数据也可以单独提供/api/chart_data路由返回JSON但毕设演示阶段用服务端渲染更稳定因为避免跨域和浏览器兼容问题。2.4 前端Layui框架如何承接图表资源包里的weadmin.css、layui.css定义了后台框架的侧边栏和卡片样式。图表不是直接铺满页面而是放在div classlayui-col-md6这类栅格中每个卡片放一个图表。比如div classlayui-row layui-col-space15 div classlayui-col-md6 div classlayui-card div classlayui-card-header评分分布/div div classlayui-card-body {{ pie_chart_html|safe }} /div /div /div /div这样做的原因是Pyecharts生成的是一个固定的宽高容器默认width: 900px。为了适配卡片你需要用bar.set_global_opts()里的width和height参数控制。bar Bar(init_optsopts.InitOpts(width100%, height400px))注意这里width100%是Pyecharts支持的最终渲染到浏览器时会根据父容器自适应比写死900px要灵活得多。如果图表没有自适应多半是因为没有加这段初始化配置而只在CSS里调div宽度。3. 核心功能实现从CSV清洗到图表渲染的完整流程3.1 数据源处理豆瓣电影链接.csv的清洗资源包里的CSV文件不是标准的数据表格式第一行通常是标题后续行可能是“电影名, 链接”这样的结构。需要先写一个清洗脚本把链接里的的id提取出来再拼接成可请求的API或页面地址。常见做法是import csv import re raw_path 豆瓣电影TOP250链接.csv cleaned [] with open(raw_path, r, encodingutf-8-sig) as f: reader csv.reader(f) header next(reader) # 跳过表头 for row in reader: if not row or len(row) 2: continue title, url row[0].strip(), row[1].strip() # 从链接里提取豆瓣ID形如 https://movie.douban.com/subject/1292052/ match re.search(r/subject/(\d)/, url) if match: movie_id match.group(1) cleaned.append({title: title, douban_id: movie_id}) print(f清洗成功共 {len(cleaned)} 条记录)清洗的逻辑很直白去掉空行提取subject后面的数字ID。因为后续如果要爬详情页douban_id才是唯一标识直接存完整URL在做外键关联时不方便。这里用utf-8-sig是因为Windows下Excel保存的CSV带BOM头不用这个编码第一列第一个字会出现\ufeff。清洗后的数据可以写回新的CSV也可以直接插入MySQL。建议插入数据库因为后续所有图表都依赖SQL查询而不是每次启动都读文件。3.2 基于Flask的动态路由与数据聚合当你有多个图表时不要在index视图里写死所有逻辑。可以拆成多个路由比如/汇总看板/rating评分TOP20条形图/genre类型占比饼图/trend年度上映趋势折线图动态路由示例app.route(/genre) def genre(): conn get_db() sql SELECT TRIM(SUBSTRING_INDEX(genres, /, 1)) AS main_genre, COUNT(*) AS cnt FROM movie WHERE genres IS NOT NULL AND genres ! GROUP BY main_genre ORDER BY cnt DESC LIMIT 10 cursor conn.cursor() cursor.execute(sql) data cursor.fetchall() pie Pie() pie.add(, [(row[main_genre], row[cnt]) for row in data]) pie.set_global_opts(title_optsopts.TitleOpts(title电影类型分布)) return render_template(genre.html, pie_htmlpie.render_embed())这里有个容易踩的坑豆瓣的genres字段在原始CSV里可能是“剧情/喜剧/爱情”这样的字符串直接用GROUP BY genres会导致每个完整组合都算一类饼图会有几十个扇区。用SUBSTRING_INDEX(genres, /, 1)截取主类型才会得到“剧情”“喜剧”“爱情”等清晰的类别。如果你在别的字段上做聚合也先想想字段是否有逗号或斜杠分隔。3.3 Pyecharts生成图表并嵌入页面Pyecharts 1.x注意不是0.5.x的API非常统一核心就是初始化图表对象设置数据设置全局配置渲染。除了柱状图和饼图本项目常用还有词云图和地理图因为豆瓣电影有地区属性。例如用Map展示不同国家和地区的电影数量from pyecharts.charts import Map app.route(/region) def region(): # 省略查库逻辑得到 [(美国, 120), (日本, 82), ...] map_chart Map() map_chart.add(地区分布, data_pairs, china) # china表示中国地图 map_chart.set_global_opts( title_optsopts.TitleOpts(title豆瓣电影地区分布), visualmap_optsopts.VisualMapOpts(max_200) ) return render_template(region.html, map_htmlmap_chart.render_embed())但这里要注意Pyecharts的Map在1.2以上版本默认需要从echarts-countries-js等包加载地图数据。如果运行后地图不显示多半是地图JS没加载。解决方法是map_chart Map().add(..., maptype中国)或者在render_embed()时指定lazy_loadTrue在前端引入对应的地图js文件。更省事的方案是干脆用柱状图代替地图用横轴放国家名视觉一样能说明问题。很多毕设项目在地图组件上卡住实际上导师不会因为地图用不了就不给分换成横向柱状图反而更清爽。3.4 前端展示grid多图布局热词里有“pyecharts grid 多个”说明很多人在研究怎样在一个页面放多个图表。Pyecharts自带的Grid组件可以组合多个图但在Flask模板中更常见的是分别生成每个图表的HTML然后用Layui栅格拼接。两种方案的取舍是用Grid()适合多个图表共用同一个X轴且图表之间有数据的联动比如“年度上映数量”和“年度评分均值”共享年份轴。用Layui栅格适合相互独立的图表比如左边饼图右边柱状图互不干扰。实际项目中我建议优先选Layui栅格因为Pyecharts的Grid在组合饼图和地图时布局很难控制需要手动设置grid_opts的百分比位置。例如grid Grid() grid.add(bar, grid_optsopts.GridOpts(pos_top55%)) grid.add(pie, grid_optsopts.GridOpts(pos_bottom55%))grid_opts里的pos_top、pos_bottom是以容器高度为基准的百分比。调起来非常耗时。而用HTML栅格每个图表的div宽度自己定图表内部用init_opts去适应半小时就能调完一张看板。Layui栅格的使用也很简单div classlayui-row div classlayui-col-md6 layui-col-md-offset0 {{ bar_html|safe }} /div div classlayui-col-md6 {{ pie_html|safe }} /div /div注意在Flask的render_template中变量名要保持一致并且用|safe过滤。因为Pyecharts生成的HTML里包含script标签Jinja2默认会转义不加|safe的话浏览器会直接显示源码。4. 部署与排错让系统在本地跑起来4.1 环境安装与依赖版本资源包没有给出requirements.txt但按技术栈推断核心依赖包括包名版本建议说明Flask2.x2.0以上插件兼容性更好PyMySQL1.x替代MySQLdb兼容Python3Pyecharts1.9.1注意不是0.5.xAPI完全不同pandas1.x如果CSV清洗用pandasrequests2.x如果还要爬详情页安装命令pip install flask pymysql pyecharts pandas requests这里有一个坑如果你之前装过pyecharts 0.5.x必须先卸载再装1.x。两个版本的导入方式完全不同——0.5用from pyecharts import Bar1.x用from pyecharts.charts import Bar。如果运行报ImportError: cannot import name Bar from pyecharts说明你的环境还是老版本。安装后验证python -c from pyecharts.charts import Bar; print(ok)建议用虚拟环境python -m venv venv source venv/bin/activate # Windows下是 venv\Scripts\activate4.2 初始化MySQL数据库与导入数据首先在MySQL中创建数据库CREATE DATABASE IF NOT EXISTS douban_movie DEFAULT CHARSET utf8mb4; USE douban_movie;然后执行表结构脚本。接着将CSV数据导入。可以使用pandas快速导入import pandas as pd from sqlalchemy import create_engine df pd.read_csv(豆瓣电影TOP250链接.csv, encodingutf-8-sig) # 注意清洗逻辑确保字段名和表结构一致 engine create_engine(mysqlpymysql://root:your_password127.0.0.1:3306/douban_movie?charsetutf8mb4) df.to_sql(movie, conengine, if_existsappend, indexFalse)如果你的表中已经有数据if_existsappend不会清空旧表。如果重复运行导致数据翻倍可以把参数改成replace但replace会先drop表再建表表结构会丢失。更好的做法是先DELETE FROM movie再append。导入后检查行数mysql SELECT COUNT(*) FROM movie;如果行数与CSV行数一致基本没问题。4.3 常见运行报错与解决办法报错1ModuleNotFoundError: No module named pymysql说明pymysql没安装执行pip install pymysql。如果你用MySQLdb那还需要装mysqlclient但Windows下mysqlclient编译困难改用pymysql更省事。报错2pymysql.err.OperationalError: (2003, Cant connect to MySQL server on 127.0.0.1)MySQL服务没启动。Windows下开始菜单搜“服务”找到MySQL80并启动。Linux下systemctl start mysqld。另外检查数据库端口如果MySQL端口改成3307连接参数也要改。报错3UnicodeDecodeError: utf-8 codec cant decode byte 0xc0 in position 0CSV文件编码不是UTF-8可能是GBK。读取时指定编码pd.read_csv(豆瓣电影链接.csv, encodinggbk)或者用encodinglatin1先让程序跑通再找具体问题。报错4Pyecharts图表不显示页面空白先打开浏览器开发者工具F12看Console有没有报JavaScript错误。常见是Jinja2模板里忘了加|safe或者Pyecharts版本过低。如果Console提示Uncaught ReferenceError: echarts is not defined说明ECharts的js库没引入。Pyecharts在render_embed()时应该自动加载但如果你在模板中手写了script src/static/echarts.min.js/script而实际文件不存在就会报这个错。建议直接使用render_embed()输出完整HTML不要手动引入额外的js。5. 进阶技巧把可视化系统扩展到生产级5.1 用Redis缓存高频查询如果你的系统演示时频繁点击刷新每次都要查询MySQL并重新计算图表。数据量小时无所谓但TOP250数据加上评论数聚合查询可能在几百毫秒。可以用Redis做查询缓存把SQL结果缓存10分钟。import redis import json cache redis.Redis(host127.0.0.1, port6379, db0) def get_rating_distribution(): cache_key rating_dist cached cache.get(cache_key) if cached: return json.loads(cached) conn get_db() cursor conn.cursor() cursor.execute(SELECT ...) result cursor.fetchall() cursor.close() conn.close() cache.setex(cache_key, 600, json.dumps(result, defaultstr)) return result注意json.dumps时MySQL返回的Decimal和date类型不能被直接序列化需要加defaultstr参数或者先将数据转换为float和str。这样处理之后路由函数里直接调用get_rating_distribution()图表数据不会因为缓存而缺失因为缓存的是聚合结果不是HTML。5.2 定时爬取与增量更新毕业设计不需要真的爬豆瓣但你可以做模拟增量更新用一个定时任务每天从CSV重新读取数据按douban_id去重更新数据库。最简单的方法是使用APScheduler在Flask启动时启动一个后台任务from apscheduler.schedulers.background import BackgroundScheduler def update_data(): # 这里执行清洗和插入逻辑 pass scheduler BackgroundScheduler() scheduler.add_job(update_data, cron, hour3, minute0) scheduler.start()注意在开发模式下Flask的debugTrue时代码重载会造成调度器启动两次。解决办法是加一个环境变量检查if os.environ.get(WERKZEUG_RUN_MAIN) true: scheduler.start()或者直接用app.run(debugFalse)。演示时debug关闭即可免得双定时任务导致数据重复。5.3 验证系统正确性你不可能手动检查每一部电影的数据但可以用SQL做校验。比如验证评分最高的电影是否是预期的那部SELECT title, rating FROM movie ORDER BY rating DESC LIMIT 1;再验证图表上的统计值是否和SQL一致。例如年份分布图先跑SQLSELECT release_year, COUNT(*) FROM movie GROUP BY release_year ORDER BY release_year;然后用Python脚本计算import pymysql conn get_db() cursor conn.cursor() cursor.execute(SELECT release_year, COUNT(*) FROM movie GROUP BY release_year) rows cursor.fetchall() years, counts zip(*rows) assert sum(counts) cursor.execute(SELECT COUNT(*) FROM movie)如果总和保持一致说明图表数据源没有缺失。如果发现年份有NULL可能清洗时没有处理空字符串需要在SQL中加WHERE release_year IS NOT NULL。最后在部署时用一个run.py统一启动export FLASK_APPrun.py flask run --host0.0.0.0 --port5000这样局域网内的其他电脑也能通过http://你的IP:5000访问看板答辩时用一台笔记本演示导师可以从自己电脑打开看效果比截图加分很多。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价