ARTICLE DETAIL

资讯详情

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

Django疫情数据分析系统实战:从数据建模到可视化看板

Django疫情数据分析系统实战:从数据建模到可视化看板 最近好几个准备毕业设计的读者来问我这类题目用Django做一个疫情数据分析系统还让我帮着看看源码。这题目确实经典但真正动手的人很容易卡在“数据表建好了却不知道下一步干嘛”上。今天我就拿我经手过的一套“源码16103”疫情数据分析系统为例从需求拆解、Django建模、数据清洗、可视化看板到WebSocket推送把完整思路和踩过的坑都摊开聊一聊。这篇不是官方文档复读更像是一个过来人带项目时的现场记录适合正在做B/S架构毕设、或想从零搭一套数据统计分析Python项目的同学。1. 项目整体设计与技术选型1.1 疫情数据分析系统到底要解决什么问题毕设题目里带“数据分析”四个字不是只做个增删改查就能过关的。疫情数据分析系统的核心价值是把多源、零散、不断变化的疫情数据用统一的结构存起来再按照日期、地区、病种状态这些维度做统计和对比最终用折线图、柱状图、热力地图等形式把结论直观呈现出来。我一般带项目时第一件事不是打开代码编辑器而是拉着学生把需求写清楚。这套系统至少要覆盖四类角色场景普通访客能查看总览看板、搜索指定日期和区域的统计数据管理员能登录后台维护基础数据、审核导入内容系统本身要周期性更新数据并把“累计值、新增值、治愈率、死亡率”这些指标计算出来最后要支持数据导出方便论文里贴报表和分析图。把需求拆成文档后你才知道哪些地方用Django自带能力哪些地方要自己写。比如用户登录可以直接用django.contrib.auth数据管理可以借助Django Admin快速生成后台统计查询用ORM的aggregate和annotate可视化交给前端ECharts。这样一来核心工作量就落在“数据清洗”和“统计口径设计”上这才是毕设真正值得写进论文章节里的部分。1.2 为什么选Django而不是Flask或Node很多新手会纠结技术栈其实选型逻辑很直接毕设要求你体现工程化的Web开发能力Django自带“全家桶”属性学习曲线虽然比Flask陡一些但一旦跑通省下的是大量重复造轮子的时间。Django默认就有完整的MVT分层、ORM、Admin后台、表单校验、Session、分页、中间件机制。做数据类项目时ORM尤为重要它能把SELECT SUM(confirmed) ... GROUP BY date这类聚合查询写成Python代码避免在项目里到处拼接原生SQL。Flask虽轻但用户认证、后台管理都要自己找第三方库毕设答辩时评委问“为什么不用Flask”你说“因为Django自带Admin和ORM能更高效率实现后台数据维护和分析查询”这就是很合理的工程理由。另外Django的项目结构天然适合论文画架构图。一个项目下拆多个app比如users管用户covid管疫情数据visual管图表接口每个app职责单一论文里画系统模块图、功能结构图都方便。相比之下如果全堆在Flask的单个app.py里代码可能很简短但复现和答辩讲逻辑时就比较痛苦。1.3 系统架构与功能模块划分我习惯把系统分成三层数据接入层、业务分析层、展示层。数据接入层负责CSV导入、爬虫采集或手工录入通常会写一个data_importer.py把原始文件清洗后写入MySQL或SQLite。业务分析层就是Django的views、models、services负责所有统计口算和权限控制。展示层则是模板页面加Ajax接口用Bootstrap搭框架图表统一走ECharts。功能模块可以按后台功能和前台功能两类划分。后台功能主要有用户管理、地区管理、疫情数据管理、数据导入导出、系统日志。前台功能主要有数据总览看板、按省市区联动查询、趋势折线图、地区排行柱状图、地图分布、最新疫情速报。这里说的“省市区”是通用区域层级概念并不限定具体国家。具体到源码目录常见的结构是这样的manage.py requirements.txt config/ settings.py urls.py asgi.py apps/ users/ covid/ visual/ static/ templates/ data/ import_data.py用startapp创建子应用时我建议直接用python manage.py startapp covid别把代码全放到一个app里。比如地图和图表接口如果混在covid/views.py里后期加一个功能就要改一大片代码拆成visual独立app后前后端联调要清晰很多。1.4 数据库模型设计一张表装不下所有分析疫情数据的核心表一定要设计好这决定了后面的统计查询能不能流畅做出来。我见过很多毕设源码所有字段堆在一张表里地区名直接写成字符串日期也用字符串存储结果用ORM按时间聚合时各种踩坑。建议至少拆两张表一张Region地区表记录地区编码和层级关系一张CovidData疫情数据表记录某地区某一天的累计确诊、疑似、治愈、死亡等数值。这样设计的好处很明显地区名称修改时不需要改动数据表按省、市不同层级汇总时可以用parent外键递归关联统计全国数据时只要查parent is null的地区再聚合。模型代码大致如下from django.db import models class Region(models.Model): name models.CharField(max_length100, verbose_name地区名称) code models.CharField(max_length32, uniqueTrue, verbose_name地区编码) parent models.ForeignKey( self, nullTrue, blankTrue, on_deletemodels.CASCADE, verbose_name上级地区 ) level models.SmallIntegerField(default1, verbose_name层级) class Meta: verbose_name 地区 verbose_name_plural verbose_name def __str__(self): return self.name class CovidData(models.Model): region models.ForeignKey(Region, on_deletemodels.CASCADE, verbose_name地区) date models.DateField(verbose_name日期) confirmed models.PositiveIntegerField(default0, verbose_name累计确诊) suspected models.PositiveIntegerField(default0, verbose_name累计疑似) cured models.PositiveIntegerField(default0, verbose_name累计治愈) dead models.PositiveIntegerField(default0, verbose_name累计死亡) created_at models.DateTimeField(auto_now_addTrue, verbose_name创建时间) updated_at models.DateTimeField(auto_nowTrue, verbose_name更新时间) class Meta: verbose_name 疫情数据 verbose_name_plural verbose_name unique_together (region, date) indexes [ models.Index(fields[region, date]), ] def __str__(self): return f{self.region.name}-{self.date}unique_together这里很重要能保证同一个地区同一天只存在一条记录不然导入数据时很容易出现重复行。配合索引按日期范围查某地区趋势时速度也能接受。如果你的毕设希望用更轻量的方式直接用SQLite也行但要把迁移文件和初始数据提交到源码里方便答辩时当场还原。2. 数据从哪来怎么清洗和入库2.1 数据采集优先用公开数据集而不是硬啃爬虫疫情数据系统的核心是数据但毕设阶段我不建议把太多精力放在爬虫上。一个反爬策略就能让你卡好几天而且爬下来的数据往往还要做大量清洗。更稳妥的做法是下载疫情公开数据集的CSV文件然后用脚本导入数据库。如果找不到合适的CSV也可以自己写一个模拟数据生成器按日期、地区、人数范围生成足够多的样本只要后续分析逻辑能跑通效果是一样的。当然如果你想在论文里体现爬虫能力可以针对一个权威公开数据页面写爬虫。用requests获取页面BeautifulSoup解析表格每次请求之间加随机延时并将抓取结果转为结构化DataFrame。这里特别要注意只爬公开、非敏感数据不要碰个人信息也不要高频请求。毕设重在展示技术不在数据体量。2.2 数据清洗的SOPpandas是主力原始CSV里的问题就那么几类日期格式不统一、数值列里面有逗号或中英文混合、一些空值代表“无数据”而不是“0”、地区名称有前后空格。我一般用pandas跑一遍清洗流程import pandas as pd df pd.read_csv(covid_data.csv, encodingutf-8-sig) # 日期标准化 df[date] pd.to_datetime(df[date], errorscoerce) df df.dropna(subset[date]) # 字符串数值转整数 for col in [confirmed, suspected, cured, dead]: df[col] ( df[col] .astype(str) .str.replace(,, ) .str.replace(--, 0) .str.replace(, 0) .fillna(0) .astype(int) ) # 地名去空格避免重复 df[region] df[region].str.strip() # 删除完全重复行 df df.drop_duplicates(subset[region, date], keeplast) print(df.info())这里有个细节str.replace(, 0)虽然笨但对半角全角空格都有兼容效果。清洗结束后再通过Django的ORM逐行get_or_create写入数据库。逐行写入百万级会慢但毕设数据量一般不大完全够用。如果想更快可以用bulk_create分批插入。2.3 从表数据到分析指标聚合查询不能靠Excel表里存的通常是累计值但看板展示时需要“每日新增”和“环比变化”。这个计算不能在模板里一条条做而应在后端用Django ORM聚合。最典型的是全国每日累计走势过滤出所有省级地区按日期分组把每个日期的confirmed加起来。用ORM写就是from django.db.models import Sum from apps.covid.models import CovidData, Region national_trend ( CovidData.objects .filter(region__parent__isnullTrue) .values(date) .annotate(totalSum(confirmed)) .order_by(date) )region__parent__isnullTrue表示取全国一级地区因为它们的parent外键为空。如果想让表结构更简单也可以不用递归外键而是在Region表加一个level字段然后按level1过滤。推荐递归外键的原因是它足够灵活论文里还能解释“树形结构建模”。每日新增的算法是把后一天的累计值减前一天累计值。实际操作时我会把这部分计算逻辑放到一个statistics.py服务里循环日期列表计算并把结果缓存到内存或Redis而不是每次都临时算。这样不仅前端响应快答辩时也能解释“为什么要设计缓存层”。3. 核心功能实现与实操要点3.1 Django Admin后台管理的“免费午餐”如果只给两个字符的忠告那就是“用Admin”。django.contrib.admin已经把数据后台搭好了只需要注册模型、配置字段就能快速管理地区表和疫情数据表。from django.contrib import admin from apps.covid.models import Region, CovidData admin.register(Region) class RegionAdmin(admin.ModelAdmin): list_display (name, code, level, parent) search_fields (name, code) admin.register(CovidData) class CovidDataAdmin(admin.ModelAdmin): list_display (region, date, confirmed, suspected, cured, dead) list_filter (date, region__parent) search_fields (region__name,) date_hierarchy date配置完以后管理员登录后台就能直接按日期过滤数据、按地区搜索记录。对毕设来说这是展示“用户权限管理”最省力的地方。但有三个坑要提醒第一线上环境一定要关掉DEBUGTrue不然一旦报错会泄露配置第二Admin的密码不要用弱口令毕业设计一般都会演示后台弱口令很减分第三如果业务量小不要在Admin里展示所有数据分页和搜索都设置好否则后台加载会卡。3.2 数据看板Ajax JSON ECharts看板页面我建议前后端分离数据接口也就是页面渲染由Django模板负责图表数据通过fetch异步获取。这样做的好处是数据刷新不需要重新加载整个页面接口可以单独测试后面如果要加移动端或大屏直接复用接口即可。后端写一个返回趋势数据的接口from django.http import JsonResponse from django.db.models import Sum from apps.covid.models import CovidData def trend_api(request): rows ( CovidData.objects .filter(region__parent__isnullTrue) .values(date) .annotate(totalSum(confirmed)) .order_by(date) ) data [{ date: item[date].strftime(%Y-%m-%d), total: item[total] } for item in rows] return JsonResponse({code: 0, data: data})注意date必须转成字符串否则JsonResponse会报TypeError: Object of type date is not JSON serializable。前端拿到数据后交给EChartsfetch(/api/trend/) .then(response response.json()) .then(res { const dates res.data.map(item item.date); const totals res.data.map(item item.total); const chart echarts.init(document.getElementById(trendChart)); chart.setOption({ tooltip: { trigger: axis }, xAxis: { type: category, data: dates }, yAxis: { type: value }, series: [{ name: 累计确诊, type: line, data: totals }] }); });首页建议同时展示三个图全国趋势折线图、地区排行柱状图、区域分布地图。地图组件引用echarts对应地图数据时注意文件体积。我第一次加载时把全国地图JS全部引进来页面卡了快三秒后来改成异步加载按需注册地图体验才正常。3.3 查询、分页与删除对象QuerySet里的四个常见细节数据量稍微上来后查询性能就很重要了。Django的QuerySet是惰性的真正执行SQL是在迭代、判断或序列化的时候。很多新人写列表页时每次循环都触发一次数据库查询这就是经典的N1问题。引入select_related和prefetch_related能显著减少查询次数。列表页查询示例from django.core.paginator import Paginator from apps.covid.models import CovidData def covid_data_list(request): queryset ( CovidData.objects .select_related(region) .order_by(-date) ) paginator Paginator(queryset, 20) page_obj paginator.get_page(request.GET.get(page)) return render(request, covid/list.html, {page_obj: page_obj})因为CovidData通过外键关联Regionselect_related(region)会生成一条JOIN查询模板中用item.region.name就不会额外发起查询。删除对象时也要注意外键约束。如果Region下面关联了很多CovidData直接删除某个地区默认会被外键约束挡住。要级联删除可以设置on_deletemodels.CASCADE但在业务上我们一般只做软删除给表加个is_deleted字段避免误删后数据无法恢复。执行批量删除时CovidData.objects.filter(date__lt2024-01-01).delete()会返回删除数量但如果有外键依赖同样要小心级联范围。3.4 WebSocket实时推送后台一更新前端看板就刷新纯轮询接口也能实现数据刷新但不够优雅。项目里如果预留了爬虫定时采集那么后台写入新数据后最好能主动通知前端页面更新。这里我用的是Django Channels实现的WebSocket推送。首先要安装channels然后在项目的asgi.py里配置协议import os from channels.routing import ProtocolTypeRouter, URLRouter from django.core.asgi import get_asgi_application from apps.visual.routing import websocket_urlpatterns os.environ.setdefault(DJANGO_SETTINGS_MODULE, config.settings) application ProtocolTypeRouter({ http: get_asgi_application(), websocket: URLRouter(websocket_urlpatterns), })消费者端把连接加入分组covidfrom channels.generic.websocket import AsyncJsonWebsocketConsumer class CovidConsumer(AsyncJsonWebsocketConsumer): async def connect(self): await self.channel_layer.group_add(covid, self.channel_name) await self.accept() async def disconnect(self, close_code): await self.channel_layer.group_discard(covid, self.channel_name) async def covid_update(self, event): await self.send_json({type: covid.update})后端数据更新后通过信号或者服务函数调用推送from channels.layers import get_channel_layer from asgiref.sync import async_to_sync def notify_covid_update(): channel_layer get_channel_layer() async_to_sync(channel_layer.group_send)( covid, {type: covid.update} )前端用WebSocket对象监听消息收到covid.update就重新请求接口刷新图表。毕设阶段如果不做实时推送这项功能也是重要的加分项推荐在论文功能模块里单独列一个小节。4. 常见问题与排查技巧实录4.1 迁移和建表问题最烦的问题就是改了模型却忘了迁移。启动项目时报no such table基本说明你只改了models.py没执行python manage.py makemigrations和python manage.py migrate。如果在原来的SQLite里有测试数据建议先备份再重置数据库否则字段冲突会越改越乱。另一个容易踩的是默认值。给confirmed等字段加PositiveIntegerField(default0)没问题但如果历史数据里有NULL又没设置空值时统计接口会返回None前端拿到空值后图表坐标轴直接归零。导入脚本里最好统一用fillna(0)保证数据库里不存在空数值。4.2 图表不显示先查接口再查前端出现“后台数据有前端图表空白”的情况我一般按三步排查。第一步直接在浏览器访问数据接口确认返回的JSON结构和想要的一致尤其注意date是字符串而不是null。第二步打开浏览器控制台看fetch请求如果接口报500多半是后端字段名写错了比如模型里叫confirmed接口里写成confirm。第三步看ECharts配置里的data和xAxis是否对应上。还有一个常见问题开发时Django跑在8000端口前端页面在另一个端口打开fetch(/api/trend/)会走不同域触发跨域。毕设简单处理可以统一用Django模板渲染页面让接口和页面同源如果分开了就安装django-cors-headers配置允许来源。4.3 部署上线容易挂的原因本地runserver很流畅一部署就白屏大概率是静态文件没配好。部署前务必检查ALLOWED_HOSTS是否包含你的域名或服务器IPDEBUG必须设为False然后执行python manage.py collectstatic让CSS、JS、图片汇总到统一目录。用Uvicorn或Gunicorn启动时也要记得给WebSocket单独配置ASGI服务器不然实时推送功能直接失效。时区问题也容易被忽略。Django默认USE_TZTrue时时间存的是UTC前端展示若不转换日期会差8小时。分析类系统最好统一口径要么USE_TZFalse所有日期直接用本地时间要么存入UTC前端转换展示。我带的毕设里统一用了USE_TZFalse简单易懂答辩也不会被问晕。4.4 答辩高频问题怎么答评委经常问“你这个系统和Excel做分析有什么区别”。要抓重点回答Django系统能把数据采集、存储、计算、展示放在一个Web闭环里支持多用户权限管理将来接实时数据源也非常方便。另一个高频问题是“数据准确性怎么保障”可以从清洗脚本、后台人工审核、唯一约束和日期校验四个角度说明。如果评委追问“数据量达到百万级怎么优化”不要慌。回答方向是给region_id date加联合索引列表页分页聚合结果用缓存或预计算高频查询走Redis爬虫定时任务用Celery异步处理避免阻塞Web服务。这些虽然不一定会全部实现但至少证明你懂性能优化方向。5. 实操心得与扩展建议5.1 我踩过的坑和最终改进方案这套系统我在几个不同版本的源码里反复改过。最开始的版本把清洗脚本放在视图函数里每次请求都重新清洗页面慢得一塌糊涂。后来拆成了独立脚本数据只导入一次页面查询才变得流畅。这个小改动让我明白毕设项目里“分层”不是抽象概念而是直接影响性能的实操问题。另一个让我印象深刻的坑是地图组件。最初用高德地图API结果开发时没有申请到正式Key页面局部空白。后来换成了纯ECharts内置地图完全离线答辩现场即使没网也能正常展示。所以如果有条件尽量选离线可用的可视化方案毕业设计演示环境不一定有外网。数据校验这块我也补了一版。在Admin的CovidDataAdmin里重写了save_model当累计值比前一天小时自动拦截并给出提示。虽然是简单校验但答辩时作为“系统健壮性”的实例比单纯展示增删改查有说服力得多。5.2 从“能跑”到“有亮点”的三个扩展方向毕设拿高分不一定要做得很复杂但要有清晰亮点。我个人推荐的三个扩展方向第一趋势预测模块。不引入复杂的深度学习用移动平均、指数平滑或线性回归做一个简单的“未来七日趋势预估”能在需求分析和算法设计两章里都写出内容。第二区域对比分析。支持选择两个或多个地区把它们同期的治愈率、增长率放在同一个图表里对比这比单地区折线图更接近“分析”。第三导出报告功能。后端生成PDF或Excel分析报告便于论文写实验结论也是企业里很常见的需求。这些扩展都不是重新做一套系统而是在现有看板页面上增加接口和组件。每条扩展花一到两天就能做完性价比很高。5.3 拿到“源码16103”后应该怎么读很多同学下载这类毕设源码后第一反应就是直接跑跑不通就开始慌。正确的阅读路径应该是先看README或部署文档确认Python版本、依赖包、数据库配置第二步用pip install -r requirements.txt安装依赖初始化数据库第三步找一个登录账号进入后台先观察页面功能最后再对着源码目录从urls.py路由开始追踪一个完整请求的流向。读代码时不要急着改功能先把“数据流”串起来CSV或后台录入的数据进库以后如何被视图函数读取如何被模板或接口使用如何最终呈现在图表上。只要这条链路在自己脑子里跑通答辩时被问任何一个模块都能接得上话。最后提个建议源码只是参考不要直接原封不动交上去。至少把项目名称、数据库字段、页面文案改成自己的再增加一两个独立小功能这样既能避免查重风险也能真正理解项目逻辑。疫情数据分析系统这个题目技术点本身不难难的是把数据思维和Web工程能力结合起来而这一点恰恰是答辩和作品集最看重的东西。
返回列表