☰
基于Django的协同过滤音乐推荐系统实战解析
2026/10/7 17:02:01 网站建设 项目流程

简介:一份基于Django的“基于协同过滤的音乐推荐系统”毕业设计完整方案,面向计算机专业毕业生及推荐系统开发者。系统采用Python+Django构建后端,MySQL存储数据,前端使用Vue.js实现交互界面,包含用户管理、音乐分类、音乐信息管理及个性化推荐等模块,通过基于用户相似度的协同过滤算法解决数据稀疏性与冷启动问题,为音乐平台提供了可落地的智能推荐参考实现。压缩包共562个文件,大小约27.69MB,主要文件类型包括后端Python源码(.py)、前端Vue组件(.vue)与JavaScript脚本(.js)、数据库SQL脚本(.sql)、项目文档(.docx/.doc)与答辩PPT(.pptx),以及启动配置脚本等,目录结构清晰,便于按模块学习与二次开发。目前已有108人浏览学习。借助此资源包,读者可快速部署运行系统,深入理解协同过滤推荐算法在真实项目中的集成方式,同时可借鉴其前后端分离架构、数据库设计与文档组织,作为毕业设计、课程设计或求职作品的有力参考。

1. 为什么说这个协同过滤音乐推荐系统值得你下载

每年毕业设计一到三月,就会有一批人被「推荐系统」这类课题卡住:算法推导能看懂,打开别人的源码却不知道文件为什么这么分;Django 的 Hello World 会写,但把用户登录、音乐管理、收藏行为、推荐列表串成一个完整工程就完全没头绪。这份「基于 Django 的协同过滤音乐推荐系统」就是拿来当脚手架用的——它把基于用户和基于物品两种协同过滤都实现了,带完整的数据库文件、设计文档和答辩 PPT,项目结构按 Django 的 MVT 规范拆开,适合做毕设或课设的本科生、想找个完整 Django 实战项目跟一遍的新手,以及想快速在本地跑通推荐流程、验证算法效果的人。

2. Django 项目骨架与数据模型设计:先把三张表设计明白

2.1 项目目录结构:Django 的 MVT 组织方式

拿到压缩包解压后,先别急着pip install然后python manage.py runserver——先看清目录,因为后面所有排错都依赖你对「哪个文件该放哪个目录」的判断。典型结构是这样:

MusicRecommend/ ├── manage.py ├── MusicRecommend/ # 项目配置目录 │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── music/ # 核心业务应用 │ ├── models.py # 数据模型 │ ├── views.py # 视图函数 │ ├── urls.py # 应用路由 │ ├── admin.py # 后台管理 │ └── migrations/ # 数据库迁移文件 ├── recommend/ # 推荐算法独立模块 │ ├── similar.py # 相似度计算 │ └── recall.py # 候选集生成 ├── static/ ├── templates/ └── db.sqlite3 # 已初始化的数据库

这个拆分逻辑建议你保留:把推荐算法单独放一个包,而不是塞进views.py。原因有两个。第一,Django 的惯例是「App 只负责一类业务」,推荐算法既不属于用户模块也不属于歌曲模块,单独放方便复用,答辩时也容易讲清楚代码分层。第二,算法模块日后要换实现方案(比如从协同过滤换成矩阵分解),你只需要改recommend包,视图层完全不用动。

创建业务应用用python manage.py startapp music,这是 Django 的固定动作。重点理解一下migrations目录的机制:里面的文件是 Django 自动生成的表结构快照,数据库的增删改查全部通过 ORM 完成,不需要手动写 SQL。对刚上手的同学,我一般建议不要手动去动migrations里的文件,改完模型后用makemigrations统一生成新的迁移文件,而不是去改旧的。

MVT 的运行逻辑一句话就能讲透:浏览器请求/music/recommend/,路由层urls.py把地址映射到views.py的某个函数,函数操作models.py里的模型完成数据库增删改查,最后把结果丢给templates/下的模板渲染成 HTML 返回。你在这个工程的任何一处排错,其实都在排查这条链路上的某一个环节。

2.2 models.py 设计:用户、歌曲、行为三张核心表

