电影推荐系统毕业设计:协同过滤算法与Python源码实现
2026/9/23 7:32:50 网站建设 项目流程

简介:这份资源是面向计算机、通信、人工智能、自动化等专业学生与教师的Python电影推荐系统毕业设计完整源码包,也可用于期末课程设计或课程大作业。项目为个人毕设成果,答辩评审分达98分,代码经过调试测试可正常运行,基础较好的使用者还能在此基础上修改调整,实现协同过滤、内容推荐等不同功能。压缩包共726个文件,约19.54MB,其中39个py文件承载推荐算法与后端逻辑,41个vue与164个js、53个css文件构成前端界面,另有2个sql文件提供数据库脚本,以及html、json、图片等配套静态资源,前后端与数据层结构完整。目前已有239人学习下载,适合小白入门学习,也便于进阶者借鉴整体架构、接口设计与推荐流程实现,快速搭建可运行的推荐系统项目。

1. 电影推荐系统毕业设计:从协同过滤到可交付源码的完整路径

很多同学做毕业设计时,选题定了“基于 Python 的电影推荐系统”,真正动手才发现难点不在算法本身,而在于数据从哪来、数据库怎么建、前后端怎么串、答辩时老师问“你的推荐结果怎么验证”该怎么答。这个标题对应的是一套可运行、可演示、可写论文的完整工程:用 Python 实现推荐算法,用数据库存用户行为与电影元数据,最终交付源码和建表脚本。它适合计算机相关专业本科毕业生,也适合想用一个真实项目补齐“数据处理 + 算法 + 工程化”链路的初学者。接下来我按实际开发顺序,把选型、建库、算法实现、接口封装和踩坑点逐一讲清楚,你照着做就能跑通一套能拿得出手的系统。

2. 技术选型与数据准备:为什么用协同过滤而不是深度学习

2.1 推荐算法选型:协同过滤、矩阵分解与内容推荐的边界

毕业设计的时间通常只有两三个月,选算法第一原则是“可解释、可复现、可写论文”。基于用户的协同过滤(UserCF)和基于物品的协同过滤(ItemCF)是最常见的选择,原因是原理清晰、代码量可控、答辩时能画图讲明白。ItemCF 更适合电影场景,因为电影之间的相似度比用户之间的相似度更稳定,新用户进来也能靠历史物品快速出推荐。

如果你的数据集评分稀疏,UserCF 会面临“找不到相似用户”的问题,这时候可以引入矩阵分解(如 SVD)做隐语义推荐。但要注意,SVD 调参和解释成本更高,论文里需要额外写一节数学推导。内容推荐(基于电影类型、导演、演员的 TF-IDF 相似度)适合做冷启动补充,不建议作为主算法。常见做法是:主算法用 ItemCF,冷启动用内容相似度兜底,论文里写“混合推荐策略”,既有工作量又好解释。

选型确定后,数据集优先考虑 MovieLens。它提供 ml-latest-small(约 10 万条评分)和 ml-1m(约 100 万条评分)两个版本,small 版本足够毕业设计使用,加载快、调试方便。数据文件包括 ratings.csv、movies.csv、users.csv,字段分别是 userId、movieId、rating、timestamp 和 title、genres。不要用爬虫现爬豆瓣或猫眼,反爬策略和版权问题会让你的进度不可控,MovieLens 是学术界公认的公开数据集,论文里引用也规范。

2.2 用 pandas 加载 MovieLens 并做最小数据清洗

拿到数据后不要急着灌数据库,先用 pandas 做一轮清洗和统计,确认数据分布合理。下面这段代码完成加载、去重、过滤低频用户和低频电影,并输出基本统计信息。

import pandas as pd # 加载 MovieLens small 数据集 ratings = pd.read_csv('ml-latest-small/ratings.csv') movies = pd.read_csv('ml-latest-small/movies.csv') # 去重:同一用户对同一电影只保留最新一条评分 ratings = ratings.sort_values('timestamp').drop_duplicates( subset=['userId', 'movieId'], keep='last' ) # 过滤评分次数少于 5 次的用户和电影,降低稀疏度 user_counts = ratings['userId'].value_counts() movie_counts = ratings['movieId'].value_counts() ratings = ratings[ ratings['userId'].isin(user_counts[user_counts >= 5].index) & ratings['movieId'].isin(movie_counts[movie_counts >= 5].index) ] # 合并电影标题和类型,方便后续展示 data = ratings.merge(movies, on='movieId', how='left') print('用户数:', data['userId'].nunique()) print('电影数:', data['movieId'].nunique()) print('评分数:', len(data)) print('评分均值: %.2f' % data['rating'].mean())

