ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Django疫情数据可视化毕设实战:从MySQL到ECharts的完整实现

Django疫情数据可视化毕设实战:从MySQL到ECharts的完整实现 简介面向需要完成毕业设计或系统学习Web开发的计算机专业学生压缩包内是一套基于Python与Django框架的疫情数据可视化分析系统覆盖数据录入、管理、统计与图表展示等核心功能支持直接运行与二次扩展。资源共含745个文件总大小15.81MB文件类型以Python源码、Vue组件、JavaScript脚本、HTML页面、CSS样式及SQL数据库文件为主。后端代码配合MySQL实现数据存储与查询前端借助Vue、JavaScript和SVG资源完成交互页面与可视化展示多个bat脚本则为安装与启动提供了简便入口。压缩包内另附配置教程与开发说明文档从环境搭建、系统运行到原理实现均有讲解有助于快速理解Django项目结构及Pandas、NumPy在数据处理中的实际应用。目前已有61人学习对毕业设计答辩、项目实战练手与简历项目积累都具有不错的参考价值。1. 打开这份 Django 疫情可视化源码前先想清楚毕设怎么做每年三、四月后台最常出现的提问就是数据可视化的毕设数据从哪里来、图表怎么做、Django 怎么和 MySQL 连起来这三关里至少翻一关。这份 Python Django 的疫情数据可视化分析系统就是来解决这套组合拳的。源码里内置了处理好的疫情历史数据前端用 ECharts 画地图和趋势图后端用 Django ORM 操作 MySQLzip 里还附带配置教程和开发说明照着走就能在本机完整跑起来。接下来我按拿到源码后的实际顺序拆一遍先看架构再配环境然后跑通数据和页面最后把最容易翻车的几个点提前摆到台面上。适合正在赶毕设的学生、做期末大作业的同学以及想快速体验 Django 数据可视化完整链路的人。2. 技术选型与源码结构Django MySQL ECharts 这套组合怎么组织疫情可视化这个题目核心词有两个后端框架和数据可视化。选型是否合理直接决定了后面开发顺畅程度和答辩时能讲出多少东西。2.1 为什么选 Django MySQL ECharts答辩有得讲开发不踩空Django 属于重量级框架自带 Admin 后台、ORM 数据库映射、表单校验和 CSRF 防护。对毕设场景来说它的优势在于「该有的都有」登录注册、后台管理、数据库操作都不需要从零造轮子。中期检查被问到系统架构你可以很自然地讲出 MTV 分层每个模块的职责边界是清晰的这比 Flask 拼装出来的项目在评分时更有话可说。MySQL 在存储层配合 Django ORM基本不需要手写原生 SQL对于平时不常写复杂查询的同学来说很友好。疫情数据是典型的时间序列加空间维度数据按省份、日期做聚合统计MySQL 的索引和分组查询完全扛得住数据量在十万级以内时性能不会成为瓶颈。数据可视化前端ECharts 是当前毕业设计里出现频率最高的方案。它对中文地图支持完善疫情按省份展示这种需求用 ECharts 的 map 类型配合 GeoJSON 就能实现折线图、柱状图、饼图的配置项也直观照着官方示例替换数据源就能出图。相比 d3 的高门槛和 Highcharts 的授权问题ECharts 的开源免费与上手成本低让它在毕设场景里几乎没有替代品。这套组合还有一层隐藏优势市场上同类资源最多。遇到「Django 查数据传给前端」「ECharts 地图数据格式怎么组织」这类问题搜索引擎随便一翻就有大量案例不至于困在某个细节上两周出不来。2.2 解压 zip 后的目录结构先认文件再动代码拿到 zip 包后先不要急着 runserver。打开目录看一遍结构知道每个文件是干什么的后面改配置时才不会迷路。源码包里的 Django 项目结构大致是这样组织的epidemic_analysis/ ├── manage.py # Django 管理入口 ├── requirements.txt # 第三方依赖清单 ├── config/ # 项目配置模块 │ ├── settings.py # 全局配置 │ ├── urls.py # 根 URL 路由 │ └── wsgi.py ├── apps/ │ ├── epidemic/ # 疫情数据核心应用 │ │ ├── models.py # 数据模型定义 │ │ ├── views.py # 视图函数 / 接口 │ │ ├── urls.py # 应用级路由 │ │ └── services.py # 数据抓取与导入服务 │ └── user/ # 用户登录注册模块 ├── templates/ # HTML 模板 ├── static/ │ ├── css/ js/ │ └── data/ # 疫情历史数据 / 中国地图 GeoJSON └── sql/ # 初始化 SQL 脚本几个关键位置的说明路径作用通常需要动吗config/settings.py数据库连接、静态文件、时区配置需要改成自己的 MySQL 账号密码apps/epidemic/models.py疫情数据表结构一般不动除非要加字段static/data/前端图表要用的数据和地图 GeoJSON可选换成你要展示的区域sql/初始化数据脚本用命令导入属于捷径requirements.txtPython 依赖版本清单严格按它安装第一次看源码我建议按这个顺序读先打开 config/urls.py 看根路由挂载了哪些 app再进 apps/epidemic/models.py 看表结构最后回到 views.py 看接口怎么查数据。把这三层串起来整个项目的请求链路就清楚了。2.3 数据模型设计核心表结构与建表语句疫情可视化系统的数据模型是整个项目的地基。打开 apps/epidemic/models.py核心模型长这样from django.db import models class EpidemicRecord(models.Model): province models.CharField(省份, max_length50, db_indexTrue) city models.CharField(城市, max_length50, blankTrue) confirmed models.IntegerField(累计确诊, default0) suspected models.IntegerField(疑似, default0) cured models.IntegerField(治愈, default0) dead models.IntegerField(死亡, default0) date models.DateField(统计日期, db_indexTrue) class Meta: db_table epidemic_record unique_together ((province, city, date),) def __str__(self): return f{self.province} {self.date} {self.confirmed}这段模型的设计逻辑province 和 city 定位区域层级confirmed、suspected、cured、dead 对应四个核心指标date 标记统计日期。db_indexTrue 是为 province 和 date 加上数据库索引后续频繁按省份筛选、按日期排序的查询会走索引而不是全表扫描。unique_together是最值得注意的设计。它声明了「省份 城市 日期」三者组合唯一也就是说同一天同一个城市只允许一条记录。数据导入脚本重复执行时这条约束能挡住重复数据避免页面上的数字莫名翻倍。对应 MySQL 里的建表语句如果是从 sql/ 目录手动导库看到的应该是这样CREATE TABLE epidemic_record ( id INT AUTO_INCREMENT PRIMARY KEY, province VARCHAR(50) NOT NULL, city VARCHAR(50) NOT NULL, confirmed INT DEFAULT 0, suspected INT DEFAULT 0, cured INT DEFAULT 0, dead INT DEFAULT 0, date DATE NOT NULL, UNIQUE KEY uk_province_city_date (province, city, date), KEY idx_province (province), KEY idx_date (date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;手写 SQL 和 Django 模型一一对应唯一索引、字段类型、字符集都保持同步。这样做的价值在于当你需要用原生 SQL 做批量导入、或者用可视化工具直接查数时不会因为两边表结构不一致而报表对不上。另外还会有一张用户表用来支撑登录注册功能。这个表结构比较简单就是 Django 默认的 auth_user 基础上扩展一个昵称字段不涉及复杂业务毕设演示时能登录、能区分普通用户和管理员就够用了。3. 环境搭建与数据导入把配置教程跑通的最短路径这一章是动手环节。我先按顺序拆解从解压到首页出现图表完整过程中途把最容易出错的地方提前标出来。3.1 Python 虚拟环境与依赖安装先把版本锁住源码包在 Python 3.8/3.9 环境下开发建议先创建独立虚拟环境避免和机器上其他项目共用依赖导致版本冲突。# 进入项目目录后创建虚拟环境 python -m venv venv # Windows 激活虚拟环境 venv\Scripts\activate # Linux / macOS 激活虚拟环境 source venv/bin/activate # 安装项目依赖 pip install -r requirements.txt为什么强制走虚拟环境这不算玄学是实打实的血泪经验。Django 版本之间不兼容的情况很常见机器上如果已有别的项目装的是 Django 4.x而源码包按 Django 3.2 写的混着用会出现各种莫名其妙的报错。venv 把依赖隔离在项目内部pip install 装的包只对当前项目生效。requirements.txt 里常见的依赖清单是这样的Django3.2.* mysqlclient2.1.* requests2.28.* pandas1.5.*Django 锁大版本mysqlclient 是连接 MySQL 的驱动。这里有一个高频坑Windows 下直接装 mysqlclient 经常失败报错信息一般是Microsoft Visual C 14.0 is required。遇到这种情况不要死磕换 pymysql 驱动pip install pymysql然后在项目同名目录下的__init__.py里加两行兼容代码import pymysql pymysql.install_as_MySQLdb()这段代码的作用是让 Django 的 MySQL 后端在调用 MySQLdb 时自动改用 pymysql。纯 Python 实现的驱动不需要本地编译工具链安装即用是 Windows 环境下的标准替代方案。提示如果你的系统是 Linux直接装 mysqlclient 一般没问题优先用它性能比 pymysql 略好。3.2 MySQL 建库与 settings.py 配置字符集和时区一次设好依赖装完后进入数据库配置环节。先用命令行建一个空库CREATE DATABASE epidemic DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;注意字符集必须显式写成 utf8mb4。疫情数据从网页抓取时偶尔会混入特殊符号如果建库时用了默认的 latin1 或 utf8写入时轻则告警重则该行数据直接报错。utf8mb4 是 MySQL 对完整 Unicode 的支持能容纳四字节字符是这类带文本数据的系统最稳妥的选择。然后修改 config/settings.py 里的 DATABASES 配置DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: epidemic, USER: root, PASSWORD: 你的数据库密码, HOST: 127.0.0.1, PORT: 3306, OPTIONS: { charset: utf8mb4, }, } }每个参数对应 MySQL 连接信息NAME 是刚才建好的库名USER 和 PASSWORD 换成你自己的数据库账号HOST 和 PORT 保持默认即可。OPTIONS 里的 charset 再设一次 utf8mb4是双保险——就算建库时没注意字符集连接层面的字符集对了也能降低乱码概率。同一份 settings.py 里还有两个配置项需要顺势确认LANGUAGE_CODE zh-hans TIME_ZONE Asia/Shanghai USE_TZ False时区设置影响数据写入时间的计算。如果 USE_TZ 保持默认的 TrueDjango 会用 UTC 时间做序列化页面显示的时间可能和你本地时间差八小时。做国内项目把这个值改成 False 是最省心的方案。3.3 数据迁移与初始化数据让首页在十分钟内出图配置完成后执行 Django 的迁移命令把模型同步到 MySQLpython manage.py makemigrations python manage.py migrate python manage.py createsuperusermakemigrations 根据 models.py 生成迁移文件migrate 把这些文件应用到数据库。createsuperuser 是创建后台管理账号用户名、邮箱、密码按提示输入即可用于登录 Django Admin 后台查看和管理数据。数据导入有两条路。第一条是直接用源码包里的 SQL 脚本mysql -uroot -p epidemic sql/epidemic_init.sql这个命令把 sql/epidemic_init.sql 里的建表语句和 INSERT 数据一次性灌进库。执行后可以在 MySQL 里确认一下记录数SELECT COUNT(*) FROM epidemic_record;第二条路是通过 Django 自带的导入脚本。源码包里通常内置了一份历史快照比如 static/data/history.json结构如下[ { date: 2020-02-01, province: 湖北, city: 武汉, confirmed: 9074, cured: 215, dead: 294 }, { date: 2020-02-01, province: 广东, city: 广州, confirmed: 535, cured: 112, dead: 4 } ]这个 JSON 是标准的数据交换格式每个对象对应数据库里的一条记录。导入脚本会遍历数组逐条写入表里遇到唯一约束冲突时先查重再决定更新还是跳过。这种 upsert 逻辑保证了脚本重复执行不会产生重复数据。导入命令一般是项目里自定义的 management commandpython manage.py import_data --file static/data/history.json执行完成后启动开发服务器python manage.py runserver浏览器访问http://127.0.0.1:8000首页大屏如果正常渲染出疫情地图和趋势折线图说明整套链路已经通了。如果页面有错先看终端控制台的报错日志大多数问题在这一步就能定位到具体模块。4. 核心功能拆解后端接口与 ECharts 大屏如何配合工作环境跑通只是开始关键是理解数据从 MySQL 到前端图表的完整流转链路。这也是答辩时最容易被追问的部分。4.1 后端视图用 ORM 聚合查询把统计结果转成 JSON疫情数据的查询集中在 apps/epidemic/views.py。核心接口之一是按省份返回每日趋势实现方式如下from django.http import JsonResponse from django.db.models import Sum from .models import EpidemicRecord def province_trend_api(request): province request.GET.get(province, 湖北) rows ( EpidemicRecord.objects .filter(provinceprovince) .order_by(date) .values(date) .annotate( confirmed_totalSum(confirmed), cured_totalSum(cured), dead_totalSum(dead), ) ) data list(rows) return JsonResponse({code: 0, data: data})这段代码的逻辑分四步filter 按省份筛选记录order_by 让结果按日期升序排列values 指定要返回的字段annotate 配合 Sum 把同一天内多城市的数据聚合到省份维度。annotate 是 Django ORM 里的分组聚合操作对应 SQL 里的 GROUP BY用它可以在 Python 层不写一行原生 SQL。JSON 响应的结构是标准格式code 为 0 表示业务成功data 是前端要的数组每个元素包含 date、confirmed_total、cured_total、dead_total。项目里其他接口沿用同一套响应格式前端处理起来统一。对应的路由配置在 apps/epidemic/urls.pyfrom django.urls import path from . import views urlpatterns [ path(api/epidemic/trend/, views.province_trend_api, nameprovince_trend), ]4.2 前端页面ECharts 折线图、柱状图与地图的接入前端模板放在 templates/ 下图表库通过静态文件引入。以省份趋势折线图为例页面里的核心 JS 逻辑如下fetch(/api/epidemic/trend/?province湖北) .then(res res.json()) .then(json { const dates json.data.map(item item.date); const confirmed json.data.map(item item.confirmed_total); const cured json.data.map(item item.cured_total); const chart echarts.init(document.getElementById(trendChart)); chart.setOption({ title: { text: 湖北省疫情趋势 }, tooltip: { trigger: axis }, legend: { data: [累计确诊, 治愈] }, xAxis: { type: category, data: dates }, yAxis: { type: value }, series: [ { name: 累计确诊, type: line, data: confirmed }, { name: 治愈, type: line, data: cured } ] }); });这段代码的逻辑fetch 请求后端接口拿到 JSON 后用 map 把日期和指标拆成两个平行数组建一个折线图实例setOption 渲染。series 数组里可放多条线一条对应累计确诊一条对应治愈天然适合做趋势对比。比例图用饼图和柱状图逻辑相同区别只在 series 里的 type 字段。三种图表类型的数据接入方式基本一致学会一种就能举一反三。4.3 地图可视化ECharts 5 里需要显式注册 GeoJSON疫情展示最核心的页面是「中国地图 各省数据着色」。这里有个重点ECharts 5 开始不再内置中国地图数据需要显式引入 GeoJSON 文件并注册。fetch(/static/data/china.json) .then(res res.json()) .then(mapJson { echarts.registerMap(china, mapJson); fetch(/api/epidemic/latest/) .then(res res.json()) .then(json { const mapChart echarts.init(document.getElementById(mapChart)); mapChart.setOption({ series: [{ type: map, map: china, roam: true, label: { show: true, fontSize: 10 }, data: json.data }], visualMap: { min: 0, max: 10000, text: [高, 低], realtime: false, calculable: true } }); }); });这里踩坑概率最高的地方是时机。registerMap 必须在 setOption 之前完成如果 fetch 地图 JSON 的请求还没返回就开始初始化图表画布上只有空白和报错。上面代码用嵌套 fetch 保证了顺序地图数据加载完才注册注册完才渲染。visualMap 是地图着色的关键配置项。min 和 max 定义了颜色映射的数据范围小于 min 的显示最浅色超过 max 的显示最深色。疫情数据全国各省差异可能上千倍max 值需要根据实际数据调整否则绝大多数省份都会因为数值远低于 max 而呈现同一种浅色地图看起来缺乏层次。大屏布局方面页面通常用 CSS Grid 或 Flexbox 排布多个图表容器。每个图表是一个独立 div高度要显式设置否则 echarts.init 初始化的容器高度为 0图表直接渲染不出来。5. 部署与使用避坑五个高频翻车点实录这一章是资源落地时最常见的五类问题每条都是「现象 → 原因 → 解决」的排查路径照着对号入座能省下大量查日志时间。5.1 现象页面渲染出来了但图表区域一片空白首页能正常打开但 ECharts 图表区域显示空白控制台报Cannot read properties of undefined。原因大概率有两个。第一echarts.init 在 DOM 还没有完成布局时执行容器高度为 0图表就画不上去第二fetch 地图 GeoJSON 是异步请求registerMap 还没执行就 setOption地图无法渲染。前者常见于把初始化脚本直接写在 body 顶部而没有等待 DOM ready后者常见于忘记嵌套请求顺序。解决方式把图表初始化逻辑放到页面底部或 DOMContentLoaded 回调中确保容器存在地图则严格按 fetch 完成后再 registerMap 再 setOption 的顺序执行。同时检查 CSS 里图表容器是否设置了明确的 height不要依赖内容撑开。document.addEventListener(DOMContentLoaded, function() { const chart echarts.init(document.getElementById(mapChart)); // 后续渲染逻辑 });5.2 现象mysqlclient 安装失败pip 报编译错误Windows 环境下执行pip install -r requirements.txt卡在 mysqlclient 这一步报error: Microsoft Visual C 14.0 is required。原因mysqlclient 需要本地编译 C 扩展Windows 上没有对应的编译器工具链就装不过去。这不是代码问题是环境问题。解决放弃在 Windows 上硬编 mysqlclient改用 pymysql。先执行pip install pymysql然后在项目同名目录的__init__.py中补充pymysql.install_as_MySQLdb()最后在 requirements.txt 里把 mysqlclient 那一行删掉或注释即可。如果服务器是 Linux 环境可以直接用 apt 装python3-dev default-libmysqlclient-dev build-essential后再装 mysqlclient成功率极高。5.3 现象中文数据显示为问号或乱码页面表格里汉字正常但写入 MySQL 后通过命令行查询显示成???或者从数据库读回来变成乱码。原因建库时没有指定 utf8mb4MySQL 使用了默认的 latin1 字符集或者在连接阶段的 charset 没对上。这是一个连锁问题建库字符集、连接字符集、表字符集三层中任何一层不是 utf8mb4中文写入就会出问题。解决第一建库时显式加上CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci第二在 settings.py 的 DATABASES 配置里补上OPTIONS: {charset: utf8mb4}第三如果表已经建好且数据已乱需要对表执行转换ALTER TABLE epidemic_record CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;转换后已写入的脏数据可能无法自动修复最稳妥的做法是删掉表重新导入一遍。5.4 现象数据重复导入后接口返回的数字翻倍运行导入脚本两次后首页的总确诊数变成实际值的两倍前端图表数据明显异常。原因导入脚本是逐条 INSERT 而不是 upsert第二次执行时没有检查该条记录是否已存在导致同一天同一个地区出现了两行记录。虽然模型里的 unique_together 设置了联合唯一约束但如果导入脚本用的是get_or_create拿不到记录时的create逻辑不够严谨或者绕过了 ORM 直接执行原生 SQL唯一约束就不生效。解决确认导入逻辑是否正确处理了重复记录。推荐的写法是用update_or_createrecord, created EpidemicRecord.objects.update_or_create( provinceitem[province], cityitem[city], dateitem[date], defaults{ confirmed: item[confirmed], suspected: item[suspected], cured: item[cured], dead: item[dead], } )这段代码按省份 城市 日期三个字段查找记录存在则更新 defaults 里的指标字段不存在才插入新行。重复执行再多次数据也不会翻倍。5.5 现象打开页面报 TemplateDoesNotExist访问首页时 Django 抛出TemplateDoesNotExist: index.html终端里能看到完整的模板加载路径列表。原因模板文件不在 Django 搜索路径中。Django 默认会按 app 目录下的 templates/ 子目录查找模板但项目的 templates/ 放在项目根目录需要在 settings.py 里显式注册。解决在 settings.py 中找到 TEMPLATES 配置确认 DIRS 里已加入模板目录TEMPLATES [ { BACKEND: django.template.backends.django.DjangoTemplates, DIRS: [BASE_DIR / templates], APP_DIRS: True, # 其余配置保持不变 }, ]注意 BASE_DIR / templates 使用的是 pathlib 语法Django 3.1 之后支持这种写法。检查完配置后重启 runserver模板路径立刻生效。6. 答辩前加一层定时更新疫情数据与接口耗时自查毕设演示时如果只是打开一个静态页面评委可能会问「数据怎么更新」。这个问题答好了是加分项。我给源码包补一个轻量方案用 Django 自定义 management command 做数据抓取再用系统定时任务触发。在 apps/epidemic/management/commands/ 目录下新建 update_data.pyfrom django.core.management.base import BaseCommand from ...services import fetch_latest_data class Command(BaseCommand): help 拉取最新疫情数据并写入 MySQL def handle(self, *args, **options): count fetch_latest_data() self.stdout.write(self.style.SUCCESS(f更新完成共写入 {count} 条新记录))这样数据更新就不再依赖手动点击页面而是可以通过命令行维护。Windows 上用任务计划程序Linux 上用 crontab每天定时执行一次python manage.py update_data。答辩现场演示时先跑一次这个命令再刷新页面比任何口头解释「我会更新数据」都有说服力。接口性能自查同样很关键。评委或老师可能会问「如果数据量翻十倍接口还扛得住吗」回答这个问题之前先自己看一眼 MySQL 的查询计划EXPLAIN SELECT province, SUM(confirmed) FROM epidemic_record WHERE date 2022-04-01 GROUP BY province;如果 EXPLAIN 结果里 type 是 ref 或 const并且用到了 idx_date 索引说明查询是健康的如果出现全表扫描typeALL就该考虑在 date 字段上建索引。项目里 models.py 已经给 date 加了 db_indexTrue正常是命中索引的你可以现场验证给评委看。我一般还会在后端接口里加一个简单的耗时打印方便自查import time def province_trend_api(request): start time.time() # ... 原有查询逻辑 elapsed (time.time() - start) * 1000 print(fprovince_trend_api 耗时: {elapsed:.1f}ms) return JsonResponse({code: 0, data: data})这个不是为了炫技是给答辩准备一组真实数字访问某个接口耗时多少毫秒数据库查询占了大头前端渲染又占多少。能讲出这层数据说明你对系统运行状态有实际掌控而不是只会跑通 demo。从那以后我每次拿到别人的 Django 项目都强制自己先看 models 再跑 migrate看完确认数据表结构没问题再调页面的图表初始化这个顺序帮我少踩了无数查日志的坑。这份源码包里的开发说明也建议按同样的节奏走先结构后功能先数据后界面。希望帮到你。本文还有配套的精品资源点击获取
返回列表