音乐推荐系统的模型不需要特别多,核心就是用户、歌曲、用户行为三张表。用户直接复用 Django 内置的User(自带认证能力,省掉自己写密码校验和 session 逻辑),歌曲表和用户行为表要仔细抠字段。先看代码:

from django.db import models from django.contrib.auth.models import User class Song(models.Model): title = models.CharField(max_length=100, verbose_name='歌名') singer = models.CharField(max_length=50, verbose_name='歌手') genre = models.CharField(max_length=20, blank=True, verbose_name='流派') play_count = models.IntegerField(default=0, verbose_name='播放次数') cover = models.CharField(max_length=200, blank=True, verbose_name='封面图URL') file = models.CharField(max_length=200, verbose_name='音频文件路径') class Meta: db_table = 'song' verbose_name = '歌曲' def __str__(self): return self.title class UserBehavior(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='用户') song = models.ForeignKey(Song, on_delete=models.CASCADE, verbose_name='歌曲') score = models.FloatField(default=0, verbose_name='评分') is_favorite = models.BooleanField(default=False, verbose_name='是否收藏') created_time = models.DateTimeField(auto_now_add=True, verbose_name='行为时间') class Meta: db_table = 'user_behavior' verbose_name = '用户行为' unique_together = ('user', 'song') def __str__(self): return f'{self.user}-{self.song}'

字段设计上有三个点值得细说。第一个是外键:user和song用 ForeignKey 而不是直接存 ID,这样反向查询(查某首歌被哪些用户收藏过)、admin 后台下拉选择、级联删除都方便。第二个是unique_together,它保证同一个用户对同一首歌只有一条行为记录,防止用户反复收藏同一首歌时数据重复。实际项目里这里要配合「更新或创建」逻辑,而不是直接insert。第三个是评分字段用 FloatField,因为协同过滤算法要对评分做归一化和加权运算,浮点数能兼容以后改成 10 分制的需求,IntegerField 在这里反而限制死了。

如果毕设要求必须用 MySQL,而不是项目自带的 SQLite,只需要改settings.py里的DATABASES配置:

DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'music_recommend', 'USER': 'root', 'PASSWORD': 'your_password', 'HOST': '127.0.0.1', 'PORT': '3306', } }

从 SQLite 切到 MySQL 有个常见的坑:SQLite 文件里的数据不会自动同步到 MySQL,你必须先在 MySQL 里建好库、设好字符集,再重新执行迁移。默认字符集是 latin1 的话,中文歌名入库直接变问号,这个放到第 5 章专门讲。

2.3 数据库迁移实操与数据写入

模型改完,执行下面两步让表结构落库:

python manage.py makemigrations music python manage.py migrate

第一条命令把models.py的变更生成迁移文件,第二条命令把迁移文件应用到数据库。这两个命令是 Django 开发里最常敲的,每次改完模型都要执行一遍,顺序不要反。如果你是第一次跑一个干净的库,建议顺序是:把原有的db.sqlite3备份后删掉(如果里面已经有过误操作,直接删了重来最省事)、执行上面两条命令、最后再执行python manage.py createsuperuser创建管理员。

需要往库里灌测试数据时,我一般在 Django shell 里写循环,比在 admin 后台一条条点快得多:

python manage.py shell
from music.models import Song songs = [ Song(title='晴天', singer='周杰伦', genre='流行', file='/music/qingting.mp3'), Song(title='海阔天空', singer='Beyond', genre='摇滚', file='/music/haikuo.mp3'), Song(title='Despacito', singer='Luis Fonsi', genre='拉丁', file='/music/despacito.mp3'), ] Song.objects.bulk_create(songs) print(Song.objects.count())

这里bulk_create是一次性写入多条记录,比循环里一条条save()快一个数量级;对毕业设计这种几十条数据无所谓,但批量写入的写法在讲代码时是加分项。顺便提醒一个查询集的操作细节:UserBehavior.objects.filter(song__id=1).delete()删的是行为记录而不是歌曲记录——删除对象时要看清 QuerySet 的过滤条件,song__id和song_id在 Django 查询语法里是两种含义,前者是跨表查询,后者是外键字段名,用混了很容易误删数据。