逻辑说明:先去重保证一个用户对一部电影只有一条有效评分,避免重复行为干扰相似度计算;再过滤低频用户和电影,因为评分次数太少的对象在协同过滤中会产生噪声。参数方面,阈值 5 是经验值,数据量大时可以调到 10,数据量小就保持 5。运行后如果用户数低于 100,说明过滤太狠,需要放宽阈值。这一步的输出要记到论文的“数据预处理”章节,附上过滤前后的对比表。

2.3 数据库表结构设计:三张核心表加一张推荐结果表

数据库选 MySQL 或 SQLite 都行。毕业设计演示环境用 SQLite 更省事,单文件、免安装;如果论文要求体现数据库设计能力,用 MySQL 并画出 E-R 图更加分。核心表四张:users(用户)、movies(电影)、ratings(评分)、recommendations(推荐结果)。ratings 表是算法输入,recommendations 表存离线计算出的 TopN 推荐,前端查询时直接读结果表,避免每次请求都跑一遍算法。

建表时注意几个细节:userId 和 movieId 建联合索引,因为协同过滤查询频繁按这两个字段过滤;rating 用 DECIMAL(2,1) 存 0.5 到 5.0 的评分;timestamp 用 BIGINT 存 Unix 时间戳。recommendations 表加一个 batch_date 字段,区分不同批次的计算结果,方便做增量更新和回滚。下面是对应的建表 SQL。

CREATE TABLE users ( user_id INT PRIMARY KEY, age INT, gender VARCHAR(10), occupation VARCHAR(50) ); CREATE TABLE movies ( movie_id INT PRIMARY KEY, title VARCHAR(255), genres VARCHAR(255) ); CREATE TABLE ratings ( user_id INT, movie_id INT, rating DECIMAL(2,1), timestamp BIGINT, PRIMARY KEY (user_id, movie_id), INDEX idx_movie (movie_id) ); CREATE TABLE recommendations ( user_id INT, movie_id INT, score FLOAT, batch_date DATE, PRIMARY KEY (user_id, movie_id, batch_date) );

参数说明:联合主键防止重复评分;idx_movie 索引加速“看过某电影的用户”查询;score 用 FLOAT 存相似度加权分;batch_date 让你可以保留历史推荐,答辩时能展示“不同时间推荐结果的变化”。如果老师要求体现增删改查,再补一个用户收藏表或评论表即可,但不要为了凑表而加无关字段。

3. 协同过滤算法实现:从相似度矩阵到 TopN 推荐

3.1 ItemCF 相似度计算的两种方式与代码实现

ItemCF 的核心是计算电影之间的相似度。常用两种度量:余弦相似度和皮尔逊相关系数。余弦相似度关注评分向量的方向,适合评分尺度统一的场景;皮尔逊去均值,能消除用户评分偏好的影响。毕业设计里用余弦相似度就够,代码简单、结果稳定。计算时先构建“用户-电影”评分矩阵,转置后得到“电影-用户”矩阵,再算电影两两之间的余弦相似度。

import numpy as np from sklearn.metrics.pairwise import cosine_similarity # 构建用户-电影评分矩阵 user_movie = data.pivot_table( index='userId', columns='movieId', values='rating' ).fillna(0) # 转置为电影-用户矩阵,计算电影间余弦相似度 movie_user = user_movie.T item_sim = cosine_similarity(movie_user) # 转为 DataFrame,行列为 movieId item_sim_df = pd.DataFrame( item_sim, index=movie_user.index, columns=movie_user.index ) # 只保留相似度最高的 K 个邻居,降低计算量和噪声 K = 20 item_sim_df = item_sim_df.apply( lambda x: x.nlargest(K + 1).sum() - x.max(), axis=1 )

