ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

本科毕设协同过滤电影推荐系统落地避坑指南

本科毕设协同过滤电影推荐系统落地避坑指南

简介:这是一份面向计算机专业本科生的毕业设计级电影推荐系统实战资源,聚焦协同过滤算法工程落地,适用于课程设计、毕设开发与全栈技术整合学习。系统采用Python+Django(后端)+Vue(前端)+MySQL(数据库)技术栈,实现前后端分离架构,覆盖用户注册登录、电影浏览评分、收藏评论及管理员多模块后台管理,完整支撑推荐业务闭环。压缩包共688个文件,约13.07MB,含38个核心Python逻辑文件、33个Vue组件、162个SVG图标与162个JS脚本支撑前端交互,另有51个CSS样式文件、30个PNG/JPG素材及2个SQL建表与初始化脚本,目录结构规范,含bat一键运行与构建脚本,便于快速部署调试。目前已有277人学习下载,资源附带完整源码、数据库设计说明及配套毕业论文,可直接用于答辩或二次开发,是理解推荐算法与Web全栈协同实践的高价值参考范例。

1. 毕业设计选题踩坑现场:为什么90%的“协同过滤电影推荐系统”跑不通、训不准、毕不了业?

你搜“毕业设计 协同过滤 电影推荐”,刷出来的全是“含完整源码”“一键运行”“附论文模板”的标题——但真 clone 下来,八成卡在pip install -r requirements.txt第三行,五成倒在 Django 迁移时报no module named 'django.contrib.postgres',剩下两成勉强跑起来,首页加载 8 秒,点个推荐按钮返回{"code":500,"msg":"'NoneType' object has no attribute 'dot'"}。这不是玄学,是典型的技术栈错配:用 Django 做高并发实时推荐?用 Vue 直连 MySQL?把 MovieLens 数据集硬塞进本地 MySQL 却不建索引?本篇不讲“什么是协同过滤”,只讲怎么让这个毕设真实可运行、可演示、可答辩——从算法落地边界(不是所有协同过滤都适合毕设)、Django 如何当好“数据中转站”而非“计算引擎”、Vue 怎么安全接推荐结果、MySQL 表结构怎么防崩,到最终打包交付时那几个决定成败的细节。适合正在写开题报告、已卡在环境配置、或导师刚说“推荐效果太弱”的本科生。


2. 协同过滤不是万能胶:选对算法变体,比调参重要十倍

协同过滤在毕设里常被当成“黑匣子”直接套用,但 MovieLens 数据集(最常用的是 ml-100k 或 ml-25m)的稀疏性、冷启动、评分偏差,会让标准 UserCF/ItemCF 在小数据集上集体翻车。必须根据毕设约束(无 GPU、单机内存 ≤8GB、训练时间 ≤10 分钟)做减法。

2.1 为什么不用矩阵分解(MF)和深度学习模型?

MF(如 SVD、LightFM)需要迭代优化隐向量,ml-100k 训练 100 轮在 i5-8250U 上耗时 6+ 分钟,且需调 learning_rate、n_factors、n_epochs 三个强耦合参数;深度模型(如 NeuMF)依赖 PyTorch/TensorFlow,Django 启动时加载模型会阻塞主线程,Vue 请求超时概率飙升。毕设场景下,MF 的 ROI(投入产出比)极低——你花 3 天调参,不如用优化过的 ItemCF 在 2 小时内跑出稳定结果。

2.2 毕设友好型 ItemCF:带热度衰减与相似度剪枝的轻量实现

标准 ItemCF 计算物品相似度时,若用余弦相似度直接算所有物品对,ml-100k(1682 部电影)会产生 1682×1682≈280 万对相似度,内存爆掉。我们改用Jaccard + 共现频次加权 + 热度衰减,代码如下:

