
又到了一年一度的毕业设计选题季我身边陆续有人来问同一类问题“基于XX框架做一个数据分析系统”这类题目到底能不能做好不好通过今天就用“django新冠疫情数据分析系统”这个题目作为例子把这个课题从选题逻辑、技术选型、数据建模到核心功能实现的完整思路都拆一遍。先说结论这类题目属于典型的“数据驱动型Web应用”技术栈成熟、需求边界清晰、演示效果好非常适合作为计算机专业的毕业设计或者课程设计。它本质上不是一个科研命题而是一个工程化系统核心考察点在于你能不能把数据采集、数据清洗、后台管理、可视化分析这几件事串成一个完整的闭环。这篇文章主要面向正在选题或已经选了这个题目的同学也适合想用Django快速练手数据可视化项目的开发者。我会从项目为什么要这么做讲起一直讲到实际开发中的坑和排查技巧尽量给你一份可以照着做的参考方案。1. 项目定位与功能模块拆解1.1 这类课题的底层逻辑在做系统之前先要把题目“翻译”成技术语言。新冠疫情数据分析系统拆开来看其实就三件事数据从哪来、数据怎么存、数据怎么展示。数据从哪来对应爬虫采集或者文件导入数据怎么存对应关系型数据库的表设计和ORM模型数据怎么展示对应图表可视化和前端交互。再往细了说还包含用户登录、后台管理、权限控制这些通用功能但那些都是Django框架自带的能力真正需要自己动脑子的就是数据分析这条主线。这个题目受欢迎的原因也在于此它不像纯算法类课题那样需要很强的数学功底也不像纯商城类系统那样功能清单无穷无尽。它有一个明确的业务主题用到的技术又全是Django体系里的常见操作难度曲线平滑做出来的效果却很容易做出“数据大屏感”答辩演示非常加分。1.2 功能模块怎么划分我一般建议把系统划分为六个功能模块按用户角色来梳理用户模块注册、登录、退出普通用户和管理员区分权限。数据管理模块支持从文件导入疫情数据也可以在后台手动维护数据记录这是整个系统的数据来源入口。数据分析模块按时间趋势、地区分布、维度对比等条件查询和统计核心指标。可视化展示模块折线图、柱状图、饼图、地图等把数据库里的数字变成直观的图表。报表导出模块把筛选后的统计结果导出为Excel或CSV文件。后台管理模块用Django Admin管理用户、数据表、轮播图和系统日志。在实际开发中我发现很多同学容易忽略数据管理模块一上来就想着做花哨的图表。这是本末倒置的。没有稳定的数据入口图表做得再好看也是空中楼阁。我给的建议是数据管理模块用Excel导入作为主路径手写增删改查作为补充这样既不需要过多依赖外部接口又能保证系统在任何环境下都能演示。1.3 角色权限要提前定好权限设计不需要做得很复杂但一定要有。我的建议是设置两类角色普通用户可以浏览数据看板、查看图表详情、导出统计报表。管理员在普通用户权限基础上额外拥有数据导入、数据编辑、用户管理等能力。这么设计的理由是毕业设计答辩时老师一定会问“你的系统安全性体现在哪里”权限管理就是最直观的回答。Django自带的认证系统加上login_required装饰器十几行代码就能完成性价比非常高。2. 技术选型解析为什么是Django2.1 Django在这个项目里的核心优势数据分析系统用Java、用Flask、用Node都能做但用Django确实是同类题目里开发效率最高的方案之一。原因有三个。第一是ORM。Django的ORM可以直接把数据库表映射成Python对象写一行EpidemicData.objects.filter(date__gtestart_date)就能完成带时间条件的查询不需要手写SQL对不熟悉数据库操作的同学来说特别友好。第二是自带Admin后台。Django Admin是框架自带的注册模型后自动生成增删改查界面。这意味着系统的数据管理模块在一开始就完成了一半省下来的时间全部可以投入到分析页面的打磨上。第三是认证系统。登录、注册、会话管理、密码加密都是现成的继承一个AbstractUser就能扩展自定义字段。对比从零手写用户系统这个优势不能忽视。有人会问那用Flask不是更轻量吗对纯API项目来说Flask确实更轻但一旦涉及Admin后台、表单处理、权限中间件这些模块Flask就需要自己去装一堆扩展插件组装成本并不低。毕设项目要求的是“功能完整、能跑通、能演示”Django的“全家桶”思路恰恰是这个场景下的最优解。2.2 数据库与可视化方案怎么配数据库层面我推荐正式环境用MySQL开发阶段先用SQLite。SQLite是Django默认支持的零配置文件初始化开发时特别方便MySQL则更贴近真实生产环境答辩时提到“迁移到MySQL”也会加分。两者的切换只需要改settings.py里的DATABASES配置前期完全可以用SQLite先把功能跑通后期再换。可视化层面首选ECharts。数据可视化前端库其实有很多Chart.js、Highcharts、AntV都可以我推荐ECharts的理由很实际中文文档全、示例丰富、地图支持好、答辩效果直观。尤其是地图功能疫情数据按地区展示时用ECharts地图要比普通表格有说服力得多。另外提示一点ECharts的渲染方式我建议采用Ajax异步加载JSON数据而不是在页面刷新时一次性嵌入所有图表数据。这样页面首屏加载更快筛选条件切换时也不需要整页刷新交互体验上有明显提升。2.3 项目目录结构和开发环境建议开发环境用的东西很简单Python 3.9以上、Django 4.x LTS版本、MySQL或者SQLite、Vue或者原生HTML模板都行。考虑到这个系统本身以服务端渲染为主直接使用Django模板系统加上少量ECharts前端代码就够了不需要额外引入前端框架增加复杂度。目录结构我建议按App拆分project/ ├── manage.py ├── requirements.txt ├── config/ # 项目配置 │ ├── settings.py │ ├── urls.py ├── apps/ │ ├── users/ # 用户认证 │ ├── data_manage/ # 数据导入与维护 │ ├── analysis/ # 数据分析与图表 │ └── dashboard/ # 首页看板 └── static/按App拆分的好处是职责清晰一个App只做一件事后期扩展也方便。很多同学图省事把所有逻辑写在一个App里结果代码量上来以后根本没法维护答辩时被问到某个文件是干什么的也说不清楚。3. 数据从哪里来采集、清洗与建模3.1 数据源的三种获取方式关于数据源实际项目里通常有三种选择爬虫采集、公共API接口、本地文件导入。对这个毕业设计题目我的建议是混合使用。爬虫采集可以写一个小脚本用requests从公开数据渠道抓取历史数据解析后存入数据库。这里要注意疫情数据的格式在不同来源里差异很大有的字段叫“确诊”有的叫“感染”有的字段是累计数量有的是每日新增解析逻辑必须做好兼容。公共API接口是最省事的方案但存在两个隐患一是接口不稳定临近答辩突然挂掉就麻烦了二是一些公开接口的数据口径无法保证直接影响系统分析结果的准确性。所以不推荐把项目核心功能完全依赖在外部接口上。更稳妥的方案是本地文件导入。在数据管理页面提供Excel上传入口规定好列名和日期格式用户在后台上传Excel文件系统读取后写入数据库。这样做的好处是数据可控答辩演示时不用担心网络问题数据质量也可以自己保证。3.2 数据库表结构设计数据模型是整个系统的地基字段设计得好不好直接影响后面所有查询代码的复杂度。参照业务需求核心数据表设计如下from django.db import models class EpidemicData(models.Model): region models.CharField(max_length100, verbose_name地区) date models.DateField(verbose_name日期) confirmed models.IntegerField(default0, verbose_name累计确诊) cured models.IntegerField(default0, verbose_name累计治愈) dead models.IntegerField(default0, verbose_name累计死亡) class Meta: verbose_name 疫情数据 verbose_name_plural verbose_name ordering [-date] indexes [ models.Index(fields[region, date]), ] def __str__(self): return f{self.region} - {self.date}这个模型包含了一个典型数据分析系统所需要的全部要素地区维度、时间维度、核心统计指标。region字段存地区名称可以是国家、省份、城市粒度根据数据来源决定。所有数值字段都设置默认值0防止导入不完整数据时出现空值。为什么要把地区和时间做成两个独立字段而不是直接拼成一个字符串因为后期的所有统计操作都是围绕这两个维度展开的。按时间趋势分析要用date做聚合按地区分布对比要用region做聚合字段不拆开的话统计分析代码会变得非常痛苦。3.3 数据清洗与初始化脚本在把数据导入真实数据库之前有一道必须做的工序数据清洗。这里分享几个我实际踩过的坑。第一是日期格式不统一。有的数据源日期是2023-01-01有的是2023/1/1还有的是2023.01.01。清洗时统一用dateutil.parser.parse解析后再格式化为标准日期。第二是缺失值处理。某个地区某天没有上报数据记录中可能出现空值。清洗规则是数值字段为空时补0地区字段为空时丢弃该条记录。第三个常见的坑是地区名称不一致同一省份有的数据源叫“省份A”有的叫“A省”需要做一次地区名称映射。我建议维护一个地区别名字典清洗时统一替换。清洗脚本可以放在一个独立的scripts/data_clean.py文件里通过python manage.py shell scripts/data_clean.py方式执行。这样可以随时重跑不用污染项目主代码。4. 核心业务逻辑查询、统计与可视化4.1 按时间维度的趋势分析数据分析系统的核心能力就是查询统计而最直观的分析维度是时间趋势。比如我想看全国范围内确诊数量的变化趋势对应的Django ORM写法是from django.db.models import Sum from django.db.models.functions import TruncDate from apps.data_manage.models import EpidemicData def trend_data(request): city request.GET.get(city, ) queryset EpidemicData.objects.all() if city: queryset queryset.filter(regioncity) trend ( queryset.annotate(dayTruncDate(date)) .values(day) .annotate(totalSum(confirmed)) .order_by(day) ) data [ {date: item[day].strftime(%Y-%m-%d), value: item[total]} for item in trend ] return JsonResponse(data)这里用TruncDate把datetime字段截断为日期再按day分组求和。注意values(day)和annotate(totalSum(confirmed))的顺序不能写反——values决定了分组字段annotate中的聚合结果会附加到每一条分组数据上。前端用ECharts折线图渲染这段数据就非常顺手fetch(/analysis/trend_data/) .then(response response.json()) .then(data { const chart echarts.init(document.getElementById(trendChart)); chart.setOption({ xAxis: { type: category, data: data.map(d d.date) }, yAxis: { type: value }, series: [{ type: line, data: data.map(d d.value), smooth: true }] }); });4.2 地区分布与横向对比除了时间趋势地区分布是另一个核心分析维度。比如我想看各省份累计确诊的对比可以用按region分组聚合from django.db.models import Sum def region_rank(request): rank_data ( EpidemicData.objects.values(region) .annotate(totalSum(confirmed)) .order_by(-total)[:20] ) data [ {region: item[region], value: item[total]} for item in rank_data ] return JsonResponse(data)这类数据最适合用柱状图展示数据从多到少排序一眼就能看出哪些地区排名靠前。如果数据源包含省市级的地理坐标还可以用ECharts地图把统计结果填充到地图对应区域的颜色深浅上视觉效果远好于柱状图。做地图展示时有一个容易踩的坑ECharts的map类型图表需要提前注册地图GeoJSON数据而不是直接用type: map就行。比如国内省市地图需要先加载对应的GeoJSON并注册fetch(/static/geo/regions.json) .then(res res.json()) .then(geoJson { echarts.registerMap(regions, geoJson); // 然后才能使用 type: map, map: regions });4.3 视图、模板与图表的数据流串联数据分析系统的另一条关键链路是视图函数、模板和前端JS之间如何配合。我的建议是页面主体用Django模板渲染数据全部通过Ajax请求JSON接口获取。这样页面结构和服务端逻辑清晰分离调试时互不干扰。具体流程是首先在urls.py里配置路由一个返回HTML页面一个返回JSON数据from django.urls import path from apps.analysis import views urlpatterns [ path(analysis/, views.analysis_index, nameanalysis_index), path(analysis/trend_data/, views.trend_data, nametrend_data), path(analysis/region_rank/, views.region_rank, nameregion_rank), ]然后在模板中通过JS发起请求拿到数据后渲染图表。模板本身只需要放几个div容器作为图表挂载点。我这里要特别强调一个细节JsonResponse默认只支持字典类型如果直接传入列表需要加safeFalse参数。很多同学第一次写接口时都会在这里报错In order to allow non-dict objects to be serialized set the safe parameter to False其实这就是最典型的低级错误。5. 管理后台与权限控制5.1 定制Django Admin后台Django Admin是整个系统里“白送”的功能但如果完全不配置就直接用界面粗糙不说管理效率也很低。我建议花半小时做一轮简单定制成本低效果明显。在admin.py中注册模型并配置列表页字段from django.contrib import admin from apps.data_manage.models import EpidemicData admin.register(EpidemicData) class EpidemicDataAdmin(admin.ModelAdmin): list_display (region, date, confirmed, cured, dead) list_filter (date, region) search_fields (region,) date_hierarchy date这样配置之后后台列表页可以直接按地区筛选、按日期筛选还能按日期的层级维度钻取。对于动辄几千条疫情数据的数据表来说这个功能比手写一个爬虫管理页面实用得多。5.2 用户认证与权限区分自定义用户模型是很多毕业设计的必选项。默认的auth.User只有用户名、密码、邮箱等字段通常不能满足项目对用户手机号、头像等信息的存储需求。扩展方式很简单from django.contrib.auth.models import AbstractUser class User(AbstractUser): phone models.CharField(max_length11, blankTrue, verbose_name手机号) avatar models.ImageField(upload_toavatars/, blankTrue, verbose_name头像)然后记得在settings.py里设置AUTH_USER_MODEL users.User这一步一定在第一次migrate之前完成否则后期切换用户模型会很麻烦需要重新初始化数据库。控制页面访问权限时用Django自带装饰器就够了from django.contrib.auth.decorators import login_required, user_passes_test login_required def dashboard(request): ... def is_admin(user): return user.is_superuser or user.is_staff user_passes_test(is_admin) def data_import(request): ...这么做之后未登录用户无法访问数据看板非管理员无法进入数据导入页面。前后端两侧的权限防线都补齐了答辩时被问安全问题也能对答如流。5.3 文件导入的完整链路关于Excel导入数据我给一个可以直接落地的思路。前端页面提供文件上传表单后端接收文件后用openpyxl解析逐行校验后写入数据库。核心逻辑类似这样import openpyxl from django.shortcuts import render, redirect def upload_excel(request): if request.method POST: excel_file request.FILES.get(file) wb openpyxl.load_workbook(excel_file) sheet wb.active for row in sheet.iter_rows(min_row2, values_onlyTrue): region, date_str, confirmed, cured, dead row EpidemicData.objects.update_or_create( regionregion, datedatetime.strptime(date_str, %Y-%m-%d).date(), defaults{ confirmed: confirmed or 0, cured: cured or 0, dead: dead or 0, }, ) return redirect(data_list) return render(request, data_manage/upload.html)这里用update_or_create而不是create是为了避免同一地区同一天的数据重复导入时产生脏数据。这个细节很多人一开始想不到等数据出现重复时才回来补。6. 常见问题与排查技巧实录6.1 高频问题速查表整个开发过程中我发现以下几个问题出现频率最高整理成速查表方便对照问题现象常见原因解决办法页面能开但图表空白前端拿到JSON的键名和后端返回的键名不一致打开浏览器控制台打印返回的data逐字段核对数据库中文乱码MySQL连接没指定utf8mb4字符集settings中DATABASES配置增加OPTIONS中charset设置或建库时指定utf8mb4图表数据为0日期筛选参数类型是字符串没转换日期视图里先datetime.strptime再过滤登录后跳转404LOGIN_URL配置了不存在的路由检查settings.py和urls.py中的登录地址导入Excel报错表头有不可见字符或空行openpyxl读取前先strip表头跳过全空行静态文件404没配置STATICFILES_DIRS或者没执行collectstatic开发环境配置STATICFILES_DIRS指向static目录6.2 时区问题导致的日期偏移再分享一个隐蔽的坑。如果数据表里保存日期用的是DateTimeField而settings.py里设置了USE_TZ True那么写入时间和读取时间会发生时区偏移导致按日期聚合时分错组。解决思路有两种一种是数据表日期字段直接用DateField而不是DateTimeField日期本身不涉及时区转换另一种是保持USE_TZ False让Django放弃UTC自动转换。从数据统计分析的角度看DateField更简洁推荐优先使用。6.3 大数据量下的查询性能优化当数据量涨到十万条级别时普通的ORM聚合查询会开始变慢页面切换筛选条件时能明显感到卡顿。我当时做的优化有三个。第一是给常用查询字段加联合索引这是最简单也最有效的优化。就是数据模型Meta类里indexes [models.Index(fields[region, date])]那段配置。第二是筛选结果加缓存。时间趋势和地区排名这类数据在短期内不会变化可以用Django的cache框架缓存几分钟减少重复计算。第三是统计口径尽量用SQL层的聚合不要在Python里循环累加。有些同学喜欢先用all()把数据全部查出来再遍历统计十万条记录会慢到怀疑人生。用values().annotate()写出来的分组查询交给数据库执行效率完全不一样。写在最后的一些经验最后聊一点我的个人体会。做这类毕业设计系统最容易犯的错就是把摊子铺得太大。我今天见过不止一个同学系统规划了十几个模块结果到中期发现核心功能都还没做完最后草草收尾。如果你也在做类似的系统我给你一个建议先把“数据导入→数据存储→时间趋势图→地区分布图→后台管理”这条主线跑到通剩下的模块再一个一个往上加加一个稳一个而不是一开始就想着做大而全的平台。另外答辩演示前一定要准备一份规模适中的干净数据。数据量太少图表看起来没有说服力数据量太大现场查询又容易卡顿。我当时准备的是近一年时间跨度、覆盖几十个地区的数据演示时既流畅又直观效果比堆几万条杂乱数据好得多。这个系统后续还可以往很多方向扩展比如接入实时数据刷新、加入趋势预测算法、部署到云服务器提供公网访问。但那些都是后话先把基础闭环跑通你的毕业设计就已经成功了一大半。