3. 协同过滤推荐算法:相似度计算与候选集生成的实现

3.1 基于用户的协同过滤:找口味相似的人

推荐系统的核心逻辑用一句话可以概括:把用户的历史行为(收藏、评分、播放)变成特征向量,计算实体之间的相似度,再把相似实体的偏好推荐给目标用户。难点不在概念,而在怎么把相似度算得又快又符合业务直觉。

基于用户的协同过滤(UserCF)分两步走。第一步,对每个用户,找他「行为最相似」的 K 个邻居用户;第二步,把邻居们有过正向行为、而目标用户没听过的音乐拿出来,按相似度加权排序成 Top-N 列表。它适合用户量大、物品相对稳定、用户兴趣跟随圈子变化的场景——校园音乐站就是典型:新生入学后听的歌,往往来自同宿舍、同社团同学的推荐。这就是 UserCF 的业务直觉:你身边的人在听什么,你大概率也会喜欢。

相似度计算最常见的做法是余弦相似度。把用户 u 和用户 v 的行为记录看成两个集合,余弦相似度等于它们共同收藏歌曲数除以收藏数的几何平均,区间落在 0 到 1 之间,越接近 1 说明两人的口味越接近:

# recommend/similar.py from math import sqrt def user_similarity(behaviors): """计算用户两两之间的余弦相似度 behaviors: { user_id: { song_id: score } } 返回: { user_id: { other_user_id: similarity } } """ # 1. 构建歌 -> 用户集合的倒排表 song_to_users = {} for user_id, items in behaviors.items(): for song_id in items: song_to_users.setdefault(song_id, set()).add(user_id) # 2. 统计用户两两之间的共同收藏数 cooccurrence = {} for song_id, user_ids in song_to_users.items(): for u in user_ids: for v in user_ids: if u != v: cooccurrence.setdefault(u, {}).setdefault(v, 0) cooccurrence[u][v] += 1 # 3. 除以各自收藏数的几何平均,得到余弦相似度 similarity = {} for u, neighbors in cooccurrence.items(): similarity[u] = {} for v, common_count in neighbors.items(): similarity[u][v] = common_count / sqrt(len(behaviors[u]) * len(behaviors[v])) return similarity

这段代码分三层逻辑。第一步建立倒排表,把「用户有哪些歌」翻转为「某首歌被哪些用户收藏」,这一步是为了避免后续 O(n²) 的笛卡尔积去遍历所有用户对,性能差别在数据量上千以后非常明显。第二步统计共同收藏次数,这是整段代码最耗时的部分,行为数据一多,瓶颈基本都在这。第三步做归一化,除以集合大小的乘积开根号,让相似度不受用户收藏数量的量级影响——收藏了 500 首歌的用户和只收藏 5 首歌的用户,不能因为数量差就天然相似。

注意一个细节:倒排表里的用户集合用的set,所以同一首歌被同一用户反复播放只算一次共同行为,这符合「是否听过」的布尔逻辑。如果你要支持评分数据,第三步可以换成 Pearson 相关系数,把计数器换成评分差值的累加,骨架完全不用动。

3.2 基于物品的协同过滤:用已收藏的音乐找同类

ItemCF 是另一个方向:不找相似的人,找相似的歌。用户收藏了 A,系统就算出与 A 最相似的歌曲 B、C,把 B、C 推给用户。它在音乐场景通常比 UserCF 更稳,因为一首歌的属性(风格、歌手、节奏)是固定的,不像人的兴趣会漂移;用户历史积累越久,ItemCF 的推荐结果越像「猜你喜欢」,而不是「你朋友在听什么」。

物品相似度和用户相似度的计算互为镜像:

def item_similarity(behaviors): """计算歌曲两两之间的余弦相似度 返回: { song_id: { other_song_id: similarity } } """ # 1. 统计每首歌被多少个不同用户收藏,同时统计同现次数 song_count = {} song_cooccur = {} for user_id, items in behaviors.items(): for s1 in items: song_count[s1] = song_count.get(s1, 0) + 1 song_cooccur.setdefault(s1, {}) for s2 in items: if s1 != s2: song_cooccur[s1][s2] = song_cooccur[s1].get(s2, 0) + 1 # 2. 用共同用户数做余弦归一化 similarity = {} for s1, neighbors in song_cooccur.items(): similarity[s1] = {} for s2, co_count in neighbors.items(): similarity[s1][s2] = co_count / sqrt(song_count[s1] * song_count[s2]) return similarity