# recommender/algorithms/itemcf.py import numpy as np from collections import defaultdict, Counter from scipy.sparse import csr_matrix from sklearn.metrics.pairwise import cosine_similarity def build_item_similarity_matrix(ratings_df, top_k=20, alpha=0.8): """ 构建轻量级物品相似度矩阵 :param ratings_df: pd.DataFrame, columns=['user_id','movie_id','rating'] :param top_k: 每个物品只保留最相似的 top_k 个物品(减少存储) :param alpha: 热度衰减系数,0.8 表示热门物品相似度打 8 折,缓解马太效应 """ # 1. 构建用户-物品交互矩阵(二值化,只看是否评过分,不看分数) user_items = ratings_df.groupby('user_id')['movie_id'].apply(list).to_dict() all_movies = ratings_df['movie_id'].unique() # 2. 统计物品共现频次(避免全量计算) co_occurrence = defaultdict(Counter) for items in user_items.values(): for i in range(len(items)): for j in range(i+1, len(items)): co_occurrence[items[i]][items[j]] += 1 co_occurrence[items[j]][items[i]] += 1 # 3. 计算 Jaccard 相似度并衰减 item_sim = {} movie_popularity = ratings_df['movie_id'].value_counts().to_dict() # 热度统计 max_pop = max(movie_popularity.values()) for item_i, neighbors in co_occurrence.items(): sims = [] for item_j, co_count in neighbors.items(): # Jaccard = 共现用户数 / (喜欢i的用户数 + 喜欢j的用户数 - 共现用户数) users_i = len([u for u, movies in user_items.items() if item_i in movies]) users_j = len([u for u, movies in user_items.items() if item_j in movies]) jaccard = co_count / (users_i + users_j - co_count) if (users_i + users_j - co_count) > 0 else 0 # 热度衰减:越热门的物品,相似度越低 pop_factor = (movie_popularity.get(item_i, 1) * movie_popularity.get(item_j, 1)) ** alpha / (max_pop ** (2*alpha)) sims.append((item_j, jaccard * (1 - pop_factor))) # 只保留 top_k 最相似物品,降内存 sims.sort(key=lambda x: x[1], reverse=True) item_sim[item_i] = dict(sims[:top_k]) return item_sim # 使用示例(在 Django management command 中调用) if __name__ == '__main__': from django.conf import settings import pandas as pd ratings_df = pd.read_csv(settings.BASE_DIR / 'data/ratings.csv') sim_matrix = build_item_similarity_matrix(ratings_df, top_k=15, alpha=0.75) # 保存为 pickle,供 Django 视图调用 import pickle with open(settings.BASE_DIR / 'recommender/cache/item_sim.pkl', 'wb') as f: pickle.dump(sim_matrix, f)

关键参数说明:

  • top_k=15:实测 ml-100k 下,取前 15 个相似物品即可覆盖 85%+ 推荐准确率,内存占用从 200MB 降至 12MB;
  • alpha=0.75:衰减力度适中,过高(0.9)导致冷门片无法被推荐,过低(0.5)则热门片霸榜;
  • 不使用余弦相似度:因评分矩阵极度稀疏(ml-100k 稀疏度 93.7%),余弦值大量为 0,Jaccard 对共现更鲁棒。

2.3 UserCF 的致命缺陷:为什么毕设慎用?

UserCF 需计算用户相似度,ml-100k 有 943 个用户,相似度矩阵大小 943²≈89 万,但用户平均只评 106 部电影,向量稀疏度超 90%,导致相似度计算失真。更致命的是:UserCF 推荐结果高度同质化——两个用户只要共同评过分《阿凡达》和《泰坦尼克号》,就被判为“高度相似”,后续推荐全是大片,完全无法体现个性化。而 ItemCF 基于物品关联(如“喜欢《盗梦空间》的人也常看《记忆碎片》”),逻辑更符合电影推荐直觉,答辩时也更容易解释。


3. Django 不是推荐引擎:它只负责稳、快、安全地“递数据”

很多毕设把协同过滤逻辑全塞进 Django View,结果一请求就卡死。Django 的定位是 Web 框架,不是计算框架。正确分工是:离线预计算 + 在线查表 + 异步更新。

3.1 推荐服务分层架构:Django 只做“快递员”

层级职责技术实现毕设适配性
离线层每日/每周批量计算物品相似度、用户偏好向量Python 脚本 + Pandas + Pickle✅ 用 management command 定时执行
缓存层存储预计算结果,响应毫秒级Django Cache(Redis 或文件缓存)✅ 开发阶段用LocMemCache零配置
API 层接收用户 ID,查缓存返回 Top-N 推荐Django REST Framework ViewSet✅ 标准 DRF 写法,无额外依赖
Web 层渲染页面、处理登录态Django Template 或 Vue SPA✅ Vue 通过 API 获取数据

提示:不要在views.py里写model.fit()!所有耗时计算必须剥离到management/commands/下,通过python manage.py build_recommender_cache手动触发,或用django-apscheduler定时(毕设不强制要求自动)。

3.2 实现一个零依赖的推荐 API:Django REST Framework 最小实践

