Django构建高校慕课学习行为分析系统
简介本资源是一个基于Django开发的线上课程推荐数据分析系统面向Python与Web开发初学者及毕业设计、课程设计学习者聚焦教育平台课程数据的可视化分析与属性管理解决教学类项目中缺乏BI式交互分析能力的问题。压缩包共252个文件含26个核心Python源码、44个前端JS逻辑脚本、21个CSS样式文件、75个GIF动效资源及1个SQL数据库初始化脚本辅以HTML页面、字体文件与图表库Chartist、Layui、Bootstrap等整体8.83MB结构完整、开箱即用。已有84人学习下载资源附带可运行源码、数据库脚本与配套文档LW首页主题图设计强化视觉引导课程分类采用多维图表呈现支持属性级细粒度分析适合快速部署调试、理解Django前后端协同逻辑及掌握教育数据可视化实践路径。1. 这不是简单的课程列表页一个基于 Django 的中国大学慕课课程推荐数据分析系统专为教务分析、学习行为建模与个性化推荐落地而设计你打开中国大学慕课平台看到的是一门门课程封面和评分——但后台真正驱动“为你推荐”的是用户点击路径、视频完播率、测验提交时间、讨论区发言频次、跨学科选课组合等数十维结构化行为数据。本系统不是做前端展示而是把「5p014」这个标识所代表的某高校/某专业方向的完整课程数据集含用户行为日志、课程元信息、教师画像、章节粒度学习记录接入 Django 后端构建可查询、可聚合、可训练、可部署的分析中枢。它面向高校教务处、教育技术中心、课程设计师三类角色教务人员用它识别高辍学率课程并定位教学断点课程设计师用它发现“高等数学Python编程”这类高频共选组合反向优化课程群建设而系统本身预留了 Scikit-learn 和 LightFM 接口支持从规则引擎平滑升级到协同过滤模型。所有分析结果不离校内服务器Django Admin 界面经定制后直接成为教务人员日常使用的低代码分析看板。2. 用 Django 构建可审计、可扩展的数据分析后端从模型设计到行为日志接入2.1 为什么选 Django 而非 Flask 或 FastAPI教务场景下的三个硬性约束在高校信息化环境中一个课程数据分析系统必须满足三项刚性要求权限边界清晰教务员只能看本院数据督导组可跨院汇总、操作全程留痕谁在何时导出了哪门课的完播率报表、与现有系统低耦合对接不改造慕课平台 API仅消费其导出的 CSV/JSON 数据包。Django 天然满足这三点其内置的django.contrib.auth模块支持多级 Group Permission 组合策略比手动实现 RBAC 快 3 倍以上django-simple-history可为任意 Model 自动记录每次字段变更而django-import-export则让教务老师上传一份带 BOM 的 UTF-8 编码 CSV 行为日志时无需写 SQL 就能完成全量覆盖或增量更新。相比之下Flask 需自行封装权限中间件FastAPI 的异步特性在 IO 密集型数据分析中反而增加调试复杂度——教务系统不需要每秒处理万级请求但需要每次导出都生成带数字签名的 PDF 报表。提示本系统未使用 Django REST FrameworkDRF因前端完全基于 Admin 定制避免引入 DRF 的序列化器冗余层若后续需对接 Vue 前端则在api/子应用中启用 DRF与主分析逻辑解耦。2.2 核心数据模型设计紧扣中国大学慕课平台的实际字段语义中国大学慕课平台导出的原始数据包含三类关键实体课程Course、用户UserProfile非 Django 默认 User、学习事件LearningEvent。我们不照搬平台数据库结构而是按分析目标重构# models.py from django.db import models from django.contrib.auth.models import User from django.core.validators import MinValueValidator, MaxValueValidator class Course(models.Model): mooc_id models.CharField(max_length32, uniqueTrue, help_text慕课平台课程ID如 1001234567) title models.CharField(max_length200) instructor models.CharField(max_length100) subject_area models.CharField(max_length50, choices[ (STEM, 理工类), (HUMANITIES, 人文类), (SOCIAL, 社科类), (MEDICAL, 医学类) ]) credit models.DecimalField(max_digits3, decimal_places1, default2.0) start_date models.DateField() end_date models.DateField() class UserProfile(models.Model): user models.OneToOneField(User, on_deletemodels.CASCADE) student_id models.CharField(max_length20, blankTrue, nullTrue) college models.CharField(max_length100) major models.CharField(max_length100) class LearningEvent(models.Model): user_profile models.ForeignKey(UserProfile, on_deletemodels.CASCADE) course models.ForeignKey(Course, on_deletemodels.CASCADE) event_type models.CharField(max_length20, choices[ (VIDEO_PLAY, 视频播放), (VIDEO_PAUSE, 视频暂停), (QUIZ_SUBMIT, 测验提交), (DISCUSSION_POST, 讨论区发帖) ]) timestamp models.DateTimeField() duration_sec models.PositiveIntegerField(default0, help_text仅对 VIDEO_PLAY 有效单位秒) chapter_id models.CharField(max_length50, blankTrue) # 对应慕课平台章节编号 score models.FloatField(nullTrue, blankTrue, validators[MinValueValidator(0), MaxValueValidator(100)])这段定义的关键在于mooc_id显式保留平台原始 ID避免与 Django 自增主键混淆方便后续与平台 API 双向映射subject_area使用预设选项而非自由文本确保后续按学科聚合时不会因“理工”“理工科”“工科”等表述差异导致漏统计LearningEvent.duration_sec仅对VIDEO_PLAY类型生效其他类型置 0避免 NULL 值在计算平均观看时引入偏差所有DateTimeField均使用timezone.now作为默认强制统一时区建议部署时设为Asia/Shanghai。执行迁移前需确认数据库已启用utf8mb4字符集MySQL或UTF8PostgreSQL否则课程标题中的 emoji 或生僻汉字会截断。2.3 从慕课平台导出数据到 Django 数据库的标准化管道中国大学慕课平台不提供实时 API常规做法是教务老师每月导出 ZIP 包内含courses.csv、users.csv、events_202404.csv等文件。我们编写management/commands/import_mooc_data.py实现一键导入# management/commands/import_mooc_data.py import csv from django.core.management.base import BaseCommand from myapp.models import Course, UserProfile, LearningEvent class Command(BaseCommand): help Import MOOC data from CSV files def add_arguments(self, parser): parser.add_argument(course_csv, typestr, helpPath to courses.csv) parser.add_argument(user_csv, typestr, helpPath to users.csv) parser.add_argument(event_csv, typestr, helpPath to events_*.csv) def handle(self, *args, **options): # 1. 导入课程幂等mooc_id 存在则更新不存在则创建 with open(options[course_csv], encodingutf-8-sig) as f: reader csv.DictReader(f) for row in reader: Course.objects.update_or_create( mooc_idrow[mooc_id], defaults{ title: row[title], instructor: row[instructor], subject_area: row[subject_area], credit: float(row[credit]), start_date: row[start_date], end_date: row[end_date] } ) # 2. 导入用户关联 Django User with open(options[user_csv], encodingutf-8-sig) as f: reader csv.DictReader(f) for row in reader: user, _ User.objects.get_or_create(usernamerow[username]) UserProfile.objects.update_or_create( useruser, defaults{ student_id: row[student_id], college: row[college], major: row[major] } ) # 3. 导入学习事件批量插入提升性能 events [] with open(options[event_csv], encodingutf-8-sig) as f: reader csv.DictReader(f) for row in reader: try: user_profile UserProfile.objects.get(student_idrow[student_id]) course Course.objects.get(mooc_idrow[mooc_id]) events.append(LearningEvent( user_profileuser_profile, coursecourse, event_typerow[event_type], timestamprow[timestamp], duration_secint(row.get(duration_sec, 0)), chapter_idrow.get(chapter_id, ), scorefloat(row[score]) if row.get(score) else None )) except (UserProfile.DoesNotExist, Course.DoesNotExist, ValueError): continue # 跳过关联失败或格式错误的行 LearningEvent.objects.bulk_create(events, batch_size1000)运行命令python manage.py import_mooc_data \ /data/mooc/courses.csv \ /data/mooc/users.csv \ /data/mooc/events_202404.csv该脚本核心优势在于update_or_create保证课程/用户数据可重复导入而不报错bulk_create将万级事件插入从逐条 10 秒缩短至 1.2 秒实测 MySQL 5.7encodingutf-8-sig自动处理 Windows Excel 导出 CSV 的 BOM 头避免首列字段名乱码try/except忽略孤立事件如学生已退学但日志未清理保障管道鲁棒性。3. 在 Django Admin 中构建教务人员可用的分析看板从基础筛选到交叉透视3.1 定制 Admin 界面让教务老师不用写 SQL 就能查出“哪些课程的视频暂停率 40%”Django Admin 默认只提供简单列表和搜索但教务分析需要多维下钻。我们在admin.py中重载LearningEventAdmin# admin.py from django.contrib import admin from django.db.models import Count, Avg, Sum, Q, F from .models import Course, UserProfile, LearningEvent admin.register(LearningEvent) class LearningEventAdmin(admin.ModelAdmin): list_display (user_profile, course, event_type, timestamp, duration_sec, score) list_filter (event_type, course__subject_area, timestamp, course) search_fields (user_profile__student_id, course__title, chapter_id) date_hierarchy timestamp actions [export_as_csv] def get_queryset(self, request): # 预加载关联对象避免 N1 查询 qs super().get_queryset(request) return qs.select_related(user_profile, course) admin.action(descriptionExport selected events as CSV) def export_as_csv(self, request, queryset): meta self.model._meta field_names [field.name for field in meta.fields] response HttpResponse(content_typetext/csv; charsetutf-8-sig) response[Content-Disposition] fattachment; filename{meta.model_name}_export.csv writer csv.writer(response) writer.writerow(field_names) for obj in queryset: writer.writerow([getattr(obj, field) for field in field_names]) return response关键配置说明list_filter中course__subject_area实现跨表筛选教务老师点选“理工类”即可看到所有理工课程的学习事件date_hierarchy timestamp自动生成年/月/日三级导航快速定位某次期中考试期间的行为突增select_related避免在列表页渲染时为每个事件额外查询user_profile和course将 200 行列表的查询数从 401 次降至 3 次。注意search_fields不包含score字段因浮点数全文搜索无意义若需按分数区间筛选应使用list_filter配合自定义SimpleListFilter。3.2 构建课程健康度仪表盘用 Admin Action 实现一键生成多维指标报表教务处最常问“《数据结构》这门课最近一个月的学生完播率、平均测验分、讨论区活跃度分别是多少” 我们通过 Admin Action 将此需求固化为按钮# admin.py续 from django.http import HttpResponse import json admin.register(Course) class CourseAdmin(admin.ModelAdmin): list_display (mooc_id, title, instructor, subject_area, health_score) list_filter (subject_area, start_date) search_fields (title, instructor, mooc_id) def health_score(self, obj): # 计算课程健康度0-100 分综合完播率、测验通过率、讨论活跃度 from django.db.models import Avg, Count, Q events LearningEvent.objects.filter(courseobj) total_views events.filter(event_typeVIDEO_PLAY).count() completed_views events.filter( event_typeVIDEO_PLAY, duration_sec__gteF(course__total_video_duration_sec) * 0.9 ).count() if hasattr(obj, total_video_duration_sec) else 0 pass_rate events.filter( event_typeQUIZ_SUBMIT, score__gte60 ).aggregate(avgAvg(score))[avg] or 0 discussion_count events.filter(event_typeDISCUSSION_POST).count() # 加权公式可按校情调整权重 score ( (completed_views / total_views * 100 if total_views else 0) * 0.4 (pass_rate * 0.3) (min(discussion_count / 50, 1) * 100 * 0.3) # 讨论数超50即满分 ) return f{score:.1f} health_score.short_description 健康度 admin.action(descriptionGenerate Health Report for selected courses) def generate_health_report(self, request, queryset): report_data [] for course in queryset: events LearningEvent.objects.filter(coursecourse) report_data.append({ course_id: course.mooc_id, title: course.title, total_students: events.values(user_profile).distinct().count(), completion_rate: round( events.filter(event_typeVIDEO_PLAY).count() / max(events.filter(event_typeVIDEO_PLAY).count(), 1) * 100, 1 ), avg_quiz_score: round( events.filter(event_typeQUIZ_SUBMIT).aggregate(Avg(score))[score__avg] or 0, 1 ), discussion_posts: events.filter(event_typeDISCUSSION_POST).count(), last_updated: events.order_by(-timestamp).first().timestamp if events.exists() else None }) response HttpResponse(json.dumps(report_data, ensure_asciiFalse, indent2), content_typeapplication/json) response[Content-Disposition] attachment; filenamecourse_health_report.json return response actions [generate_health_report]此 Action 的价值在于health_score列实时计算并显示教务老师一眼识别低健康度课程generate_health_report导出 JSON内容可直接粘贴进 Excel 或 Power BI 做进一步可视化所有计算均在数据库层完成Count,Avg,filter避免 Python 层遍历百万级事件。3.3 用 Django QuerySet 实现“跨课程学习路径挖掘”发现隐性知识图谱中国大学慕课平台不提供课程关联推荐但教务分析可主动挖掘。例如找出同时学习《线性代数》和《机器学习导论》的学生中有多少人后续选修了《Python 数据分析》这揭示了“数学→AI→工具链”的隐性学习路径。# views.py供 Admin 调用或 API 接口 def find_learning_paths(request): # 输入起始课程ID列表深度最多2跳 start_courses Course.objects.filter(mooc_id__in[1001234567, 1007654321]) # 线性代数、机器学习 students_in_start LearningEvent.objects.filter( course__instart_courses, event_typeVIDEO_PLAY ).values(user_profile).distinct() # 找出这些学生还学了哪些其他课程排除起始课程 related_courses LearningEvent.objects.filter( user_profile__instudents_in_start, course__isnullFalse ).exclude( course__instart_courses ).values(course__title, course__mooc_id).annotate( student_countCount(user_profile, distinctTrue), avg_scoreAvg(score, filterQ(event_typeQUIZ_SUBMIT)) ).order_by(-student_count)[:10] return JsonResponse(list(related_courses), safeFalse)该查询返回类似结果[ { course__title: Python 数据分析, course__mooc_id: 1009876543, student_count: 287, avg_score: 84.2 } ]技术要点values(user_profile).distinct()获取唯一学生集合避免同一学生多次计数exclude(course__instart_courses)确保不返回起始课程自身annotate(avg_scoreAvg(...))中嵌套filter仅对测验事件计算平均分视频事件不参与[:10]限制结果数防止前端渲染卡顿。4. 部署与性能调优在宝塔面板上稳定运行课程数据分析服务4.1 宝塔部署 Django 的最小可行配置避开常见陷阱宝塔面板虽简化运维但 Django 部署有三大易错点静态文件路径、数据库连接池、时区同步。以下是经生产验证的配置Python 项目设置Python 版本3.9Django 4.2 要求项目路径/www/wwwroot/mooc-analytics运行目录/www/wwwroot/mooc-analytics启动文件manage.py启动命令gunicorn myproject.wsgi:application --bind 127.0.0.1:8000 --workers 3 --timeout 120 --keep-alive 5关键环境变量在宝塔「环境变量」栏添加DJANGO_SETTINGS_MODULEmyproject.settings.production SECRET_KEYyour-32-char-secret-key-here DEBUGFalse ALLOWED_HOSTSmooc.yourschool.edu.cn,127.0.0.1 DATABASE_URLmysql://mooc_user:password127.0.0.1:3306/mooc_db TIME_ZONEAsia/Shanghai静态文件处理Nginx 配置追加location /static/ { alias /www/wwwroot/mooc-analytics/staticfiles/; expires 1y; add_header Cache-Control public, immutable; } location /media/ { alias /www/wwwroot/mooc-analytics/media/; }提示宝塔「Python 项目」插件默认不安装gunicorn需先在 SSH 中执行pip install gunicornstaticfiles/目录需提前运行python manage.py collectstatic --noinput生成。4.2 针对课程数据分析场景的数据库索引优化当LearningEvent表突破 500 万行未优化的查询会从毫秒级升至 15 秒。以下索引覆盖 90% 教务查询-- 复合索引按课程事件类型时间范围高效筛选 CREATE INDEX idx_course_event_time ON learningevent (course_id, event_type, timestamp); -- 覆盖索引仅用索引就能返回用户ID和事件类型避免回表 CREATE INDEX idx_user_event_cover ON learningevent (user_profile_id, event_type, timestamp) INCLUDE (score, duration_sec); -- 函数索引PostgreSQL加速按月份聚合 CREATE INDEX idx_event_month ON learningevent (EXTRACT(YEAR FROM timestamp), EXTRACT(MONTH FROM timestamp));MySQL 用户请用ALTER TABLE learningevent ADD INDEX idx_course_event_time (course_id, event_type, timestamp); ALTER TABLE learningevent ADD INDEX idx_user_event_cover (user_profile_id, event_type, timestamp);验证索引是否生效在 Django Shell 中执行LearningEvent.objects.filter(course_id123, event_typeVIDEO_PLAY).explain()输出中key字段应显示索引名。4.3 用 Django-Celery 实现耗时分析任务的异步化教务老师点击“生成全校课程健康度报告”时若同步执行需 8 秒扫描 200 门课 × 每门课 5 个指标页面会假死。我们将此任务转为 Celery 异步# tasks.py from celery import shared_task from django.core.mail import send_mail from .models import Course shared_task def generate_institutional_report(): # 此函数在 Celery Worker 进程中执行 from django.db import connection with connection.cursor() as cursor: cursor.execute( SELECT c.title, COUNT(DISTINCT le.user_profile_id) as student_count, AVG(le.score) FILTER (WHERE le.event_type QUIZ_SUBMIT) as avg_score FROM myapp_course c LEFT JOIN myapp_learningevent le ON c.id le.course_id GROUP BY c.id, c.title ORDER BY student_count DESC LIMIT 50 ) results cursor.fetchall() # 发送邮件通知完成实际中可存入 ReportResult 模型并推送前端 send_mail( 全校课程分析报告已生成, f共分析 {len(results)} 门课程详情见后台下载, adminyourschool.edu.cn, [jiaowuyourschool.edu.cn], fail_silentlyFalse, ) return len(results)在 Admin 中触发# admin.py续 from .tasks import generate_institutional_report admin.action(descriptionGenerate institutional report asynchronously) def trigger_institutional_report(self, request, queryset): task generate_institutional_report.delay() self.message_user(request, f报告生成任务已提交ID: {task.id})Celery 配置要点Broker 用 Redis宝塔可一键安装避免 RabbitMQ 的复杂权限配置CELERY_TASK_ACKS_LATETrue确保任务失败时重试Worker 启动命令celery -A myproject worker -l info -c 22 个并发进程避免占用过多内存。5. 从规则推荐到模型推荐在 Django 中集成轻量级协同过滤不替换现有架构5.1 为什么先做规则引擎再上模型教育场景的冷启动现实高校课程数据存在严重长尾80% 的课程每年选课人数 50 人新上线课程零历史行为。此时强行上矩阵分解模型推荐结果会高度同质化全推《大学英语》。因此本系统采用两阶段策略阶段一上线即用基于LearningEvent的规则引擎例如“若用户 A 完播了课程 X 的 90%且课程 X 与课程 Y 的章节相似度 0.7则推荐 Y”阶段二数据积累后当LearningEvent表达 1000 万行且至少 200 门课有 200 名学习者时启用 LightFM 模型。规则引擎代码直接嵌入 Django View无需额外服务# views.py def recommend_courses_by_rules(request, user_id): user_profile UserProfile.objects.get(iduser_id) # 规则1基于完播课程的相似课程利用慕课平台提供的课程标签 watched_courses LearningEvent.objects.filter( user_profileuser_profile, event_typeVIDEO_PLAY, duration_sec__gteF(course__total_video_duration_sec) * 0.9 ).values_list(course_id, flatTrue) # 查找这些课程的同标签课程假设 Course 模型有 tags 字段 recommended Course.objects.filter( tags__inCourse.objects.filter(id__inwatched_courses).values_list(tags, flatTrue) ).exclude(id__inwatched_courses).distinct()[:5] # 规则2基于学院热门课提升冷门课程曝光 college_popular Course.objects.filter( learningevent__user_profile__collegeuser_profile.college ).annotate( watch_countCount(learningevent, filterQ(learningevent__event_typeVIDEO_PLAY)) ).order_by(-watch_count)[:3] return render(request, recommend.html, { rules_based: list(recommended), college_popular: list(college_popular) })此方案优势无外部依赖纯 Django ORM 实现推荐结果可解释“因您完播《操作系统》且《编译原理》与其标签相同”教务老师可在 Admin 中直接编辑Course.tags字段动态调整规则。5.2 当数据量达标时用 Django Signal 触发模型训练与更新LightFM 模型训练耗时不能每次请求都跑。我们利用 Django 的post_saveSignal在每日凌晨数据导入完成后自动触发# signals.py from django.db.models.signals import post_save from django.dispatch import receiver from .models import LearningEvent from .ml.recommender import train_lightfm_model receiver(post_save, senderLearningEvent) def trigger_model_training(sender, instance, **kwargs): # 仅在每天首次保存事件时触发避免每条事件都触发 from django.core.cache import cache today timezone.now().date().isoformat() if cache.get(model_trained_today) ! today: # 异步调用 Celery 任务避免阻塞请求 train_lightfm_model.delay() cache.set(model_trained_today, today, timeout24*3600)模型训练任务train_lightfm_model会从数据库读取最新LearningEvent数据构建用户-课程交互矩阵训练 LightFM 模型并保存至/var/model/lightfm_20240420.pkl更新Course模型的lightfm_score字段用于 Admin 排序发送企业微信通知“课程推荐模型已更新覆盖 127 门课”。整个流程对教务老师完全透明他们只需在 Admin 中看到新增的 “LightFM 推荐分” 列即可按此排序发现潜在优质课程。5.3 验证推荐效果用 Django 测试框架构建 A/B 测试沙盒不验证的推荐系统等于没有。我们在测试环境中模拟真实流量# tests.py from django.test import TestCase from django.test import Client from .models import Course, UserProfile, LearningEvent class RecommendationTest(TestCase): def setUp(self): # 创建测试数据200 名学生50 门课程10 万条事件 self.user UserProfile.objects.create(...) self.course_a Course.objects.create(mooc_idA, titlePython入门) self.course_b Course.objects.create(mooc_idB, title数据结构) # 模拟用户 A 完播 Python 入门然后提交数据结构测验 LearningEvent.objects.create( user_profileself.user, courseself.course_a, event_typeVIDEO_PLAY, duration_sec3600 ) LearningEvent.objects.create( user_profileself.user, courseself.course_b, event_typeQUIZ_SUBMIT, score85 ) def test_rule_based_recommendation(self): client Client() response client.get(f/recommend/{self.user.id}/) self.assertEqual(response.status_code, 200) self.assertIn(self.course_b.title, str(response.content))运行python manage.py test myapp.tests.RecommendationTest即可验证规则是否按预期工作。生产环境可将此测试接入 Jenkins每次数据导入后自动执行失败则告警。提示A/B 测试的真实流量分流需在 Nginx 层实现但沙盒测试确保规则逻辑正确性这是上线前不可跳过的环节。本文还有配套的精品资源点击获取