逻辑说明:pivot_table 把长表转成矩阵,fillna(0) 表示未评分视为 0,这是余弦相似度的常见处理。cosine_similarity 直接输出 N×N 相似度矩阵,N 是电影数。K=20 表示每部电影只保留最相似的 20 部作为邻居,减少冷门电影带来的噪声。注意最后一行代码是简化写法,实际应该保留 TopK 的索引而不是求和,这里只是示意过滤思路,完整实现需要返回索引列表。参数 K 一般取 10 到 50,数据量大取小值,数据量小取大值。

3.2 生成推荐列表:加权评分与已看过滤

有了相似度矩阵,就可以对每个用户生成推荐。思路是:遍历用户看过的电影,找到每部电影的 K 个相似电影,用“相似度 × 评分”加权累加,最后去掉用户已经看过的,按分数排序取 TopN。

def recommend_for_user(user_id, user_movie, item_sim_df, top_n=10): # 用户已看过的电影 watched = user_movie.loc[user_id] watched = watched[watched > 0].index.tolist() scores = {} for movie in watched: if movie not in item_sim_df.index: continue sim_movies = item_sim_df[movie].nlargest(21).index[1:] # 去掉自身 for sim_movie in sim_movies: if sim_movie in watched: continue score = item_sim_df.loc[movie, sim_movie] * user_movie.loc[user_id, movie] scores[sim_movie] = scores.get(sim_movie, 0) + score # 按分数降序取 TopN recs = sorted(scores.items(), key=lambda x: x[1], reverse=True)[:top_n] return recs # 示例:给用户 1 生成推荐 recs = recommend_for_user(1, user_movie, item_sim_df) for movie_id, score in recs: title = movies[movies['movieId'] == movie_id]['title'].values print(movie_id, title, round(score, 3))

逻辑说明:nlargest(21) 取 21 个是因为第一个是电影自身,相似度恒为 1,要去掉。加权公式是“相似度 × 用户对已看电影的评分”,评分越高、相似度越高的电影得分越高。已看过滤避免推荐重复内容。参数 top_n 控制推荐数量,前端一般展示 10 条。如果推荐结果全是冷门电影,说明相似度矩阵被低频电影主导,需要回到 2.2 节加大过滤阈值。

3.3 离线计算与数据库写入:把推荐结果落表

线上请求实时跑协同过滤会很慢,标准做法是离线批量计算,把结果写入 recommendations 表。写一个定时脚本,每天或每次数据更新后跑一遍,前端只查结果表。

import sqlite3 from datetime import date conn = sqlite3.connect('movie_recommend.db') cursor = conn.cursor() batch = date.today().isoformat() # 对所有用户批量生成推荐 all_users = user_movie.index.tolist() for uid in all_users: recs = recommend_for_user(uid, user_movie, item_sim_df, top_n=10) for movie_id, score in recs: cursor.execute( 'INSERT OR REPLACE INTO recommendations VALUES (?, ?, ?, ?)', (uid, int(movie_id), float(score), batch) ) conn.commit() conn.close() print('推荐结果写入完成,批次:', batch)

逻辑说明:INSERT OR REPLACE 保证同一批次重复执行不会报主键冲突。batch 用日期标记,方便按批次查询和清理旧数据。参数 top_n=10 与前端展示数量一致。如果用户量超过一万,逐条插入会慢,改用 executemany 批量提交。写入完成后,前端查询语句就是SELECT movie_id, score FROM recommendations WHERE user_id = ? AND batch_date = ? ORDER BY score DESC,响应时间在毫秒级。

4. 避坑与排查:毕业设计里最容易翻车的五个点

4.1 现象:推荐结果全是同一部电影

原因:相似度矩阵没有做 TopK 截断,某部热门电影与所有电影相似度都高,加权后霸榜。解决:在 3.1 节对每部电影只保留 K 个最近邻,K 取 20 左右;同时在加权时对热门电影做惩罚,比如除以该电影的评分人数对数。

4.2 现象:数据库插入中文电影名乱码

原因:SQLite 默认编码与 Python 字符串编码不一致,或者 MySQL 建表时字符集不是 utf8mb4。解决:SQLite 连接时加conn.text_factory = str;MySQL 建表语句末尾加DEFAULT CHARSET=utf8mb4,连接串加charset='utf8mb4'