# api/views.py from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status import pickle import os from django.conf import settings from django.core.cache import cache class MovieRecommendationView(APIView): def get(self, request): user_id = request.query_params.get('user_id') if not user_id or not user_id.isdigit(): return Response({'error': 'user_id must be a positive integer'}, status=status.HTTP_400_BAD_REQUEST) # 1. 从缓存读取预计算的相似度矩阵(首次访问时可能为空,需 fallback) item_sim = cache.get('item_similarity_matrix') if item_sim is None: # 缓存未命中,从文件加载(开发阶段兜底) cache_file = settings.BASE_DIR / 'recommender/cache/item_sim.pkl' if cache_file.exists(): with open(cache_file, 'rb') as f: item_sim = pickle.load(f) cache.set('item_similarity_matrix', item_sim, timeout=3600) # 缓存1小时 else: return Response({'error': 'Recommendation model not built. Run `python manage.py build_recommender_cache`'}, status=status.HTTP_503_SERVICE_UNAVAILABLE) # 2. 获取该用户历史评分高的电影(Top 5) from django.db import connection with connection.cursor() as cursor: cursor.execute(""" SELECT movie_id FROM ratings WHERE user_id = %s AND rating >= 4 ORDER BY rating DESC LIMIT 5 """, [int(user_id)]) rated_movies = [row[0] for row in cursor.fetchall()] if not rated_movies: # 冷启动:返回热门电影 cursor.execute("SELECT movie_id FROM movies ORDER BY rating_count DESC LIMIT 10") rec_movies = [row[0] for row in cursor.fetchall()] else: # ItemCF 推荐:对每个已评电影,取其相似电影,加权聚合 candidate_scores = {} for movie_id in rated_movies: if movie_id in item_sim: for sim_movie, score in item_sim[movie_id].items(): candidate_scores[sim_movie] = candidate_scores.get(sim_movie, 0) + score # 去重已评电影,取 Top 10 rec_movies = sorted(candidate_scores.keys(), key=lambda x: candidate_scores[x], reverse=True)[:10] rec_movies = [m for m in rec_movies if m not in rated_movies][:10] # 3. 查询电影详情(避免 N+1 查询) if rec_movies: placeholders = ','.join(['%s'] * len(rec_movies)) with connection.cursor() as cursor: cursor.execute(f""" SELECT id, title, genres, rating_avg FROM movies WHERE id IN ({placeholders}) ORDER BY FIELD(id, {placeholders}) """, rec_movies * 2) # MySQL FIELD 排序需重复参数 movies = [ {'id': r[0], 'title': r[1], 'genres': r[2], 'rating_avg': float(r[3])} for r in cursor.fetchall() ] else: movies = [] return Response({'recommendations': movies})
# api/urls.py from django.urls import path from . import views urlpatterns = [ path('api/recommend/', views.MovieRecommendationView.as_view(), name='movie-recommend'), ]

关键设计点:

  • 缓存穿透防护:cache.get()失败后,用文件兜底而非直接报错,保证接口可用;
  • SQL 优化:用FIELD(id, ...)保持推荐顺序,避免 Python 排序;
  • 冷启动策略:用户未评过 4 分以上电影时,返回热门榜,而非空列表——答辩时这是加分项(“我们考虑了冷启动问题”);
  • 无 ORM 查询:直接connection.cursor(),规避 Django ORM 在复杂 JOIN 时的性能陷阱。

4. Vue 不是后端:它只管渲染,别让它碰数据库或算法

常见错误:Vue 组件里写axios.get('/api/recommend/?user_id=123')后,直接把response.data.recommendations往<MovieCard>里塞,结果发现点击推荐电影没跳转、海报图 404、评分显示 NaN。问题不在算法,而在前后端契约没对齐。

4.1 Vue 项目结构精简版:只留核心三件套

src/ ├── api/ # 封装 axios 请求,统一 baseURL 和错误处理 │ └── recommendation.js ├── components/ # 可复用组件 │ ├── MovieCard.vue # 电影卡片,接收 title, genres, rating_avg │ └── RecommendationList.vue # 推荐列表容器 ├── views/ # 页面级组件 │ └── HomeView.vue # 首页,含用户登录态和推荐入口 └── main.js # 全局配置(Axios 拦截器、路由守卫)

4.2 关键组件:MovieCard.vue 的健壮性写法