这段和 UserCF 的差别只在「用户」和「物品」两个词互换:外层循环从用户出发,遍历他的收藏列表,每首歌都和他收藏的其他歌产生一次同现计数;分母除以两首歌被收藏次数的几何平均。同一个用户收藏《晴天》和《七里香》各一次,这两首歌的同现次数就加一;收藏这两首歌的人越多,它们的相似度越高。

做毕设时,建议两种算法都实现,然后按下面的维度在答辩 PPT 里对比选型理由:

对比维度UserCFItemCF
推荐逻辑找相似用户,推荐相似用户喜欢的歌找相似歌曲,推荐与已收藏歌曲相似的歌
适合场景用户量小、兴趣跟随圈子变化物品属性稳定、用户历史行为多
冷启动表现新用户无行为,无法定位相似用户新歌无行为,无法参与相似度计算
可解释性「和你口味相似的人也喜欢」「因为你收藏了《晴天》」
计算热点用户两两相似度,随用户数平方增长物品两两相似度,随歌曲数平方增长

音乐推荐项目我更推荐把 ItemCF 作为主算法、UserCF 作为辅助,原因在于可解释性。在推荐页写「因为你收藏了《晴天》,所以推荐《七里香》」,用户能一秒理解;写「因为用户小王和你口味相似」,评委大概率追问一句「相似度怎么算的」。ItemCF 的解释路径更短,答辩时省很多口舌。

3.3 推荐生成与冷启动兜底

有了相似度矩阵,最后一步是生成推荐列表。下面这段同时兼容 UserCF 和 ItemCF 的相似度矩阵结构,接口统一:

# recommend/recall.py def recommend_for_user(user_id, behaviors, sim_matrix, top_n=10): """根据相似度矩阵生成 Top-N 推荐 sim_matrix 可以是 item_similarity 的结果。 """ interacted = set(behaviors.get(user_id, {}).keys()) scores = {} # UserCF: 遍历相似用户,累加相似度作为候选歌的分数 for other_user, sim in sim_matrix.get(user_id, {}).items(): if sim <= 0: continue for song_id, score in behaviors.get(other_user, {}).items(): if song_id in interacted: continue scores[song_id] = scores.get(song_id, 0) + sim * score ranked = sorted(scores.items(), key=lambda x: x[1], reverse=True) return ranked[:top_n]

生成逻辑有三个原则。第一,已交互的歌曲必须过滤,否则推荐页会把你收藏过的歌再推一遍,演示时显得系统「没干活」。第二,分数累加用sim * score而不是简单加 sim,这样能兼容评分制数据;如果只用布尔收藏,score 全是 1,加权式也不会出错。第三,排序用sorted(..., reverse=True)在几千条候选数据下完全够用,不需要引入额外排序依赖。

冷启动是协同过滤自带的短板。新用户没有任何历史行为,相似度矩阵里搜不到他,推荐结果必然是空列表。最常见的兜底方案是「热门榜补齐」:按play_count倒序取前 10 首热门歌,塞进推荐接口的返回里,页面上标注「热门推荐」。把兜底逻辑写进视图接口而不是写在模板里,这样前端只认一个返回结构,后续换成混合推荐时改动最小。这个方案不完美,但应对毕设演示足够了——老师点开一个没听过歌的新账号,看到的不是空页面,而是热门歌单,体验分直接拉满。

4. 把推荐结果送到页面:视图、路由与模板渲染

4.1 views.py 与 urls.py:推荐接口的数据流

算法写在recommend包里,还得把结果通过 HTTP 送出去。路由层做的是 URL 到视图函数的映射。主项目的urls.py用include把请求分发到 app 的urls.py:

