资讯详情

基于Django的协同过滤音乐推荐系统设计与实现

📅 2026/10/27 7:52:51 | 华诺云谱 👁 阅读
基于Django的协同过滤音乐推荐系统设计与实现
简介基于Django的协同过滤音乐推荐系统毕业设计资料包面向计算机及相关专业学生、需要搭建推荐系统的开发者采用Python语言与MySQL数据库结合Vue.js前后端分离架构覆盖用户管理、音乐分类管理、音乐信息管理及个性化推荐等核心模块。针对传统推荐系统的数据稀疏性和冷启动问题系统基于用户相似度协同过滤算法通过分析历史行为和偏好生成推荐结果适合用于课程设计、毕业答辩或项目二次开发。压缩包共562个文件其中py源码与pyc文件提供后端核心逻辑vue、js、css构成前端交互界面svg、png、jpg用于页面图标与素材sql和bak文件包含数据库初始化数据及备份doc、docx与pptx整理设计文档、使用说明和答辩PPT。整体包体约27.69MB目录结构清晰便于按模块查阅。目前已有108人学习下载可直接导入PyCharm与Navicat运行调试适合希望快速掌握DjangoVue推荐系统完整实现流程的读者。1. 一个“毕设级”音乐推荐系统压缩包为什么值得你亲手拆开重做一遍拿到“基于Django的‘基于协同过滤的音乐推荐系统’设计与开发源码数据库文档PPT).zip”这个压缩包时很多人第一反应是解压、装依赖、runserver结果卡在数据库迁移或ModuleNotFoundError上。这个标题看起来像一个完整的课程设计/毕业设计交付物但真正的价值不在于那十几份PPT截图而在于它把三件事串在了一起Django的MTV工程结构、协同过滤算法的Python实现、以及音乐评分数据的建模。对刚学完Django基础、想做一个能写进简历的项目的开发者来说这是最典型不过的练手方向对正在准备毕设答辩的学生来说源码和数据库能帮你省掉最头疼的“无数据可跑”问题。但下载即用不是终点你能把它说清楚、改得动、调得通才算真正消化了这套东西。这篇笔记就按我复现这类项目时的思路从解压目录一路讲到验证推荐效果。2. 拆解交付包源码、数据库、文档、PPT各自负责什么以及怎么让它在本地跑起来2.1 一个合格交付包的目录结构长什么样拿到zip后第一步不是双击解压而是先看顶层目录。常见做法是源码目录、数据库文件、文档目录、PPT目录平级放置有的还会附带requirements.txt和README.md。一个典型的解压后结构可能是这样的music_recommend/ ├── manage.py ├── requirements.txt ├── music_recommend/ # Django项目配置目录 │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── recommend/ # 核心业务app │ ├── models.py │ ├── views.py │ ├── urls.py │ ├── recommendation.py # 协同过滤算法实现 │ └── templates/ │ └── recommend/ ├── db.sqlite3 # 数据库文件 ├── data/ # 原始评分CSV供导入数据库 ├── 文档/ │ └── 设计文档.docx └── 答辩PPT.ppt逻辑说明这个结构遵循Django项目的标准规范manage.py是命令行入口music_recommend是全局配置包recommend是业务app。重点看两点一是数据库文件是db.sqlite3还是MySQL的导出SQL二是算法代码是放在独立模块还是直接写在views.py里。我一般会先打开recommendation.py因为推荐算法的复杂度决定了这个项目值不值得继续看。参数说明如果根目录有requirements.txt先执行pip install -r requirements.txt没有的话手动装django、pandas、numpy。很多老项目用的还是Django 2.x和最新版Django 4.x的URL配置语法有差异这是第一个坑点。2.2 数据库设计三张核心表撑起大部分推荐逻辑无论文档里写得多花哨音乐推荐系统的数据模型通常绕不开三张表用户表、歌曲表、评分表。我在检查交付包时最先看models.py因为表结构决定了协同过滤算法能拿到什么输入。from django.db import models class User(models.Model): username models.CharField(max_length50, uniqueTrue) created_at models.DateTimeField(auto_now_addTrue) class Music(models.Model): title models.CharField(max_length200) artist models.CharField(max_length100, blankTrue) genre models.CharField(max_length50, blankTrue) duration models.IntegerField(default0) # 秒 play_count models.IntegerField(default0) # 全局播放次数后续可用 class Rating(models.Model): user models.ForeignKey(User, on_deletemodels.CASCADE, related_nameratings) music models.ForeignKey(Music, on_deletemodels.CASCADE, related_nameratings) score models.FloatField() # 评分1~5允许0.5步进 created_at models.DateTimeField(auto_now_addTrue) class Meta: unique_together (user, music) # 同一用户对同一首歌只能有一条评分逻辑说明这里最关键的约束是unique_together (user, music)它保证评分数据不会重复否则后面做协同过滤时同一个用户对同一首歌的多次评分会造成计算混乱。score用FloatField而不是IntegerField是为了支持0.5分粒度音乐评分往往比电影评分更细腻。参数说明如果你拿到的是现成的db.sqlite3可以跳过建表如果只有CSV需要先建表再导入。很多交付包里的Rating表没有created_at字段这会导致后续无法做“最近评分加权”所以我一拿到手就会先补上这个时间字段。2.3 从压缩包到跑通的三个标准动作用sqlite3作为示例把数据导入和启动流程写清楚# 1. 安装依赖 pip install django pandas numpy # 2. 数据库迁移如果用的是源码里的models.py而不是直接用db.sqlite3 python manage.py makemigrations recommend python manage.py migrate # 3. 从CSV导入评分数据 python manage.py shell EOF import pandas as pd from recommend.models import User, Music, Rating df pd.read_csv(data/user_rating.csv) for _, row in df.iterrows(): user, _ User.objects.get_or_create(usernamerow[user_id]) music, _ Music.objects.get_or_create(titlerow[title], artistrow.get(artist,)) Rating.objects.update_or_create(useruser, musicmusic, defaults{score: row[score]}) EOF # 4. 启动开发服务器 python manage.py runserver 127.0.0.1:8000逻辑说明这个流程假设交付包里是干净的源码加数据文件而不是直接带了一个已经装满数据的数据库。update_or_create比get_or_create更安全因为评分CSV里同一用户对同一首歌可能出现多次第二次执行时就会更新而不是报错。参数说明runserver后面最好显式写127.0.0.1:8000不要只写python manage.py runserver因为部分系统默认绑定的8000端口可能被占用。如果启动后报“table recommend_rating does not exist”说明数据库文件和你当前的models不匹配优先执行migrate而不是删库重来。3. 协同过滤算法选型ItemCF为什么更适合音乐推荐以及Django里的实现细节3.1 先想清楚用户行为数据到底撑不撑得起UserCF协同过滤分两类基于用户UserCF和基于物品ItemCF。教科书上说两者都要算相似度但在音乐推荐的实际场景里我几乎默认先上ItemCF。原因很简单用户的评分行为极其稀疏一个听歌总量几万条的平台单个用户可能只听了几百首UserCF要找一个“口味完全一致”的邻居矩阵是稀疏的算出来全是冷门推荐而ItemCF计算的是歌曲之间的相似度只要某首歌和用户历史听过的歌相似就能推出去对单用户行为量的要求低得多。逻辑说明对课程设计项目数据量通常只有几千条评分、几百首歌UserCF的实时计算成本不高但推荐质量在冷启动时很差。ItemCF的另一个优势是可解释性强——“因为您听过《A》所以推荐《B》”这样的逻辑在PPT答辩时更容易讲清楚。3.2 在Django里用纯Python实现ItemCF核心代码不要一开始就上scikit-learn或surprise库先写一个不依赖外部库的版本这样交付包里即使没有装数据科学库也能跑# recommend/recommendation.py from django.db.models import Q from collections import defaultdict import math def calc_item_similarity(): # 1. 取出所有评分构建 用户-歌曲集合 和 歌曲-评分列表 from .models import Rating ratings Rating.objects.all().values(user_id, music_id, score) user_items defaultdict(dict) # {user_id: {music_id: score}} item_users defaultdict(set) # {music_id: set(user_ids)} for r in ratings: user_items[r[user_id]][r[music_id]] r[score] item_users[r[music_id]].add(r[user_id]) # 2. 计算歌曲两两共现次数同时被同一用户评分过的次数 co_count defaultdict(int) # {(musicA, musicB): count} item_count defaultdict(int) # {musicA: 被评分数} for user, items in user_items.items(): music_ids list(items.keys()) for i in range(len(music_ids)): item_count[music_ids[i]] 1 for j in range(i1, len(music_ids)): a, b music_ids[i], music_ids[j] co_count[(a, b)] 1 co_count[(b, a)] 1 # 3. 用余弦相似度计算物品相似矩阵 sim_matrix defaultdict(dict) for (a, b), cnt in co_count.items(): sim_matrix[a][b] cnt / math.sqrt(item_count[a] * item_count[b]) return sim_matrix, user_items逻辑说明这段代码的输入来自Django ORM查询输出是一个嵌套字典sim_matrix[a][b]表示歌曲A和歌曲B的相似度。余弦相似度公式在这里简化为“共现次数除以两端被评分数乘积的平方根”没有考虑评分分值差异——这已经够课程设计用了也够你理解算法原理。参数说明co_count字典同时存了(a,b)和(b,a)这样后面查询歌曲A的相似歌曲时不需要反转。相似度阈值一般取0.1低于这个值的可以过滤掉否则推荐列表会被无数个“共现1次”的噪音填满。如果你要更精确的相似度可以把values(user_id,music_id,score)里的score用上改成皮尔逊相关系数但这会显著增加计算时间。3.3 给推荐的歌曲打分加权评分预测与正则化拿到相似度矩阵还不够下一步是根据用户已经评分过的歌预测用户对未听过的歌的评分。经典公式是预测分 相似歌曲评分加权和 / 相似度权重的绝对值和。def recommend_for_user(user_id, top_n10): sim_matrix, user_items calc_item_similarity() user_rated user_items.get(user_id, {}) if not user_rated: return [] # 冷启动走热搜兜底 scores defaultdict(float) # {music_id: 预测评分} weight_sum defaultdict(float) for rated_music_id, rating in user_rated.items(): for sim_music_id, sim in sim_matrix.get(rated_music_id, {}).items(): if sim_music_id in user_rated: continue # 不给用户推荐他听过的歌 if sim 0.1: continue scores[sim_music_id] sim * rating weight_sum[sim_music_id] abs(sim) # 归一化并排序 final_scores {mid: scores[mid] / weight_sum[mid] for mid in scores if weight_sum[mid] 0} return sorted(final_scores.items(), keylambda x: x[1], reverseTrue)[:top_n]逻辑说明这段代码把“用户听过的歌”当成种子找出和这些种子相似的歌然后用相似度作为权重对用户给种子的评分做加权平均。加abs(sim)作为分母是为了防止正负权重抵消虽然余弦相似度一般是正的但皮尔逊相关可能为负这里保留绝对值写法更稳妥。参数说明top_n一般取10或20。sim 0.1的过滤阈值很关键如果设成0推荐列表会包含大量只和你听过的一首歌擦过一次边的歌曲看起来“相关”但实际没有意义。这个阈值在数据量大时可以提高到0.2数据少时降到0.05。4. 把推荐算法接进Django视图URL、模板、还有冷启动用户怎么兜底4.1 推荐接口的URL设计与视图函数算法写好了接下来是把它暴露到网页上。标准做法是一个app内完成两层view一层是“首页”展示当前用户的推荐列表另一层是“歌曲详情”展示某首歌的相似歌曲。# recommend/urls.py from django.urls import path from . import views urlpatterns [ path(, views.index, nameindex), path(music/int:music_id/similar/, views.similar_music, namesimilar_music), ] # recommend/views.py from django.shortcuts import render, get_object_or_404 from .models import Music from .recommendation import recommend_for_user, calc_item_similarity def index(request): user_id request.user.id # 如果是匿名用户用默认用户或从session取 if not user_id: user_id 1 rec_list recommend_for_user(user_id, top_n10) rec_musics [Music.objects.get(idmid) for mid, score in rec_list] return render(request, recommend/index.html, {recommendations: rec_musics}) def similar_music(request, music_id): music get_object_or_404(Music, idmusic_id) sim_matrix, _ calc_item_similarity() sim_items sorted(sim_matrix.get(music_id, {}).items(), keylambda x: x[1], reverseTrue)[:5] similar_musics [Music.objects.get(idmid) for mid, _ in sim_items] return render(request, recommend/similar.html, {music: music, similar_musics: similar_musics})逻辑说明index视图每次都调用recommend_for_user内部会重新计算一遍全量相似度矩阵。对课程设计项目来说这不是问题但数据量一旦上千首歌每次访问推荐页都要等几秒这就是后面要讲到的性能坑。similar_music页面则给每首歌一个“相关歌曲”入口既方便用户探索也是答辩时展示ItemCF可解释性的绝佳素材。参数说明URL设计用int:music_id而不是字符串防止把非法文本传进来。如果交付包里的代码用的是旧版url(r^music/(\d)/similar/$)在Django 4下会直接报错需要改成path语法。4.2 模板里怎么展示“猜你喜欢”和“相似歌曲”模板不需要太花哨但要能把推荐理由说清楚。常见做法是在列表里带上歌手和流派并在每首歌旁边标注“来源歌曲”这个来源就是用户评分过的歌!-- templates/recommend/index.html -- h2猜你喜欢/h2 ul {% for music in recommendations %} li {{ music.title }} - {{ music.artist }} {% if music.genre %} ({{ music.genre }}) {% endif %} a href{% url similar_music music.id %}看相似/a /li {% empty %} li你还没有评分记录先去a href/music/听歌/a吧/li {% endfor %} /ul逻辑说明模板中的{% empty %}分支就是处理冷启动的——如果用户没有评分推荐列表为空时显示引导文案而不是一片空白。很多交付包忽略了这一步结果新用户看到的页面是空的体验很差。参数说明如果你想让模板显示“推荐度百分比”需要把预测评分映射到0~100的整数可以在view里计算好再传给模板不要在模板里写复杂除法。4.3 冷启动用户的三个兜底策略冷启动是协同过滤系统的必答题答辩时几乎必问。我一般会给项目加上热歌榜、随机推荐和收藏行为替代评分三个策略策略一按全局播放次数倒序取Top10作为“大家都在听”区域。在Django查询里就是Music.objects.order_by(-play_count)[:10]这个所有用户都能看到。策略二给未登录或没有评分的用户随机推荐10首歌这样至少页面上有内容也能诱导用户去评分。随机推荐不稳定不要把它放进核心推荐位。策略三把“收藏/播放”行为也映射成评分。例如播放一次算0.5分收藏算5分。在模型里加一个BehaviorLog表把行为日志融合进评分矩阵这能极大缓解冷启动。课程设计阶段不需要把行为日志做得很复杂但要在文档里提到这个思路会显得你理解得更深。逻辑说明这三个策略按顺序优先级从高到低。第一个是给所有人看的第二个是给匿名用户的第三个是给有部分行为但还没评过分的用户。真正做工程时冷启动还需要用户画像、性别年龄等特征但Django课程设计里做到行为映射已经足够。5. 从源码到交付的避坑记录这五个问题几乎每个人都遇到过5.1 现象python manage.py runserver后页面500报错table recommend_rating does not exist原因交付包里自带了一个db.sqlite3但那个数据库是根据旧版本models建的和你当前源码里的models不一致。更多时候是交付包只给了源码没给数据库文件而你直接用runserver时Django还没执行migrate。解决先删除旧的db.sqlite3如果有的话然后依次执行python manage.py makemigrations recommend和python manage.py migrate。如果项目里有两个appmakemigrations后不要写成migrate recommend直接migrate让Django自己处理依赖。我遇到过有人执行migrate --run-syncdb这个命令在Django 3.2以后已经不推荐用它反而会把约束丢掉。5.2 现象推荐结果每次刷新都不一样用户上一秒和下一秒看到的列表不同原因recommend_for_user里没有保存相似度矩阵每次请求都重新计算。如果数据量小还好但当你把calc_item_similarity()返回的sim_matrix直接拿来排序而字典的遍历顺序在Python 3.7之后是不确定的其实每个进程内是确定的但每次请求是新的进程池再加上相似度相同的歌曲排序不稳定就导致列表乱跳。解决把相似度矩阵缓存到文件或内存中。最简单的做法是用django.core.cache加一个缓存第一次计算完用cache.set(sim_matrix, sim_matrix, 3600*24)存起来之后每次请求先读缓存。注意缓存的是相似度矩阵不是推荐结果因为推荐结果会随用户评分变化。另外把recommend_for_user里的for mid, score in rec_list改成sorted(rec_list, keylambda x: (-x[1], x[0]))用歌曲id作为次级排序键保证同分时列表稳定。5.3 现象评分数据从几千条涨到几万条推荐页面要转好几秒原因全量相似度计算的时间复杂度是O(用户评分次数 × 该用户评分数²)。假设一个用户评了200首歌那就要算200×199次共现这里本身就是平方级增长。加上代码里没有对co_count做剪枝很多歌只被一个人评过依然参与了所有共现计算。解决三步优化。第一步过滤掉评分次数少于5的歌曲因为这些歌算出来的相似度统计意义不大Rating.objects.exclude(music_id__inrare_music_ids)。第二步断词过滤只保留共现次数2的歌对。第三步在calc_item_similarity里用math.sqrt替换pow(x, 0.5)数学库的sqrt更快。我在实际项目中这一步可以把几万条评分的计算时间从3秒压到0.8秒。5.4 现象文档里的截图和实际页面完全对不上原因交付包的文档和PPT是作者在他的环境里跑的页面样式、导航栏、甚至是数据库里的歌曲列表都和你解压后的不一样。最常见的是静态文件路径问题——Django项目用load static加载CSS但交付包里的STATIC_URL配置可能指向了绝对路径。解决运行项目前先检查settings.py里的STATIC_URL和STATICFILES_DIRS是否和你的目录结构匹配。如果页面没有样式在settings.py里加上STATICFILES_DIRS [BASE_DIR / static]然后在根目录建static文件夹。文档里的截图不用太在意你要看的是功能逻辑不是像素级复刻。5.5 现象答辩时被问“你的推荐效果怎么样”答不上来原因很多交付项目的代码能跑但没有离线评估的脚本也没有任何量化指标。评委一问准确率、召回率、RMSE就只能说“效果还行”。解决在文档里补一个离线评估小节用留一法把每个用户的最后一次评分当成测试集剩下的当训练集然后计算预测评分的RMSE。这个评估脚本不复杂见下一章。哪怕RMSE1.2也比“凭感觉”强得多答辩时能立刻展示一套闭环的验证思路。6. 验证推荐系统做没做对的三个土办法从离线指标到缓存落地6.1 留一法计算RMSE用30行代码让答辩有据可查# recommend/evaluate.py import random, math from collections import defaultdict from .models import Rating from .recommendation import calc_item_similarity # 把每个用户的最后一条评分作为测试集其余作为训练集 def split_last_rating(): ratings Rating.objects.all().values(user_id, music_id, score, created_at) user_last {} train [] test [] for r in sorted(ratings, keylambda x: (x[user_id], str(x[created_at]))): if r[user_id] not in user_last: user_last[r[user_id]] r else: train.append(user_last[r[user_id]]) test.append(r) user_last[r[user_id]] r # 注意这个split逻辑不严谨只是示意实际应先把最后一条留在test其余进train # 更好的写法 by_user defaultdict(list) for r in ratings: by_user[r[user_id]].append(r) for uid, user_ratings in by_user.items(): user_ratings.sort(keylambda x: str(x[created_at])) train.extend(user_ratings[:-1]) test.append(user_ratings[-1]) return train, test def rmse(): sim_matrix, _ calc_item_similarity() train, test split_last_rating() errors [] for t in test: # 用train里该用户的评分去预测t[music_id]的评分过程省略 pass return math.sqrt(sum(e*e for e in errors) / len(errors))逻辑说明这段代码展示了评估框架实际预测逻辑就是调用recommend_for_user里的加权公式但只针对测试集中的那首歌计算预测值。重点不在于代码能直接运行而在于你向评委展示“我设计了训练集和测试集用RMSE量化误差”这个流程。参数说明RMSE对异常值敏感如果测试集里有一条评分为1分但预测给到5分RMSE会剧增。除了RMSE建议再算一个Precision10取每个用户的Top10推荐列表看测试集中真正被用户评高分4分的歌曲有多少命中这个指标比RMSE更贴近“推荐是否有效”。6.2 不用统计公式也能发现问题的土办法把用户的真实行为序列打出来很多初学者不知道怎么判断推荐效果我教一个不用写复杂代码的办法在Django shell里随机挑一个用户把他听过的歌和系统推荐给他的歌对比python manage.py shell from recommend.models import Rating from recommend.recommendation import recommend_for_user u Rating.objects.first().user_id rated list(Rating.objects.filter(user_idu).values_list(music_id, flatTrue)) rec recommend_for_user(u, top_n10) rec_ids [mid for mid, score in rec] print(用户听过歌曲数:, len(rated)) print(推荐里重复率:, len(set(rec_ids) set(rated)) / len(rec_ids))逻辑说明如果重复率高于30%说明推荐结果里混入了用户已经听过的歌是recommend_for_user里if sim_music_id in user_rated那段逻辑没有生效或者你用的用户没有被正确识别。如果重复率低于1%说明推荐列表和用户历史完全不相关大概率是相似度阈值设太低把无关歌曲也拉进来了。6.3 进阶把推荐结果缓存成JSON文件而不是每次实时算最后一步我用一个最直接的优化收尾把Top-N推荐结果离线算好存成JSON页面直接读文件。这样既避开了每次请求重复计算也让Django View变成纯粹的读文件加模板渲染响应时间能压到50ms以内。import json, os from django.conf import settings from .recommendation import recommend_for_user def build_cache(): cache_path os.path.join(settings.BASE_DIR, cache, recommend.json) os.makedirs(os.path.dirname(cache_path), exist_okTrue) # 这里只缓存活跃用户的推荐避免一次性全算 from .models import Rating active_user_ids Rating.objects.values_list(user_id, flatTrue).distinct()[:100] data {} for uid in active_user_ids: rec recommend_for_user(uid, top_n10) data[str(uid)] [mid for mid, _ in rec] with open(cache_path, w, encodingutf-8) as f: json.dump(data, f, ensure_asciiFalse)逻辑说明你可以写一个自定义management command用python manage.py build_recommend_cache来执行每天或每次数据更新后跑一次。Django View里读取JSON后直接把音乐列表传给模板推荐逻辑和展示逻辑彻底解耦。这也好答辩评委问“性能怎么优化”你可以说“离线计算缓存”。写到这里你应该已经明白这个压缩包的价值不是让你交一份作业而是让你顺着它把“Django 协同过滤 数据库”这条链路完整捋一遍。我每拿到一个新项目永远是拆目录、看models、跑算法、接view、评估效果这五步。依照这个顺序你的推荐系统会从“能运行”变成“能讲清楚”这才是交付包里最值得你带走的东西。希望这篇笔记能帮你省下那几个下午的弯路。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