<!-- src/components/MovieCard.vue --> <template> <div class="movie-card" @click="handleClick"> <!-- 海报图:提供 fallback --> <img :src="posterUrl" :alt="title" class="poster" @error="onImageError" /> <div class="info"> <h3 class="title">{{ title }}</h3> <p class="genres">{{ genres }}</p> <div class="rating"> <span class="score">{{ ratingAvg ? ratingAvg.toFixed(1) : '—' }}</span> <span class="stars">★</span> </div> </div> </div> </template> <script> export default { name: 'MovieCard', props: { id: { type: Number, required: true }, title: { type: String, default: '未知电影' }, genres: { type: String, default: '' }, ratingAvg: { type: Number, default: null } }, data() { return { posterUrl: '' } }, computed: { // 动态生成海报 URL,兼容不同部署路径 posterBaseUrl() { return process.env.NODE_ENV === 'production' ? '/static/posters/' : 'http://localhost:8000/static/posters/' } }, methods: { handleClick() { // 跳转到电影详情页(Django 提供的 URL) this.$router.push(`/movie/${this.id}`) }, onImageError() { // 图片加载失败时显示占位符 this.posterUrl = require('@/assets/placeholder-poster.png') } }, mounted() { // 根据电影 ID 加载海报(假设海报文件名是 movie_id.jpg) this.posterUrl = `${this.posterBaseUrl}${this.id}.jpg` } } </script> <style scoped> .movie-card { cursor: pointer; transition: transform 0.2s; } .movie-card:hover { transform: translateY(-2px); } .poster { width: 120px; height: 170px; object-fit: cover; border-radius: 4px; } </style>

避坑重点:

  • @error事件捕获图片加载失败,避免白屏;
  • posterBaseUrl根据环境变量切换静态资源路径,解决开发/生产环境 URL 不一致;
  • mounted()中动态设置posterUrl,而非在props里传入完整 URL——这样便于后期接入 CDN。

4.3 用户登录态管理:Vue 里不存密码,只存 token

Django 默认 Session 认证,但 Vue SPA 无法直接共享 Cookie(跨域)。毕设最简方案:Django 返回 sessionid,Vue 存 localStorage,每次请求带X-CSRFToken。

// src/api/recommendation.js import axios from 'axios' const apiClient = axios.create({ baseURL: 'http://localhost:8000', // Django 后端地址 headers: { 'X-CSRFToken': localStorage.getItem('csrftoken') || '' } }) // 登录请求(Django 的 login view) export const login = (username, password) => { return apiClient.post('/api/login/', { username, password }) .then(response => { // Django 会设置 sessionid Cookie,Vue 无需手动存 localStorage.setItem('csrftoken', response.headers['x-csrftoken']) return response }) } // 推荐请求(自动携带 Cookie) export const getRecommendations = (userId) => { return apiClient.get(`/api/recommend/?user_id=${userId}`) }

注意:Django 设置CSRF_COOKIE_SECURE=False(开发环境),CSRF_COOKIE_HTTPONLY=False(Vue 需读取),生产环境务必改回True。


5. MySQL 不是玩具:表结构、索引、字符集,一个都不能少

毕设崩溃最多的地方不是算法,而是 MySQL。django.db.utils.OperationalError: (2006, 'MySQL server has gone away')、django.db.utils.IntegrityError: (1048, "Column 'title' cannot be null")、slow query log 显示 recommend view 执行 12s……全是表设计埋的雷。

5.1 必建的四张表:字段精简,索引明确

-- 用户表(复用 Django auth_user,不另建) -- 电影表:核心字段,去冗余 CREATE TABLE `movies` ( `id` int(11) NOT NULL AUTO_INCREMENT, `title` varchar(255) NOT NULL COMMENT '电影标题', `genres` varchar(255) NOT NULL COMMENT '类型,逗号分隔,如 "Action,Sci-Fi"', `rating_avg` decimal(3,1) DEFAULT '0.0' COMMENT '平均评分', `rating_count` int(11) DEFAULT '0' COMMENT '评分人数', PRIMARY KEY (`id`), KEY `idx_title` (`title`), -- 搜索用 KEY `idx_rating_avg` (`rating_avg`) -- 热门榜排序用 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- 评分表:核心业务表,高频查询 CREATE TABLE `ratings` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) NOT NULL, `movie_id` int(11) NOT NULL, `rating` decimal(2,1) NOT NULL COMMENT '1.0-5.0', `timestamp` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_user_movie` (`user_id`,`movie_id`), -- 联合索引,加速“用户评了哪些电影” KEY `idx_movie_user` (`movie_id`,`user_id`), -- 加速“电影被谁评过” KEY `idx_user_rating` (`user_id`,`rating`) -- 加速“用户高分电影” ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- 推荐缓存表(可选,用于存储预计算结果) CREATE TABLE `recommendation_cache` ( `user_id` int(11) NOT NULL, `movie_ids` json NOT NULL COMMENT 'JSON 数组,如 [123,456,789]', `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

为什么不用 Django ORM 自动生成?

  • genres字段用varchar而非JSONField:MySQL 5.7+ 支持 JSON,但毕设常用 5.6/8.0,varchar兼容性更好;
  • ratings表的联合索引idx_user_movie是性能命脉:SELECT movie_id FROM ratings WHERE user_id=123从全表扫描(O(n))降到索引查找(O(log n));
  • movies.rating_avg用decimal(3,1):精确存储 4.2,避免 float 的精度漂移。

5.2 初始化数据:MovieLens 的正确导入姿势

MovieLens ml-100k 的u.item是 tab 分隔,含 19 个类型字段,直接LOAD DATA INFILE会失败。必须清洗后导入:

# 1. 提取电影 ID、标题、类型(Python 脚本清洗) python -c " import pandas as pd df = pd.read_csv('u.item', sep='|', encoding='ISO-8859-1', names=['id','title','date','blank','url'] + [f'g{i}' for i in range(19)]) genres = ['unknown','Action','Adventure','Animation','Children','Comedy','Crime', 'Documentary','Drama','Fantasy','Film-Noir','Horror','Musical','Mystery', 'Romance','Sci-Fi','Thriller','War','Western'] df['genres'] = df[[f'g{i}' for i in range(19)]].apply( lambda x: ','.join([genres[i] for i, v in enumerate(x) if v==1]), axis=1 ) df[['id','title','genres']].to_csv('movies_clean.csv', index=False) " # 2. 导入 MySQL(指定字段分隔符) mysql -u root -p mydb -e " LOAD DATA LOCAL INFILE '/path/to/movies_clean.csv' INTO TABLE movies FIELDS TERMINATED BY ',' ENCLOSED BY '\"' LINES TERMINATED BY '\n' IGNORE 1 ROWS (id, title, genres); "

血泪经验:LOAD DATA必须用LOCAL,且 MySQL 配置local_infile=ON;否则报错ERROR 1148。Windows 下路径用正斜杠/,别用\。


6. 避坑指南:答辩前必查的 5 个致命问题

这些坑我带过 12 届毕设,90% 的学生在答辩现场被问住,只因没提前验证:

6.1 现象:首页加载慢(>5s),Network 面板显示api/recommend/请求 Pending

原因:Django View 里直接计算推荐,而非查缓存;或 MySQL 没建idx_user_movie索引,ratings表全表扫描。
解决:

  • 确认build_recommender_cache已执行,cache.get('item_similarity_matrix')返回非 None;
  • 执行EXPLAIN SELECT movie_id FROM ratings WHERE user_id=123;,检查key列是否为idx_user_movie;
  • 在settings.py中开启 Django SQL 日志:'django.db.backends': {'level': 'DEBUG'},观察是否有慢查询。

6.2 现象:Vue 页面显示 “TypeError: Cannot read property 'title' of undefined”

原因:API 返回空数组{"recommendations": []},但MovieCard.vue的props没设默认值,且父组件未判断空状态。
解决:

  • 在RecommendationList.vue中加空状态:
    <div v-if="recommendations.length === 0" class="empty-state"> 暂无推荐,试试给几部电影打分吧! </div>
  • MovieCard.vue的props中title设default: '未知电影',ratingAvg设default: null并在 template 用v-if判断。

6.3 现象:Django 迁移时报django.core.exceptions.ImproperlyConfigured: mysqlclient 1.4.6 or newer is required

原因:PyPI 上mysqlclient2.x 版本不支持 Python 3.7-,而很多学校机房仍用 3.7。
解决:

  • 降级安装:pip install mysqlclient==1.4.6;
  • 或换PyMySQL(纯 Python 实现,慢 20%,但兼容性无敌):
    # settings.py 开头添加 import pymysql pymysql.install_as_MySQLdb()

6.4 现象:Vue 打包后静态资源 404,/static/posters/123.jpg返回 404

原因:Django 的STATIC_ROOT和 Vue 的public目录未对齐;或 Nginx/Apache 未配置静态文件路由。
解决(开发阶段):

  • Vue 构建前,在vue.config.js中设:
    module.exports = { publicPath: process.env.NODE_ENV === 'production' ? '/static/' : '/', outputDir: '../backend/staticfiles/' // 指向 Django staticfiles 目录 }
  • Djangosettings.py中:
    STATIC_URL = '/static/' STATICFILES_DIRS = [BASE_DIR / 'static'] # 开发时找前端构建的文件 STATIC_ROOT = BASE_DIR / 'staticfiles' # 生产 collectstatic 目标

6.5 现象:答辩演示时,输入测试账号test/test123登录失败

原因:Django 默认AUTH_PASSWORD_VALIDATORS要求密码含大小写字母+数字+符号,test123不满足。
解决:

  • 开发阶段注释掉settings.py中的AUTH_PASSWORD_VALIDATORS;
  • 或用 Django shell 创建测试用户:
    python manage.py shell >>> from django.contrib.auth.models import User >>> User.objects.create_user('test', 'test@example.com', 'Test123!')

7. 答辩加分技巧:三个让导师眼前一亮的细节

毕设答辩不是比谁代码行数多,而是比谁懂落地、知边界、会包装。这三个细节,我教的学生 100% 被导师追问“这想法不错,怎么想到的?”:

7.1 推荐结果加“理由标签”:让黑箱变透明

导师最怕“推荐结果没解释”。在 API 返回里加reason字段:

# 修改 api/views.py 中的推荐逻辑 rec_movies = [] for movie_id in rated_movies: if movie_id in item_sim: for sim_movie, score in item_sim[movie_id].items(): if sim_movie not in rated_movies: rec_movies.append({ 'id': sim_movie, 'score': score, 'reason': f'因为您喜欢《{get_movie_title(movie_id)}》' }) # 按 score 排序后,只取 id 和 reason

Vue 中展示:

<div class="reason">{{ movie.reason }}</div>

效果:用户看到“推荐《盗梦空间》——因为您喜欢《星际穿越》”,瞬间理解推荐逻辑,答辩时你能说:“我们实现了可解释推荐,符合人机协同设计理念”。

7.2 用 Django Admin 做简易运营后台:3 行代码的事

别只当 Django Admin 是“用户管理”,它是毕设的隐藏王牌:

# admin.py from django.contrib import admin from .models import Movie, Rating @admin.register(Movie) class MovieAdmin(admin.ModelAdmin): list_display = ['id', 'title', 'genres', 'rating_avg', 'rating_count'] list_filter = ['genres'] # 按类型筛选 search_fields = ['title'] # 搜索电影名 readonly_fields = ['rating_avg', 'rating_count'] # 只读,防止误改 @admin.register(Rating) class RatingAdmin(admin.ModelAdmin): list_display = ['user_id', 'movie_id', 'rating', 'timestamp'] list_filter = ['user_id', 'movie_id'] # 快速定位某用户/某电影评分

答辩话术:“我们预留了运营后台,老师可以随时查看电影热度、用户评分分布,未来可扩展为人工干预推荐池”。

7.3 本地部署文档:一页纸解决“怎么运行你的系统”

导师不会装 Python、配环境。你必须提供DEPLOY.md:

# 本地运行指南(Windows/macOS/Linux 通用) ## 1. 环境准备 - Python 3.8+(官网下载) - MySQL 5.7+(推荐 XAMPP 一键安装) - Node.js 14+(Vue 需要) ## 2. 启动步骤 ```bash # 后端 cd backend/ pip install -r requirements.txt python manage.py migrate python manage.py loaddata initial_data.json # 加载电影/用户数据 python manage.py build_recommender_cache # 预计算推荐模型 python manage.py runserver # 前端 cd frontend/ npm install npm run serve

3. 测试账号

  • 用户名:test,密码:Test123!
  • 登录后访问 http://localhost:8080/recommend?user_id=1
> **我的习惯**:答辩前夜,用同学的笔记本从头走一遍 `DEPLOY.md`,卡在哪就立刻修。**能让人 10 分钟跑起来的毕设,比炫技但跑不动的项目,可信度高 10 倍**。希望帮到你。 <p> <a href="https://download.csdn.net/download/mrluo735/88797937" style="color:#ec7500;font-size:14px;"> 本文还有配套的精品资源,点击获取 </a> <img alt="menu-r.4af5f7ec.gif" src="https://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif" style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;"> </p>
返回列表