ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于Django的Bilibili青少年模式数据分析系统开发实战

基于Django的Bilibili青少年模式数据分析系统开发实战 做毕业设计最怕什么不是技术难项目复杂而是选了一个又老又没营养的题目比如“图书管理系统”“学生信息管理系统”模板化严重答辩时老师问两句就露馅。今天想分享的这套题目是个相当有代表性的方向基于django的Bilibili青少年模式使用情况的数据分析系统。名字虽然长核心其实就三个词——django、Bilibili、数据分析。它难得的点在于选题有社会热度青少年模式是平台方和监管方都在持续推进的机制技术上有层次从后端框架到数据清洗再到可视化全链路而且实现起来不那么卷只要逻辑清楚工作量是实实在在可以看得见的。无论是想做个像样的毕设还是想在企业项目里积累一些数据统计模块的经验这套东西都值得拆开嚼一嚼。我下面会把整个系统的设计思路、数据模型、功能实现、可视化方案和那些只有真正经手过一遍才会遇到的坑全部摊开来讲尽量讲到一种“带着照着做就能落地”的程度。1. 项目整体设计与选题价值1.1 为什么这个选题比传统管理系统更有底气先聊聊选题层面的东西因为很多同学项目写着写着就卡死了回头一看全是选题的时候埋的雷。传统管理系统图书、教务、超市进销存本质上就是几个增删改查页面的排列组合技术含量低需求描述基本是十年前的产物答辩时老师最常问的就是“你的系统解决了什么别人解决不了的问题”答不上来就尴尬。而“基于django的Bilibili青少年模式使用情况的数据分析系统”天然自带三个加分项第一数据源有故事。Bilibili是一个日活过亿的UGC视频社区它的青少年模式是个真实存在且被持续讨论的机制系统设计时可以向“未成年人内容过滤”这个社会议题靠拢这是“需求来源”的最好背书。第二分析链路是完整闭环。不是简单的表单、报表而是从“采集用户行为数据”到“特征加工”再到“可视化洞察”的完整链路涉及的数据处理技术点是实打实的。第三django框架能够很好地承载这类应用。它的ORM、Admin、模板引擎、认证体系开箱即用对毕业设计来说与其用Flask那种“光着膀子干”的微框架硬撑业务逻辑不如用django全家桶让系统结构更完整更能撑起“设计”两个字的分量。这个系统能做什么一句话监控和分析某一类用户未成年人画像在Bilibili平台上的内容消费行为模式包括使用时段、视频类别偏好、观看时长分布、弹幕互动频率、搜索行为特征等。系统最终要输出的是“面向运营和管理者的决策数据”也就是给平台做内容治理或者给家长提供使用建议时有数据可依。适合谁参考如果你是2024届、2025届计算机相关专业的学生正在为选题焦虑或者你是想学习一个完整的django数据可视化项目怎么搭的初级开发者再或者你只是在帮亲戚朋友找毕设方案的——都适用。1.2 总体架构选型为什么是django全家桶而不是微服务架构上我的建议是不上微服务不搞Redis缓存不做消息队列就用一个django项目打天下。原因很简单毕设的核心逻辑是“把数据分析清楚”而不是“让系统承受高并发”。微服务那一套会让你陷入服务发现、网关配置、分布式事务的泥潭里工作量爆炸但展示出来的部分又不容易让答辩老师快速理解。推荐的分层是前端层HTML CSS JavaScript ECharts图表 控制层django views接收请求、调度数据分析任务 业务层service模块封装核心统计分析算法 数据层django ORM SQLite/MySQLSQLite可以作为开发环境默认库部署时切换成MySQL。django的ORM在这时候体现出了极大便利——你不需要写复杂的SQL join语句数据的聚合、分组、过滤直接在Python层表达改起来也快。为什么要用ECharts而不是直接塞给前端一个表格因为数据分析系统的灵魂在于“让人一眼看出规律”ECharts的图表交互能力和组件丰富程度比Charts.js更成熟而且中文文档对毕设作者友好得多。1.3 影响范围与应用场景分析不要小看这个系统的延展空间往深了推它能覆盖下面几个真实场景平台运营侧分析青少年模式下不同内容品类的完播率、点赞率、搜索热词帮助产品团队调整“青少年模式底栏推荐策略”。内容治理侧识别峰值时段比如21-23点反推内容审核人力排班是否需要跟着波动。家长教育侧输出“一周观看内容品类报表”让家长不用翻手机也能直观知道孩子看了什么。顺着这个思路做你不会只交一个“课程作业”而是可以宣称“这是一个具备产品化潜力的数据分析原型系统”。2. 核心细节解析与数据模型设计2.1 数据模型怎么把Bilibili青少年模式的数据抽象成表数据分析系统最关键的第一步是把业务行为翻译成数据表结构。这一步要做扎实否则后面写统计逻辑时会反复改表结构。我的设计里一共三张核心表表一用户基础信息表Users 这张表存储画像维度信息注意不要存明文密码等敏感字段在毕设场景下用django自带的User模型扩展一个Profile即可。扩展字段包括年龄段、首次开启青少年模式时间、当前模式状态普通/青少年、所在省份、设备类型移动端/PC/平板。表二使用行为明细表BehaviorRecord 这是全系统的数据基础记录每一次“行为动作”。字段设计如下id: 自增主键 user_id: 外键关联Users表 behavior_type: 行为类型VIEW, SEARCH, LIKE, COMMENT, SHARE content_category: 内容大类知识、游戏、音乐、动画、影视、生活 video_duration: 视频总时长秒 watch_duration: 实际观看时长秒 is_completed: 是否看完布尔型 trigger_time: 行为触发时间datetime这里我要特别强调behavior_type和content_category建议用整数枚举0-5不要直接存中文字符串。一是省空间二是查询对比时效率更高三是后续如果要扩展类型不用改表结构。大部分同学在这翻车因为存了太多冗余信息结果统计时被数据清洗折腾得头大。表三上下文状态表ContextSnapshot这是容易被忽视但分析价值极高的一张表。数据分析系统如果想回答“青少年模式下用户的观看行为是否发生了变化”必须保留行为发生时的上下文状态。字段包括是否处于青少年模式、当日累计使用时长、当前连续使用时长、上一次模式切换时间间隔。为什么要专门存这个因为同一个用户在青少年模式和非青少年模式下行为模式往往差异很大。比如普通模式下刷游戏视频能刷到凌晨切到青少年模式后可能十分钟就退出了。如果不记录上下文状态你会得出极不准确的结论。2.2 关键设计决策为什么把“状态”单独拆表有人会问这不就是一个字段的事吗干嘛单拆一张表我用亲身经历回答你如果并在一张表里每增加一个状态维度你都要去改用户表改完还要写迁移脚本烦不说还容易破坏已有数据。拆成ContextSnapshot表之后场景快照独立演进行为表和上下文表通过user_id和trigger_time关联多个分析任务可以同时抽取不同时间段的快照互不干扰。此外还有隐私合规的考量。青少年数据属于敏感个人信息系统设计里就该把“最小化存储”体现出来——上下文表中不存具体用户只存行为指纹ID这是一种“设计即合规”的思路在项目说明文档里写上这一点答辩老师会高看一眼。2.3 数据模拟与采集策略这是必须诚实面对的问题。毕设阶段你无法拿到B站真实的青少年模式用户明细数据也不可能自己大规模爬取用户行为那么数据从哪来我的方案是写一个数据模拟脚本在合理假设的前提下生成约1-3万条行为记录。具体参数遵循以下几点假设用户总量500人模拟一个中等规模测试社区男女比例6:4内容类别分布知识类占比15%游戏类占比30%动画类占比20%音乐类占比15%影视类占比10%生活类占比10%观看时长与视频时长比值的正态分布均值0.7标准差0.2这一步诚实地写进项目文档里说明叫“基于蒙特卡洛模拟的样本数据生成”不是你造假而是你在无法获取商业数据的环境下用科学手段构造数据集这个思路本身就有说明力。不过我也建议系统预留“导入真实CSV数据”的入口一旦后期拿到脱敏数据可以直接替换模拟器。3. 功能模块拆解与可视化实现3.1 核心功能模块一使用概况总览数据看板首页用卡片展示五个核心指标今日活跃用户数、平均单次使用时长、青少年模式开启率、热门内容Top3、峰值时段。这几个指标怎么算今日活跃用户数BehaviorRecord.objects.filter(trigger_time__datedate.today()).values(user_id).distinct().count()平均单次使用时长先按(user_id, session_id)分组计算会话时长再求平均值。session_id需要在写入行为数据时一并写入可以在生成数据的脚本里用“连续行为间隔超过30分钟算一个新会话”的规则来定义。青少年模式开启率ContextSnapshot.objects.filter(is_teen_modeTrue).count() * 1.0 / ContextSnapshot.objects.count()这些指标单独看不难但把它们整合到一张看板上还要让图表联动需要前后端配合好。前端我用ECharts的init方法创建实例后端通过django的JsonResponse返回统计JSON前端fetch拿到数据后setOption完全够用。3.2 核心功能模块二时段规律分析这是我最推荐你在答辩时重点展示的模块因为它的分析结果有很强的故事性。实现思路是把一天24小时切分成96个15分钟窗口然后统计每个窗口内的行为事件密度即每15分钟的行为触发次数。用django ORM怎么写from django.db.models import Count from django.db.models.functions import TruncHour, TruncMinute results ( BehaviorRecord.objects .filter(trigger_time__datetarget_date) .annotate(hourTruncHour(trigger_time)) .values(hour) .annotate(event_countCount(id)) .order_by(hour) )如果要精确到15分钟窗口用TruncMinute配合分钟整除就能算出来from django.db.models import F from django.db.models.functions import Cast BehaviorRecord.objects.annotate( minute_partExtractMinute(F(trigger_time)) ).annotate( quarterCast((F(minute_part) / 15), output_fieldIntegerField()) )看到没有django的ORM最迷人的地方就在这里——你不用写一行原生SQL就能完成数据库端的分组聚合计算性能也不错。可视化层面我会画一张双曲线图一条是“青少年模式行为密度”另一条是“普通模式行为密度”。两条曲线叠加能很明显地看出切换模式后用户的活跃时段如何发生位移。实际操作中我们发现模拟数据里青少年模式在19-21点出现明显高峰而普通模式在21-23点仍维持高位。这个发现可以引导出结论“青少年模式有效降低了深夜使用行为的发生概率”这就是分析的价值。3.3 核心功能模块三内容偏好画像内容偏好画像背后的分析逻辑并不复杂对用户的历史观看记录做聚合统计每个内容类别的观看总时长和观看次数再计算“偏好指数”。这里需要引入一点信息论的思想。指数公式为偏好指数 (某类别观看时长 / 总观看时长) / (某类别内容供给量 / 总内容供给量)如果偏好指数大于1说明该类别的消费份额超过了供给份额用户存在明显偏好小于1则说明需求相对低迷。这样算出来的“偏好”比单纯看观看时长Top1、Top2要科学得多因为它剔除了“内容供给分布不均”的干扰。数据输出格式大致如下{ category: 知识, watch_share: 0.28, supply_share: 0.15, preference_index: 1.87, trend: up }前端我建议用雷达图展现多类别偏好指数。为什么雷达图比柱状图好因为雷达图可以同时展现多个维度的相对关系“高维对比”一目了然答辩老师一看就知道你考虑过可视化语义。3.4 核心功能模块四线性模型下的使用时长预测这部分属于锦上添花但能让你的系统含金量上一个台阶。我用一个最简单的多元线性回归预测“青少年模式下的用户单次使用时长”。特征变量取当日累计使用时长、时段上午、下午、晚上、深夜、内容类别、后续是否发生搜索行为。django集成机器学习的方式很直接训练阶段用scikit-learn在本地生成模型参数然后通过django的joblib加载模型文件进行预测。代码结构如下import joblib model joblib.load(analysis/ml_models/duration_predictor.pkl) def predict_duration(request): feature_vector [ float(request.GET.get(daily_total, 0)), float(request.GET.get(period_code, 0)), float(request.GET.get(category_code, 0)), float(request.GET.get(search_after, 0)) ] pred model.predict([feature_vector])[0] return JsonResponse({predicted_seconds: round(pred, 2)})为什么选线性回归而不是决策树、随机森林因为线性回归的可解释性在答辩时很加分。你可以说出每个特征的系数比如“深夜时段的系数为负说明深夜观看会缩短单次使用时长”这种从系数反推业务含义的能力是黑盒模型给不了的。4. 实操过程与核心环节实现4.1 django项目初始化和App规划环境准备阶段我建议直接用conda建一个Python 3.10的虚拟环境避免系统Python环境弄脏。然后conda create -n bili_analysis python3.10 -y conda activate bili_analysis pip install django4.2 scikit-learn pandas joblib django-admin startproject bili_analysis_system cd bili_analysis_system python manage.py startapp users python manage.py startapp behavior python manage.py startapp analysis python manage.py startapp dashboardApp划分的逻辑很清楚users管用户画像behavior管行为数据录入与查询analysis管统计分析业务dashboard管看板和图表接口。混在一个App里会越到后面越乱职责分离是不可省的一步。项目跑起来后先去settings.py注册这些AppINSTALLED_APPS [ django.contrib.admin, django.contrib.auth, django.contrib.contenttypes, django.contrib.sessions, django.contrib.messages, django.contrib.staticfiles, users, behavior, analysis, dashboard, ]4.2 models.py表结构落地以behavior这个App为例我给的models.py核心代码如下from django.db import models from django.contrib.auth.models import User class BehaviorRecord(models.Model): BEHAVIOR_TYPES [ (0, VIEW), (1, SEARCH), (2, LIKE), (3, COMMENT), (4, SHARE), (5, BLOCK), ] CATEGORY_TYPES [ (0, 知识), (1, 游戏), (2, 动画), (3, 音乐), (4, 影视), (5, 生活), ] user models.ForeignKey(User, on_deletemodels.CASCADE, related_namebehavior_records) behavior_type models.IntegerField(choicesBEHAVIOR_TYPES) content_category models.IntegerField(choicesCATEGORY_TYPES) video_duration models.IntegerField(help_text视频总时长秒) watch_duration models.IntegerField(help_text实际观看时长秒) is_completed models.BooleanField(defaultFalse) trigger_time models.DateTimeField(db_indexTrue) class Meta: ordering [-trigger_time] indexes [ models.Index(fields[user, trigger_time]), models.Index(fields[content_category, trigger_time]), ] class ContextSnapshot(models.Model): record models.OneToOneField(BehaviorRecord, on_deletemodels.CASCADE, related_namecontext) is_teen_mode models.BooleanField(defaultTrue) daily_usage_seconds models.IntegerField() continuous_usage_seconds models.IntegerField() last_mode_change_interval models.IntegerField()两个细节你务必注意把trigger_time加上db_index后面大量按时间范围过滤的查询会快很多。数据量过万后没索引的datetime字段查询会肉眼可见地慢。双字段联合索引(user, trigger_time)能有效加速“查询某个用户一段时间的行为记录”这种高频操作。写完后执行python manage.py makemigrations和python manage.py migrate把表结构同步到数据库。4.3 数据生成脚本的编写要点我专门写了一个generate_mock_data.py脚本放在项目根目录通过python manage.py shell generate_mock_data.py方式运行。脚本的核心逻辑是import random from datetime import datetime, timedelta from users.models import UserProfile from behavior.models import BehaviorRecord, ContextSnapshot CATEGORY_WEIGHTS [0.15, 0.30, 0.20, 0.15, 0.10, 0.10] BEHAVIOR_WEIGHTS [0.70, 0.05, 0.10, 0.05, 0.05, 0.05] def gen_user(): return User.objects.create(usernamefmock_user_{random.randint(1000,9999)}) def gen_record(user, ts, is_teen_mode): category random.choices(range(6), weightsCATEGORY_WEIGHTS)[0] behavior_type random.choices(range(6), weightsBEHAVIOR_WEIGHTS)[0] video_duration random.randint(60, 1800) # 青少年模式下完播率更高观看时长占比也略高 if is_teen_mode: watch_duration int(video_duration * min(1.0, random.gauss(0.8, 0.15))) is_completed random.random() 0.75 else: watch_duration int(video_duration * min(1.0, random.gauss(0.55, 0.2))) is_completed random.random() 0.35 return BehaviorRecord(...)留意青少年模式下参数为何不同——模拟数据不瞎造要建立在行为学假设上。青少年模式下内容经过筛选更符合其认知水平因此完播率和观看完成度理论上应该高于普通模式下的随机浏览这跟现实中青少年模式“精选内容”的特征一致。这种“有依据的假设”贯穿整个生成过程数据分布才经得起统计检验。生成完数据后跑几条BehaviorRecord.objects.count()、BehaviorRecord.objects.filter(trigger_time__date...).count()去验证数据落库情况。这里遇到过一个大坑批量生成时没有关闭django的auto_now属性导致trigger_time被自动覆盖为写入时刻所有记录的时间戳都是一样的。解决办法是生成记录时显式赋值trigger_timets并保证auto_now_addFalse。4.4 数据看板接口的实现在dashboard这个App中我按“指标卡片接口”、“趋势图接口”、“雷达图接口”来拆分视图函数不搞一个大而全的接口。每个视图只做一件事from django.http import JsonResponse from django.db.models import Count, Sum from django.utils import timezone from behavior.models import BehaviorRecord, ContextSnapshot def overview_metrics(request): today timezone.localdate() active_users ( BehaviorRecord.objects .filter(trigger_time__datetoday) .values(user_id) .distinct() .count() ) total_records BehaviorRecord.objects.count() avg_watch BehaviorRecord.objects.aggregate(avg_durSum(watch_duration) / Count(id)) teen_rate ( ContextSnapshot.objects.filter(is_teen_modeTrue).count() * 100.0 / ContextSnapshot.objects.count() ) return JsonResponse({ active_users: active_users, total_records: total_records, avg_watch_duration: round(avg_watch[avg_dur], 2), teen_mode_rate: round(teen_rate, 2), })口径一致性问题我踩过坑。比如“今日活跃用户数”如果行为表里没有当天记录但用户登录过系统算不算活跃这里我明确口径活跃 当天至少触发过一条行为记录的用户并在接口注释里写清楚。口径不一致导致前后端数据对不上是开发调试中最浪费时间的问题之一。前端模板用一个简易的dashboard.html通过fetch获取JSON渲染。fetch(/dashboard/api/overview_metrics/) .then(response response.json()) .then(data { document.getElementById(activeUsers).innerText data.active_users; document.getElementById(avgWatchDuration).innerText data.avg_watch_duration s; document.getElementById(teenModeRate).innerText data.teen_mode_rate %; });4.5 可视化图表的前端集成图表部分我以最常用的ECharts折线图为例// 引入 ECharts 主模块 import * as echarts from echarts; let chart echarts.init(document.getElementById(trendChart)); fetch(/dashboard/api/hourly_trend/?date2025-01-10) .then(res res.json()) .then(data { chart.setOption({ tooltip: { trigger: axis }, legend: { data: [青少年模式, 普通模式] }, xAxis: { type: category, data: data.hours }, yAxis: { type: value }, series: [ { name: 青少年模式, type: line, smooth: true, data: data.teen_counts }, { name: 普通模式, type: line, smooth: true, data: data.normal_counts } ] }); });这部分要测试的点在于ECharts的响应式重绘tab切换或者窗口缩放时图表得调用chart.resize()否则会出现留白或挤压。我见过好多项目答辩现场演示切tab后图表变形这种细节最影响评价。4.6 django Admin后台的二次定制django自带Admin是个宝很多同学只用默认样式没有发光。青少年模式使用情况分析系统的数据管理后台我推荐花半小时做简单定制在admin.py中admin.register(BehaviorRecord) class BehaviorRecordAdmin(admin.ModelAdmin): list_display (id, user, behavior_type, content_category, watch_duration, is_completed, trigger_time) list_filter (behavior_type, content_category, is_completed, trigger_time) search_fields (user__username,) date_hierarchy trigger_timedate_hierarchy是一个非常实用的钻取工具自动生成“年-月-日”的下钻链接筛选数据效率暴增。这一步做完系统就有了一个可用的后台管理界面也算是毕设文档里的一处亮点。5. 常见问题与排查技巧实录5.1 ORM查询性能坑N1查询问题刚开始统计分析时我写了类似下面的代码for record in BehaviorRecord.objects.filter(user_id1): print(record.context.is_teen_mode)每访问一个关联对象数据库就执行一次新的查询5000条记录下来卡到页面超时。优化方式是使用select_relatedrecords BehaviorRecord.objects.filter(user_id1).select_related(context)这会把两张表的JOIN结果一次性加载到内存查询次数从N1降到1。类似的还有prefetch_related适用于多对多和反向外键。这类优化技巧在项目性能优化章节里写一段能有效体现实操深度。5.2 动态字段排序导致的注入风险在使用表头排序功能时如果不加过滤直接把用户传入的排序字段拼进order_by会有产生异常的风险sort_field request.GET.get(sort, trigger_time) records BehaviorRecord.objects.order_by(sort_field)排序字段白名单一定要限制ALLOWED_SORT {time: trigger_time, duration: watch_duration, category: content_category} sort_key ALLOWED_SORT.get(request.GET.get(sort), trigger_time) records BehaviorRecord.objects.order_by(sort_key)虽然这不是严格的SQL注入django ORM的参数化机制会保护一部分但动态字段名不校验传个不存在的字段可能直接抛异常浪费排查时间。5.3 数据可视化时的NaN值处理统计均值时如果数据库中某类别的记录数为0聚合结果会出现None除以0的情况前端直接显示NaN图表断线。我的处理方式是在视图层统一兜底def safe_avg(dividend, divisor): return round(dividend / divisor, 2) if divisor else 0这个“数据兜底意识”很关键写接口时不要假设数据永远是完整的要对缺失情况有预案。5.4 青少年模式判断的口径不一问题“青少年模式开启率”到底是算“当前处于模式的用户数占比”还是“每次行为发生时处于模式的记录占比”两种算法结果可能差出10个百分点。查阅了不少公开资料后我采用按“记录占比”算因为这个口径更能反应用户真实使用过程中的模式覆盖情况。所有涉及比例计算的接口统一萃取成一个工具函数避免dashboard和mobile接口各算一遍数值不一致。5.5 时间分组查询的时区陷阱django的USE_TZ True配置下数据库存的是UTC时间而前端展示需要北京时间。如果直接按trigger_time__hour分组你会发现“深夜高峰”被记到了前一天下午前后端数据完全对不上。建议做法是from django.db.models.functions import TruncHour from django.utils import timezone from datetime import timedelta local_tz timezone.get_current_timezone() BehaviorRecord.objects.annotate( local_hourTruncHour(trigger_time timedelta(hours8)) )这里我图省事直接加了8小时严谨做法是用django.utils.timezone.localtime()转换但在聚合QuerySet中操作起来更繁琐直接加固定偏移对北京时间环境下的演示项目可以接受。这些时间处理的细节如果没经验能卡你两三个小时。6. 项目复盘与可扩展方向这套系统完整做下来本质上你已经把django从模型设计到视图编写从数据分析到可视化呈现从性能优化到安全控制全过了一遍这些东西可以原封不动地移植到企业里的运营数据分析后台。我个人在实际操作中的体会是毕业设计项目代码量不重要重要的是让每个模块都“自己觉得经得起追问”。比如“为什么用线性回归”不能答“因为别人都用”要能从可解释性、复杂度、适合小数据量三个维度说出理由“为什么选ECharts”不能只说“好看”要说出ECharts对时空数据可视化的生态优势。这些“为什么”累积起来就是一个有深度的项目。最后再分享一个小技巧项目说明文档里建议画一张“用户使用流程图”——从用户进入青少年模式到内容推荐到行为触发再到数据采集、分析、展示的整体时序。这张图不必用多高级的工具画逻辑清楚比画得精美重要得多因为答辩老师80%的问题都会顺着这张图展开。如果时间充裕可以再往上扩展两个方向一个是接入django-channels做WebSocket实时推送让看板数据每隔几秒自动刷新不用手动点浏览器刷新另一个是加一个PDF报表导出功能生成每日数据分析报告这在实际工作中是真实需求。这个选题最终的落点是谁看了你的系统都能信服“数据分析不是炫技是把数据变成行动依据”。能做到这一点你的毕设就成功了。
返回列表