# MusicRecommend/urls.py from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('music/', include('music.urls')), ]
# music/urls.py from django.urls import path from . import views urlpatterns = [ path('recommend/', views.recommend_view, name='recommend'), path('song/<int:song_id>/', views.song_detail, name='song_detail'), ]

path('song/<int:song_id>/')里的<int:song_id>是 URL 转换器,Django 会把 URL 里的数字捕获出来并转成 int 传给视图函数。如果不用 int 转换器,拿到的就是字符串,后面还得手动int()转换,一旦 URL 里传了非数字,直接 500。路由顺序也有讲究:Django 从上往下匹配,recommend/要放在song/前面,避免前缀相同的路由被跳过去。

视图层负责取数、计算、渲染,我把流程拆成五步写进代码:取当前登录用户、读出所有用户行为、算相似度矩阵、生成推荐列表、渲染模板:

# music/views.py from django.shortcuts import render, redirect from django.contrib.auth.decorators import login_required from .models import Song, UserBehavior from recommend.similar import user_similarity from recommend.recall import recommend_for_user @login_required def recommend_view(request): # 1. 读出全部行为记录,组织成算法要求的嵌套字典 behaviors = {} for record in UserBehavior.objects.all(): behaviors.setdefault(record.user_id, {})[record.song_id] = record.score # 2. 算相似度矩阵(数据量小,实时算即可) sim_matrix = user_similarity(behaviors) # 3. 生成推荐结果 rec_list = recommend_for_user(request.user.id, behaviors, sim_matrix, top_n=10) # 4. 兜底:冷启动用户返回热门榜 if not rec_list: hot_songs = Song.objects.order_by('-play_count')[:10] return render(request, 'music/recommend.html', {'songs': hot_songs, 'scores': {}}) song_ids = [sid for sid, _ in rec_list] scores = dict(rec_list) songs = Song.objects.filter(id__in=song_ids) # 5. 返回模板上下文 return render(request, 'music/recommend.html', { 'songs': songs, 'scores': scores, })

这里有一个我踩过的问题:Song.objects.filter(id__in=song_ids)返回的对象顺序,跟song_ids列表顺序不一致。数据库返回顺序由主键或索引决定,不是按 IN 条件里的列表顺序。所以如果模板按返回顺序显示,排第一的不一定是分数最高的歌。解决办法是保留scores字典,模板里用它来控制展示顺序,或者把 songs 转成字典后按song_ids重新组装。我一般用后者,因为模板里的逻辑越少,代码越容易讲清楚。

4.2 模板渲染:把推荐分数和歌曲卡片展示出来

模板层用 Django Template Language 做数据展示。推荐页的核心逻辑是:有推荐结果就循环渲染卡片,没有结果给空态提示,推荐分数保留三位小数显示,让用户能感知「推荐度」这个数字的存在:

<!-- templates/music/recommend.html --> <div class="song-list"> {% for song in songs %} <div class="song-card"> <h3>{{ song.title }}</h3> <p>{{ song.singer }} · {{ song.genre }}</p> <span class="score">推荐度 {{ scores|default_if_none:0|floatformat:3 }}</span> <a href="{% url 'song_detail' song.id %}">去听听</a> </div> {% empty %} <p>你还没有足够的历史行为,先去多听几首歌,推荐会更准。</p> {% endfor %} </div>

{% url 'song_detail' song.id %}是反向解析,模板里不硬编码 URL 地址,路由地址改一次,所有调用它的模板自动跟着变,这是 DTL 里值得养成的习惯。floatformat:3把相似度加权的长小数格式化成三位小数,不然页面上会显示 0.12345678 这种没法看的一长串。default_if_none:0处理热门榜兜底时 scores 为空字典的情况,避免模板取到空值报错。

模板里不适合写复杂业务判断。如果你发现模板里{% if %}嵌套超过两层,就该考虑把逻辑下沉到视图层。冷启动兜底就是标准例子——判断条件放在视图函数里,模板只负责展示结果,这样职责边界清晰,答辩被追问时也好答。

4.3 管理后台:不写代码就能维护数据

Django 自带的 admin 后台对毕业设计项目非常实用。答辩现场你要演示推荐结果变化,如果靠写 SQL 造数据,演示节奏会非常僵硬;把模型注册进 admin,所有的数据库增删改查都变成网页操作:

