尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Python电影推荐系统毕业设计:从数据库设计到协同过滤算法完整落地

发布时间:2026/9/28 1:27:05

资讯中心
01
ARTICLE

Python电影推荐系统毕业设计:从数据库设计到协同过滤算法完整落地

Python电影推荐系统毕业设计:从数据库设计到协同过滤算法完整落地
简介这是一套面向计算机、通信、人工智能、自动化等相关专业学生与教师的Python电影推荐系统毕业设计源码包适合作为毕业设计、期末课程设计或课程大作业的参考方案也可供初学者学习与进阶。压缩包共726个文件约19.54MB其中39个py文件承载推荐算法与后端逻辑41个vue与164个js、53个css文件构成前端界面另有2个sql数据库脚本、30个html页面及若干图片、字体与配置文件前后端结构完整。项目已通过调试测试可直接运行答辩评审分达98分具有较高的学习借鉴价值。目前已有239人学习下载。读者可从中获取完整的推荐系统实现思路、数据库表结构设计、前后端交互代码与目录组织方式基础较好的同学还能在此基础上修改调整扩展出个性化推荐等不同功能。1. 从零搭一套能跑通答辩的电影推荐系统Python 数据库到底怎么落地很多同学做毕业设计时选题一确定就卡在第一步电影推荐系统听起来简单但真正动手才发现数据从哪来、推荐算法怎么选、数据库怎么设计、前后端怎么串起来每一步都是坑。我带过几届学生的毕设也帮人改过不少代码最常见的翻车场景是算法跑通了但数据库里只有几十条数据推荐结果全是重复的或者前端页面能打开但后端接口一调就报 500。这套方案的核心思路很明确——用 Python 做推荐引擎用 MySQL 或 SQLite 存电影元数据和用户行为前端用 Flask 或 Django 快速搭一个能演示的界面。适合谁适合计算机相关专业、想用一套完整可运行的代码通过毕业设计答辩的同学也适合刚入门 Python、想拿一个真实项目练手的开发者。下面我会把选型、数据库设计、推荐算法实现、接口联调和避坑经验全部拆开讲你照着做就能跑起来。2. 技术选型与数据库设计为什么用 Python MySQL 而不是别的组合2.1 推荐引擎为什么首选 Python 而不是 Java 或 PHP毕业设计的时间窗口通常只有 2 到 3 个月中间还要写论文、做答辩 PPT真正留给编码的时间可能不到一个月。选 Python 的核心原因不是它“简单”而是它的生态能让你少写很多底层代码。推荐系统涉及矩阵运算、相似度计算、数据清洗NumPy 和 Pandas 能直接把这些活干掉如果换成 Java你得自己引入 Apache Commons Math 或者 ND4J配置成本高出一截。PHP 就更不用说了做推荐算法几乎是自找麻烦。具体到推荐算法协同过滤是毕设里最常用的方案分为 User-Based 和 Item-Based 两种。User-Based 是找相似用户Item-Based 是找相似物品。电影推荐场景下Item-Based 更稳定因为电影的数量和特征相对固定用户口味却容易变。Python 的scikit-learn提供了cosine_similarity一行代码就能算出物品之间的相似度矩阵。如果你用 Java 或 PHP光是实现这个矩阵运算就要写上百行还容易在边界条件上出错。另一个容易被忽略的点是数据处理。电影推荐系统需要处理用户评分、观看历史、电影标签等多张表的数据Pandas 的merge、pivot_table、groupby能让你在几行代码内完成数据透视和聚合。我见过有同学用 Java 写 SQL 拼接光是一个用户-物品评分矩阵就写了 200 多行最后还因为空值处理不当导致推荐结果全为 NaN。所以选 Python 不是跟风是实打实地降低实现风险。提示如果你的学校要求必须用 Java 或 PHP建议把推荐算法单独拆成一个 Python 微服务通过 HTTP 接口给主系统调用这样既满足语言要求又能享受 Python 的算法生态。2.2 数据库选 MySQL 还是 SQLite一张表看清适用边界数据库选型是毕设里第二个容易纠结的点。很多同学听说 MySQL 是“企业级”就无脑选 MySQL结果在本地开发时被安装配置、用户权限、端口占用折腾掉两天。其实对于毕业设计这种单机演示场景SQLite 完全够用而且零配置、单文件、随项目走答辩时拷贝到任何电脑上都能直接跑。下面这张表是我根据实际带毕设的经验整理的对比你可以直接对照自己的情况选对比维度MySQLSQLite安装配置需要安装服务、设置密码、建库建用户无需安装Python 内置支持并发能力支持高并发读写单写入锁不适合高并发数据量上限理论上无上限单库建议不超过 1GB答辩演示需要确保服务启动、端口不冲突拷贝文件即可运行适合场景多用户在线系统、需要远程访问单机演示、快速原型、嵌入式如果你的毕设要求“系统能支持多用户同时在线”那就选 MySQL如果只是本地演示、老师看的是功能完整性和算法效果SQLite 更省事。我一般会建议学生先用 SQLite 把功能跑通论文里再写“系统支持迁移至 MySQL”这样既省时间又不影响答辩。数据库表设计方面最少需要四张表用户表、电影表、评分表、推荐结果表。用户表存user_id、username、password_hash电影表存movie_id、title、genres、release_year、avg_rating评分表存user_id、movie_id、rating、timestamp推荐结果表存user_id、movie_id、predicted_score、generated_at。评分表是核心它决定了协同过滤的输入质量。-- 电影表存储电影元数据 CREATE TABLE movies ( movie_id INT PRIMARY KEY, title VARCHAR(255) NOT NULL, genres VARCHAR(255), -- 类型如 Action|Comedy release_year INT, avg_rating FLOAT DEFAULT 0 -- 平均评分用于冷启动兜底 ); -- 评分表用户对电影的评分协同过滤的输入 CREATE TABLE ratings ( user_id INT, movie_id INT, rating FLOAT NOT NULL, -- 评分范围 0.5 到 5.0 timestamp INT, -- Unix 时间戳 PRIMARY KEY (user_id, movie_id), FOREIGN KEY (movie_id) REFERENCES movies(movie_id) ); -- 推荐结果表离线计算后写入前端直接查 CREATE TABLE recommendations ( user_id INT, movie_id INT, predicted_score FLOAT, generated_at DATETIME, PRIMARY KEY (user_id, movie_id) );建表时有两个参数需要特别注意。rating字段用FLOAT而不是INT因为很多公开数据集比如 MovieLens的评分是 0.5 到 5.0 的浮点数用整数会丢失精度。timestamp用INT存 Unix 时间戳而不是DATETIME因为协同过滤里做时间衰减时整数运算比日期函数快得多。另外评分表的主键设为(user_id, movie_id)联合主键天然防止同一用户对同一电影重复评分省掉一层去重逻辑。注意如果使用 SQLiteFOREIGN KEY约束默认不生效需要在连接时执行PRAGMA foreign_keys ON;否则删除电影时不会级联处理评分记录容易留下脏数据。3. 推荐算法实现从评分矩阵到 Top-N 推荐的完整代码路径3.1 用 Pandas 构建用户-物品评分矩阵的四个关键步骤协同过滤的第一步是把数据库里的评分记录转成矩阵。这个矩阵的行是用户列是电影值是评分。听起来简单但实际操作中有四个坑稀疏性、缺失值、索引对齐、内存占用。下面这段代码是我常用的模板直接改数据库连接就能跑。import pandas as pd import numpy as np from sqlalchemy import create_engine # 连接数据库SQLite 和 MySQL 只需改连接字符串 engine create_engine(sqlite:///movie_recommend.db) # 步骤1读取评分数据 ratings pd.read_sql(SELECT user_id, movie_id, rating FROM ratings, engine) # 步骤2构建用户-物品矩阵缺失值填 0 # pivot 后行是 user_id列是 movie_id matrix ratings.pivot_table( indexuser_id, columnsmovie_id, valuesrating ).fillna(0) # 步骤3过滤掉评分过少的用户和电影 # 评分少于 5 条的用户协同过滤效果很差直接剔除 user_counts (matrix 0).sum(axis1) matrix matrix[user_counts 5] movie_counts (matrix 0).sum(axis0) matrix matrix.loc[:, movie_counts 5] # 步骤4转为 NumPy 数组准备计算相似度 matrix_np matrix.values print(f矩阵形状{matrix_np.shape}稀疏度{(matrix_np 0).sum() / matrix_np.size:.2%})这段代码的逻辑说明pivot_table把长表转成宽表fillna(0)把未评分的位置填 0表示“没有交互”。过滤步骤很关键——如果某个用户只评了 1 部电影他和任何人的相似度都不可靠如果某部电影只有 1 个人评过它也无法和其他电影建立有效关联。我一般把阈值设为 5你可以根据数据量调整数据少就降到 3数据多就升到 10。参数说明user_counts 5里的 5 是最小评分次数不是评分值。matrix.values返回的是 NumPy 二维数组后续计算余弦相似度时直接用。打印稀疏度是为了让你心里有数——MovieLens 100K 数据集的稀疏度大约在 93% 到 95% 之间如果你的矩阵稀疏度超过 99%说明数据太稀疏推荐效果会大打折扣需要考虑换数据集或改用基于内容的推荐。3.2 Item-Based 协同过滤余弦相似度计算与 Top-N 推荐生成Item-Based 协同过滤的核心思想是如果用户喜欢电影 A而电影 A 和电影 B 被很多相似用户共同喜欢那就把电影 B 推荐给这个用户。计算电影之间的相似度用余弦相似度公式是similarity dot(A, B) / (norm(A) * norm(B))。scikit-learn的cosine_similarity可以直接算但要注意输入矩阵的方向。from sklearn.metrics.pairwise import cosine_similarity # 转置矩阵让行变成电影列变成用户 # 这样计算的是电影之间的相似度 movie_user_matrix matrix_np.T # 计算电影-电影相似度矩阵 movie_sim cosine_similarity(movie_user_matrix) # 把相似度转成 DataFrame方便按电影 ID 查询 movie_sim_df pd.DataFrame( movie_sim, indexmatrix.columns, columnsmatrix.columns ) def recommend_movies(user_id, top_n10): 给指定用户推荐 top_n 部电影 # 获取用户已评分的电影 user_ratings matrix.loc[user_id] rated_movies user_ratings[user_ratings 0].index.tolist() # 计算候选电影的预测评分 scores {} for movie_id in matrix.columns: if movie_id in rated_movies: continue # 跳过已评分的电影 # 用相似电影的评分加权求和 sim_scores movie_sim_df[movie_id][rated_movies] user_scores user_ratings[rated_movies] # 只取相似度大于 0 的电影参与计算 mask sim_scores 0 if mask.sum() 0: continue scores[movie_id] np.dot(sim_scores[mask], user_scores[mask]) / sim_scores[mask].sum() # 按预测评分降序排列取前 top_n sorted_scores sorted(scores.items(), keylambda x: x[1], reverseTrue) return sorted_scores[:top_n] # 给用户 1 推荐 10 部电影 recommendations recommend_movies(user_id1, top_n10) for movie_id, score in recommendations: print(f电影 ID{movie_id}预测评分{score:.2f})逻辑说明matrix_np.T把用户-物品矩阵转置成物品-用户矩阵这样cosine_similarity算出来的就是电影之间的相似度。recommend_movies函数里先拿到用户已评分的电影列表然后对每一部候选电影找到它和已评分电影的相似度用相似度加权用户的评分最后除以相似度之和做归一化。这个公式是加权平均相似度越高的电影对预测评分的贡献越大。参数说明top_n10是推荐数量毕设演示一般 10 部足够。sim_scores 0这个过滤条件很重要如果某部候选电影和用户看过的所有电影相似度都为 0那它的预测评分没有意义直接跳过。np.dot是点积运算比 Python 循环快一个数量级。实际跑的时候如果电影数量超过 5000 部这个双重循环会变慢可以考虑用矩阵运算一次性算出所有候选电影的评分或者只对相似度最高的 50 部电影做加权。提示如果推荐结果里出现大量用户已经看过的电影检查rated_movies的过滤逻辑。常见错误是user_ratings 0写成了user_ratings 0导致所有电影都被当成已评分。4. 前后端联调与接口设计让推荐结果在页面上跑起来4.1 Flask 接口设计三个必须实现的 API 端点推荐算法跑通后下一步是把它暴露成 HTTP 接口让前端页面能调用。Flask 是最轻量的选择一个文件就能起服务。我一般会设计三个核心接口获取电影列表、提交评分、获取推荐结果。下面是最小可用代码。from flask import Flask, request, jsonify import pandas as pd from sqlalchemy import create_engine app Flask(__name__) engine create_engine(sqlite:///movie_recommend.db) app.route(/api/movies, methods[GET]) def get_movies(): 分页获取电影列表 page request.args.get(page, 1, typeint) size request.args.get(size, 20, typeint) offset (page - 1) * size df pd.read_sql( fSELECT * FROM movies LIMIT {size} OFFSET {offset}, engine ) return jsonify(df.to_dict(orientrecords)) app.route(/api/rate, methods[POST]) def rate_movie(): 提交用户评分 data request.get_json() user_id data[user_id] movie_id data[movie_id] rating data[rating] # 使用 INSERT OR REPLACE 避免重复评分报错 with engine.connect() as conn: conn.execute( INSERT OR REPLACE INTO ratings (user_id, movie_id, rating, timestamp) VALUES (?, ?, ?, strftime(%s, now)), (user_id, movie_id, rating) ) return jsonify({status: ok}) app.route(/api/recommend/int:user_id, methods[GET]) def get_recommendations(user_id): 获取用户的推荐结果 df pd.read_sql( fSELECT movie_id, predicted_score FROM recommendations fWHERE user_id {user_id} ORDER BY predicted_score DESC LIMIT 10, engine ) return jsonify(df.to_dict(orientrecords)) if __name__ __main__: app.run(debugTrue, port5000)逻辑说明/api/movies支持分页避免一次性返回所有电影导致页面卡顿。/api/rate用INSERT OR REPLACE处理重复评分SQLite 和 MySQL 的语法略有不同MySQL 用ON DUPLICATE KEY UPDATE。/api/recommend直接查推荐结果表不实时计算因为协同过滤的计算耗时在秒级放在接口里会导致前端超时。参数说明page和size是分页参数默认每页 20 条。strftime(%s, now)是 SQLite 获取当前 Unix 时间戳的写法MySQL 用UNIX_TIMESTAMP()。debugTrue只在开发时用答辩演示时关掉避免错误信息暴露在页面上。4.2 推荐结果离线计算与定时更新策略推荐结果不应该在用户请求时实时计算原因有两个一是协同过滤的矩阵运算耗时较长用户等不起二是每次请求都算一遍会浪费服务器资源。正确的做法是离线计算把结果写入recommendations表前端直接查。离线计算的触发方式有三种手动脚本、定时任务、评分后触发。毕设演示用手动脚本最简单答辩前跑一次把结果写进数据库。如果想让系统看起来更“智能”可以用APScheduler做一个定时任务每天凌晨重新计算。from apscheduler.schedulers.blocking import BlockingScheduler import subprocess def batch_recommend(): 批量计算所有用户的推荐结果并写入数据库 # 调用推荐脚本实际项目中可以把 recommend_movies 逻辑封装进来 subprocess.run([python, batch_recommend.py], checkTrue) print(推荐结果更新完成) scheduler BlockingScheduler() # 每天凌晨 2 点执行 scheduler.add_job(batch_recommend, cron, hour2, minute0) scheduler.start()逻辑说明batch_recommend.py是一个独立脚本遍历所有用户调用recommend_movies函数把结果批量写入recommendations表。用subprocess调用而不是直接 import是为了避免内存泄漏——推荐计算会占用大量内存独立进程跑完就释放。参数说明hour2, minute0是执行时间避开白天演示时段。checkTrue表示如果脚本报错就抛出异常方便排查。如果数据量小也可以不用定时任务直接在答辩前手动跑一次。注意批量计算时如果用户数量超过 1000建议分批处理每批 100 个用户避免内存溢出。可以在脚本里加time.sleep(1)让数据库有机会释放连接。5. 避坑与排查毕业设计答辩前最容易翻车的五个问题5.1 推荐结果全是重复电影或已看过的电影现象前端页面显示推荐列表但 10 部电影里有 8 部是同一个系列或者全是用户已经评过分的电影。原因推荐结果表没有做去重或者过滤已评分电影的逻辑写错了。常见错误是在recommend_movies函数里用if movie_id in rated_movies判断但rated_movies是 Pandas Index 对象in操作在某些版本下行为不一致。解决把rated_movies转成 Python 列表再判断rated_movies user_ratings[user_ratings 0].index.tolist()。另外写入recommendations表时用INSERT OR REPLACE并在查询时加GROUP BY movie_id去重。5.2 数据库连接报错“Too many connections”现象Flask 服务运行一段时间后接口返回 500日志里出现Too many connections或database is locked。原因每次请求都创建新的数据库连接没有关闭。SQLAlchemy 的create_engine默认使用连接池但如果用engine.connect()后忘记close()连接会泄漏。解决用with engine.connect() as conn:上下文管理器确保连接自动关闭。或者在 Flask 里用flask_sqlalchemy它自带请求级别的连接管理。SQLite 的话把connect_args{check_same_thread: False}加上避免多线程报错。5.3 评分矩阵稀疏度过高导致推荐效果差现象推荐结果看起来“随机”预测评分都在 3.0 左右没有明显区分度。原因评分数据太少用户-物品矩阵稀疏度超过 99%余弦相似度计算出来的值都很小加权平均后趋同。解决换用 MovieLens 100K 或 1M 数据集这些数据集有 10 万到 100 万条评分稀疏度在 93% 到 95% 之间推荐效果明显更好。如果必须用自己的数据把最小评分次数阈值从 5 降到 3或者改用基于内容的推荐用电影类型、导演、年份计算相似度不依赖用户评分。5.4 前端页面能打开但接口跨域报错现象浏览器控制台显示Access-Control-Allow-Origin错误前端请求被拦截。原因Flask 默认不允许跨域请求前端页面和后端服务不在同一个端口。解决安装flask-cors在代码里加两行from flask_cors import CORS CORS(app)如果不想装额外库也可以手动在响应头里加Access-Control-Allow-Origin: *但flask-cors更省事。5.5 答辩演示时数据库文件路径找不到现象本地跑得好好的拷贝到答辩教室的电脑上一运行就报unable to open database file。原因代码里用了相对路径sqlite:///movie_recommend.db但工作目录变了找不到文件。解决用绝对路径或者把数据库文件放在代码同级目录用os.path.dirname(os.path.abspath(__file__))动态获取路径。更稳妥的做法是把数据库文件一起拷贝并在代码里加一个判断如果文件不存在自动从 SQL 脚本初始化。import os BASE_DIR os.path.dirname(os.path.abspath(__file__)) DB_PATH os.path.join(BASE_DIR, movie_recommend.db) engine create_engine(fsqlite:///{DB_PATH})6. 进阶技巧用混合推荐和评估指标让毕设答辩更有说服力6.1 混合推荐协同过滤 基于内容的加权融合纯协同过滤有一个致命问题新电影没有评分无法被推荐冷启动。答辩时老师如果问“新上映的电影怎么推荐”只靠协同过滤答不上来。解决办法是混合推荐把基于内容的相似度和协同过滤的相似度加权融合。基于内容的推荐用电影类型、导演、年份计算相似度。比如两部电影都是“Action|Sci-Fi”年份相差不超过 3 年就给一个较高的内容相似度。然后把内容相似度和协同过滤相似度按 0.3 和 0.7 的权重相加得到最终相似度。def hybrid_similarity(movie_sim_df, content_sim_df, alpha0.7): 融合协同过滤和基于内容的相似度 # alpha 是协同过滤的权重1-alpha 是内容相似度的权重 hybrid alpha * movie_sim_df (1 - alpha) * content_sim_df return hybrid # 内容相似度可以用 genres 字段的 Jaccard 相似度计算 def content_similarity(movies_df): 基于电影类型的 Jaccard 相似度 genres movies_df[genres].str.split(|) n len(genres) sim np.zeros((n, n)) for i in range(n): for j in range(n): set_i set(genres.iloc[i]) set_j set(genres.iloc[j]) sim[i][j] len(set_i set_j) / len(set_i | set_j) if set_i | set_j else 0 return pd.DataFrame(sim, indexmovies_df[movie_id], columnsmovies_df[movie_id])参数说明alpha0.7表示协同过滤占 70% 权重内容相似度占 30%。这个比例可以根据数据量调整——评分数据多就提高 alpha评分数据少就降低 alpha。Jaccard 相似度的计算复杂度是 O(n²)电影数量超过 5000 部时会很慢建议先用协同过滤筛出候选集再算内容相似度。6.2 用 RMSE 和 PrecisionK 量化推荐效果答辩时老师一定会问“你的推荐效果怎么评估”。光说“看起来还不错”是过不了的必须给出量化指标。最常用的两个指标是 RMSE均方根误差和 PrecisionK前 K 个推荐的准确率。RMSE 衡量预测评分和真实评分的偏差值越小越好。计算方法把评分数据分成训练集和测试集8:2用训练集算相似度预测测试集里的评分然后算 RMSE。from sklearn.model_selection import train_test_split from sklearn.metrics import mean_squared_error # 划分训练集和测试集 train, test train_test_split(ratings, test_size0.2, random_state42) # 用训练集构建矩阵和相似度代码同上略 # 对测试集里的每条评分预测评分并计算 RMSE predictions [] actuals [] for _, row in test.iterrows(): pred predict_rating(row[user_id], row[movie_id]) if pred is not None: predictions.append(pred) actuals.append(row[rating]) rmse np.sqrt(mean_squared_error(actuals, predictions)) print(fRMSE: {rmse:.4f})PrecisionK 的计算稍微复杂一点给用户推荐 K 部电影看其中有多少部是用户实际喜欢的评分 4.0。然后对所有用户取平均。def precision_at_k(user_id, k10, threshold4.0): 计算单个用户的 PrecisionK recs recommend_movies(user_id, top_nk) rec_movie_ids [movie_id for movie_id, _ in recs] # 查用户对推荐电影的真实评分 user_test test[(test[user_id] user_id) (test[movie_id].isin(rec_movie_ids))] if len(user_test) 0: return None hits (user_test[rating] threshold).sum() return hits / k # 对所有用户取平均 precisions [precision_at_k(uid) for uid in test[user_id].unique()] precisions [p for p in precisions if p is not None] print(fPrecision10: {np.mean(precisions):.4f})参数说明threshold4.0是“喜欢”的评分阈值MovieLens 数据集里 4.0 以上算喜欢。k10是推荐数量和前端展示保持一致。RMSE 一般在 0.85 到 1.0 之间算正常Precision10 在 0.15 到 0.25 之间算合理。如果 RMSE 超过 1.2说明模型欠拟合需要增加数据量或调整相似度计算方式。我自己的习惯是答辩前把这两个指标跑出来写在 PPT 上老师问起来直接给数字比任何解释都管用。另外如果时间允许可以对比 User-Based 和 Item-Based 的指标差异说明为什么选了 Item-Based这样答辩时更有说服力。希望帮到你。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。