4.3 现象:pivot_table 内存溢出

原因:用户数和电影数乘积过大,稠密矩阵占内存。MovieLens small 约 600 用户、9000 电影,矩阵约 540 万格,还能承受;如果换 ml-1m 就会爆。解决:改用 scipy.sparse 稀疏矩阵,或者只对活跃用户和热门电影做计算,冷门对象走内容推荐兜底。

4.4 现象:答辩时被问“推荐准确率怎么算”答不上来

原因:只做了实现,没做评估。解决:用 RMSE 或 MAE 评估评分预测,用 Precision@K、Recall@K 评估 TopN 推荐。把数据集按 8:2 划分训练集和测试集,在论文里放一张对比表,ItemCF 和 UserCF 各跑一遍,有数据就有说服力。

4.5 现象:前端请求推荐接口超时

原因:接口里实时跑协同过滤。解决:按 3.3 节做离线计算,接口只查 recommendations 表;如果必须实时,加 Redis 缓存,缓存键用 userId,过期时间设 1 小时。

5. 从能跑到能答辩:评估指标与演示技巧

系统跑通之后,真正决定毕业设计分数的是“你怎么证明它有效”。我一般会做两件事:一是离线评估,二是准备一个可交互的演示界面。离线评估用 RMSE 和 Precision@10,代码不长但能让论文的“实验与分析”章节有实质内容。

from sklearn.model_selection import train_test_split from sklearn.metrics import mean_squared_error import numpy as np # 划分训练集和测试集 train, test = train_test_split(data, test_size=0.2, random_state=42) # 用训练集构建矩阵和相似度(复用前面函数) user_movie_train = train.pivot_table( index='userId', columns='movieId', values='rating' ).fillna(0) # 对测试集中的每条评分做预测,这里用简化版:取相似电影加权平均 def predict_rating(user_id, movie_id, user_movie, item_sim_df): if movie_id not in item_sim_df.index: return user_movie.values.mean() sim_movies = item_sim_df[movie_id].nlargest(11).index[1:] numer, denom = 0, 0 for m in sim_movies: if m in user_movie.columns and user_movie.loc[user_id, m] > 0: numer += item_sim_df.loc[movie_id, m] * user_movie.loc[user_id, m] denom += abs(item_sim_df.loc[movie_id, m]) return numer / denom if denom != 0 else user_movie.values.mean() # 计算 RMSE preds, actuals = [], [] for _, row in test.iterrows(): preds.append(predict_rating(row['userId'], row['movieId'], user_movie_train, item_sim_df)) actuals.append(row['rating']) rmse = np.sqrt(mean_squared_error(actuals, preds)) print('RMSE: %.4f' % rmse)

逻辑说明:train_test_split 按 8:2 划分,random_state 固定保证可复现。predict_rating 用相似电影的加权平均做评分预测,denom 为 0 时回退到全局均值。RMSE 越小越好,MovieLens small 上 ItemCF 的 RMSE 通常在 0.85 到 0.95 之间,如果超过 1.0 说明相似度计算或过滤有问题。参数 11 是取 10 个邻居加自身,与前面 K=20 不冲突,评估时可以调小以加快速度。

演示技巧方面,准备三个账号:一个只看动作片、一个只看爱情片、一个评分很杂。答辩时现场切换账号,展示推荐结果随历史行为变化。再准备一张“推荐结果落库前后响应时间对比”的表格,离线前 800ms、离线后 20ms,老师一看就明白工程价值。数据库里保留两个 batch_date 的数据,展示“昨天的推荐”和“今天的推荐”,说明系统支持增量更新。

最后说一个血泪经验:论文里的系统截图一定要提前跑好,不要答辩前一天才跑,环境一变、路径一改就可能翻车。源码打包时把数据库文件、建表 SQL、依赖清单(requirements.txt)和 README 一起放进去,README 写清楚“先跑 init_db.py 再跑 train.py 最后启动 app.py”。这个顺序错了,老师按文档操作跑不起来,印象分直接扣光。希望帮到你。

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

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

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

立即咨询