# music/admin.py from django.contrib import admin from .models import Song, UserBehavior @admin.register(Song) class SongAdmin(admin.ModelAdmin): list_display = ('title', 'singer', 'genre', 'play_count') search_fields = ('title', 'singer') # 顶部的搜索框按歌名/歌手搜 list_filter = ('genre',) # 右侧按流派过滤 @admin.register(UserBehavior) class BehaviorAdmin(admin.ModelAdmin): list_display = ('user', 'song', 'score', 'is_favorite', 'created_time') list_per_page = 50 # 行为记录多时每页放 50 条

list_display决定列表页展示哪些字段列,search_fields让搜索走 SQL 的 LIKE 查询,list_filter生成侧边栏过滤选项。这些配置不需要写一行前端代码。需要提醒的是,admin 后台只对is_staff用户开放,创建超级用户之后别急着登——先python manage.py runserver 0.0.0.0:8000把服务拉起来,再访问/admin/才有意义。

5. 避坑:把这份音乐推荐系统跑通前最常见的五个坑

5.1 Django 版本太新,老项目直接崩

现象:按说明装好依赖,python manage.py runserver,控制台立刻报错ImportError: cannot import name 'url' from 'django.conf.urls',或者django.core.exceptions.ImproperlyConfigured。

原因:老项目urls.py里用的是 Django 2.x 时代的from django.conf.urls import url写法,而你的环境里装的是 Django 4.x,这个函数早已被移除。网上大部分带毕设性质的 Django 资源都有这个问题,下载物自带的依赖说明不兼容新版本环境。

解决:先看项目里有没有requirements.txt,有就按里面钉住的版本装,一般这类项目锁的是 Django 2.2 或 3.2。没有的话,把urls.py里的url(r'^song/(?P<song_id>\d+)/$', ...)改写成path('song/<int:song_id>/', ...),兼容性直接解决。我拿到任何 Django 项目第一步都是pip freeze对比版本,再决定改代码还是重装环境——这是处理了十来个毕业设计项目攒下的血泪经验。

5.2 数据库迁移报「表已存在」,越改越乱

现象:改了models.py加了一个字段,执行python manage.py migrate提示Table 'music_userbehavior' already exists,或者提示字段已存在但模型定义里没有。

原因:项目目录里已经有一个初始化过的db.sqlite3文件,表结构早就落库;你又改了模型或重跑迁移。Django 的迁移是增量式的,它只应用「模型与迁移文件不一致」的部分,如果数据库、迁移文件、模型三者本来就对不上,就会互相矛盾,报各种「已存在」或「不存在」的错。

解决:最省事的办法是把db.sqlite3复制一份备份后删掉,然后按顺序重新执行makemigrations+migrate。如果库里已经造好了要保留的数据,用python manage.py migrate music --fake-initial把当前迁移标记为已应用,再单独对新增字段做迁移。注意:改完表结构后,dumpdata导出的 JSON 里字段对不上,loaddata也会报错,所以先备份 sqlite 文件再动模型,是唯一不会后悔的做法。

5.3 推荐页空白,日志里全是 NaN

现象:页面能打开,但推荐区空无一物;切换到 DEBUG 模式后看到ValueError: cannot convert float NaN to integer或float division by zero。

原因:协同过滤在数学上有个边界情况——两个用户行为集合为空,或者某首歌从未被共同收藏,余弦公式里0 / sqrt(0 * 0)会得到 NaN。后续排序、渲染遇到 NaN 就直接报错或静默丢弃,表现就是推荐结果为空。这属于算法边界问题,不是代码写错。

解决:在相似度函数里给分母加一个极小值,common_count / sqrt(len(items_u) * len(items_v) + 1e-9)。加 epsilon 不是玄学,是数值计算的常规操作,行为数据稀疏时也能稳住,不会因为一个用户只收藏了一首歌就让整页白屏。同时在生成推荐的循环里过滤掉sim <= 0和取值为 NaN 的相似度条目,双保险。

5.4 中文乱码,admin 后台显示一排问号

现象:在 admin 后台新增歌曲,中文歌名、歌手名保存后全部变成????;页面展示看起来正常,但数据库里存的就是问号。

原因:SQLite 对 UTF-8 支持没问题,但如果你按学校要求改用 MySQL,建库时默认字符集是 latin1,中文直接存不进去。这是「数据库端字符集」和「Django 连接端字符集」两处不一致叠加的结果。

解决:建库时显式指定字符集:

CREATE DATABASE music_recommend CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

再把settings.py里 DATABASES 的 OPTIONS 加上'charset': 'utf8mb4'。utf8mb4 比 utf8 更完整,能存 Emoji 和生僻字,我所有 Django 项目都直接用 utf8mb4,省得以后再改表结构。

5.5 创建了超级用户,admin 却登不进去

现象:python manage.py createsuperuser显示创建成功,但登录 admin 一直提示「用户名或密码错误」;或者登录时直接报no such table: django_session。

原因:第二种是明确原因——migrate没执行完整,Django 的 session 表没建立,登录逻辑没法存会话信息。第一种往往也是同一个原因:createsuperuser虽然把用户写进了auth_user表,但 session 表缺失导致登录流程走不完,表现看起来像是密码输入错误。

解决:先执行python manage.py migrate,把 auth、session、admin 相关的系统表全部建好;再执行python manage.py createsuperuser重建管理员;最后重启服务。如果中途切换过数据库(比如从 SQLite 切到 MySQL),要确认 MySQL 里有完整的 Django 系统表——只导了业务表、没迁系统表,照样卡在这一步。

6. 验收推荐算法:用三行假数据验证相似度和推荐队列

拿到这类带算法的毕设项目,我第一件事不是跑页面,而是先把算法拆出来用假数据验证。页面可能因为模板报错、路由不通、数据库缺失而失败,但如果算法本身输出不对,就算页面全绿,答辩演示也会露馅。推荐算法不是黑匣子,几行手造数据就能把它的行为看清楚。

python manage.py shell
from recommend.similar import user_similarity from recommend.recall import recommend_for_user behaviors = { 1: {101: 5, 102: 4, 103: 3}, 2: {101: 4, 102: 5, 104: 2}, 3: {105: 5, 106: 4}, } sim = user_similarity(behaviors) print(sim.get(1)) # 期望输出: {2: 0.6666666666666666} recs = recommend_for_user(1, behaviors, sim, top_n=5) print(recs) # 期望输出: [(104, 1.3333333333333333)]

三行假数据能验证三件事。第一,用户 1 和用户 2 共同收藏了 101、102 两首歌,各自收藏总数都是 3,相似度应该是 2 / sqrt(3*3) ≈ 0.6667,而用户 3 和用户 1 没有共同歌曲,根本不会出现在相似度矩阵里。第二,推荐列表中只出现 104 这一首歌,101、102 被正确过滤(用户 1 已经听过),103 来自用户 1 自己所以也被过滤。第三,用户 3 不参与推荐,他没有与用户 1 的任何共同行为。这套验证应该在拿到项目的第一天就跑一遍,比看任何 Django 教程都更能帮你理解 ORM 数据结构和算法输入输出之间的关系。

验证算法通过后,再看一眼数据库里UserBehavior的数据分布。如果所有用户的行为都集中在同一批歌曲上,推荐结果会退化成热门榜,这是数据问题不是算法问题——答辩前要主动准备好「数据稀疏」的说明,解释清楚为什么推荐列表在某些账号下是空的、兜底热门榜是怎么触发的。算法能跑通只算第一步,能解释清楚边界条件才是拿高分的关键。

从那以后我每次跑这类毕业设计项目,都强制先走一遍三件事:核对依赖版本、确认迁移状态、用 shell 假数据验证算法输出非空。上个月帮一个学弟排查推荐页白屏,查到最后就是db.sqlite3没删干净导致的迁移错乱——这种问题在毕业设计季几乎天天见,提前走完这三步能省掉大半天的排查时间。这套资源的文档和 PPT 是给答辩用的,但我觉得最值钱的反而是recommend包里那两个相似度函数,把算法单独抽出来验证的思维,比任何现成文档都耐用。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询