恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
基于Python的电影推荐系统毕业设计:ItemCF算法、数据库设计与Flask实现
首页
资讯中心
/
基于Python的电影推荐系统毕业设计:ItemCF算法、数据库设计与Flask实现
基于Python的电影推荐系统毕业设计:ItemCF算法、数据库设计与Flask实现
发布时间:2026/10/12 11:59:34
简介这是一套基于Python的电影推荐系统毕业设计完整源码与数据库面向计算机、通信、人工智能、自动化等专业的学生与教师可用于毕业设计、期末课程设计或课程大作业也适合作为小白入门与进阶学习的实战项目。压缩包共726个文件约19.54MB其中39个py文件承载推荐算法与后端逻辑41个vue与164个js、53个css构成前端界面另有sql数据库脚本、html页面、图片与字体等静态资源并附bat启动脚本与md说明目录结构清晰便于按模块阅读与二次开发。项目经过调试测试可正常运行答辩评审分达98分已有239人学习下载。读者可从中获取完整的推荐系统实现方案、数据库设计、前后端交互逻辑与工程化目录组织方式并在此基础上修改调整实现个性化推荐等不同功能具备较高的学习借鉴价值。1. 从一份能跑起来的电影推荐系统说起毕业设计到底该交什么很多同学做毕业设计选题定了「基于 Python 的电影推荐系统设计与实现」结果卡在第一步到底交什么才算完整。我见过太多人把 MovieLens 数据集下载下来跑通一个协同过滤的 demo截图几张评分预测的表格就交差了答辩时被老师一句「你的数据库表结构呢」问得哑口无言。这个标题背后其实包含四样东西一套能跑的 Python 推荐算法、一个存电影和用户行为的数据库、一份可复现的源码工程以及一套能自圆其说的设计文档。它解决的不是「推荐准不准」这种工业级问题而是「你能不能独立完成一个数据闭环系统」的验证。适合谁适合计算机相关专业、需要在一个学期内交付可演示系统的本科生也适合想拿一个完整项目练手 Python 后端与数据处理的新手。下面我按自己带过几届毕设的经验把这条路拆开讲清楚。2. 推荐算法选型协同过滤、内容推荐还是混着来2.1 三种主流方案在毕设场景下的取舍毕业设计的时间通常只有 8 到 12 周算法选型的第一原则是「能讲清楚原理且代码量可控」。基于用户的协同过滤UserCF和基于物品的协同过滤ItemCF是最常见的选择因为它们只需要用户-物品评分矩阵不需要电影文本特征。ItemCF 在电影场景下通常比 UserCF 更稳原因是电影数量相对固定物品相似度矩阵可以离线算好线上响应快答辩时也容易解释「为什么给你推荐这部」。内容推荐Content-Based需要电影的类型、导演、演员等元数据MovieLens 的 movies.csv 里只有 genres 字段信息量偏少单独用效果一般。混合推荐听起来高级但如果你把协同过滤和内容推荐简单加权答辩老师很可能追问「权重怎么定的」这时候没有实验对比就会很被动。我的建议是主算法用 ItemCF辅以基于流行度的冷启动兜底。这样既有协同过滤的核心逻辑又能解释新用户第一次进来看到什么。2.2 ItemCF 的核心计算与 Python 实现ItemCF 的核心是算物品之间的相似度。常用余弦相似度公式是两列评分向量的点积除以模长乘积。下面这段代码用 pandas 和 numpy 实现输入是用户-电影评分表输出是电影相似度矩阵。import numpy as np import pandas as pd def item_similarity(ratings: pd.DataFrame): ratings: 包含 user_id, movie_id, rating 三列 返回: 电影-电影余弦相似度 DataFrame # 构建用户-电影评分矩阵缺失值填 0 matrix ratings.pivot_table( indexuser_id, columnsmovie_id, valuesrating ).fillna(0) # 转置成电影-用户矩阵每行是一部电影的评分向量 item_matrix matrix.T.values # 计算模长 norms np.linalg.norm(item_matrix, axis1, keepdimsTrue) # 防止除零 norms[norms 0] 1e-9 # 归一化后点积即余弦相似度 normalized item_matrix / norms sim np.dot(normalized, normalized.T) # 对角线置零避免自己和自己最相似 np.fill_diagonal(sim, 0) return pd.DataFrame(sim, indexmatrix.columns, columnsmatrix.columns)逻辑说明pivot_table把长表转成宽表这是协同过滤的标准输入格式。fillna(0)表示未评分注意这里用 0 而不是均值是为了让余弦相似度只受共同评分影响。np.fill_diagonal那一步很关键否则推荐结果永远是电影自己。参数说明rating列如果是 1 到 5 分可以直接用如果要做归一化可以改成(rating - 用户均分)这样能消除用户打分偏严或偏松的影响但代码会多几行毕设里看时间取舍。2.3 生成推荐列表与冷启动兜底有了相似度矩阵给用户推荐就是找到用户看过的电影取这些电影的相似电影按相似度加权求和排序去掉已看过的。def recommend_for_user(user_id, ratings, sim_df, top_n10): 给指定用户生成 top_n 推荐 user_ratings ratings[ratings[user_id] user_id] if user_ratings.empty: # 冷启动返回评分人数最多的电影 popular ratings.groupby(movie_id)[rating].count() return popular.sort_values(ascendingFalse).head(top_n).index.tolist() seen set(user_ratings[movie_id]) scores {} for _, row in user_ratings.iterrows(): mid, r row[movie_id], row[rating] if mid not in sim_df.columns: continue similar sim_df[mid].drop(labelslist(seen), errorsignore) for other_mid, s in similar.items(): scores[other_mid] scores.get(other_mid, 0) s * r ranked sorted(scores.items(), keylambda x: x[1], reverseTrue) return [mid for mid, _ in ranked[:top_n]]逻辑说明加权求和时用用户原始评分r作为权重评分高的电影对推荐影响更大。drop(labelslist(seen))去掉已看避免推荐重复。参数说明top_n一般取 10 到 20毕设演示取 10 足够。冷启动分支返回流行电影这是最常见的兜底策略答辩时能体现你考虑了新用户场景。3. 数据库设计从 ER 图到能跑 SQL 的表结构3.1 电影推荐系统需要哪几张表毕设的数据库不用太复杂但必须完整。核心四张表用户表user、电影表movie、评分表rating、推荐结果表recommendation。评分表是算法输入推荐结果表是算法输出这样整个数据流是闭环的。表名关键字段说明useruser_id, username, password_hash, create_time存登录信息moviemovie_id, title, genres, release_year存电影元数据ratinguser_id, movie_id, rating, timestamp存用户评分联合主键recommendationuser_id, movie_id, score, gen_time存离线算好的推荐评分表用(user_id, movie_id)做联合主键天然防止同一用户对同一电影重复评分。推荐结果表可以加一个gen_time方便演示「重新计算推荐」的功能。3.2 建表 SQL 与索引优化下面这段 SQL 在 MySQL 8.0 和 SQLite 上都能跑注意 SQLite 不支持AUTO_INCREMENT要改成AUTOINCREMENT。CREATE TABLE user ( user_id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password_hash VARCHAR(128) NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE movie ( movie_id INT PRIMARY KEY, title VARCHAR(200) NOT NULL, genres VARCHAR(100), release_year INT ); CREATE TABLE rating ( user_id INT NOT NULL, movie_id INT NOT NULL, rating FLOAT NOT NULL, timestamp INT, PRIMARY KEY (user_id, movie_id), INDEX idx_movie (movie_id) ); CREATE TABLE recommendation ( user_id INT NOT NULL, movie_id INT NOT NULL, score FLOAT NOT NULL, gen_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (user_id, movie_id) );逻辑说明movie_id在评分表里建索引因为 ItemCF 算相似度时要频繁按电影聚合。rating用 FLOAT 而不是 INT是为了兼容 MovieLens 的 0.5 分制。参数说明VARCHAR(128)存密码哈希如果用 bcrypt 够用timestamp用 INT 存 Unix 时间戳和 MovieLens 原始数据一致导入时不用转换。3.3 从 CSV 导入 MovieLens 数据的脚本MovieLens 的 ratings.csv 格式是userId,movieId,rating,timestamp写个脚本批量插入比手动敲 SQL 靠谱。import pandas as pd import sqlite3 conn sqlite3.connect(movie_recommend.db) ratings pd.read_csv(ratings.csv) # 列名映射到数据库字段 ratings ratings.rename(columns{ userId: user_id, movieId: movie_id }) ratings.to_sql(rating, conn, if_existsappend, indexFalse) conn.close()逻辑说明to_sql是 pandas 自带的批量写入比逐条 INSERT 快很多。if_existsappend表示追加第一次跑之前记得先建表。参数说明如果数据量超过百万行to_sql会慢可以加chunksize10000分批写。SQLite 单文件数据库适合毕设演示换成 MySQL 只需改连接字符串。4. 避坑与排查毕设答辩前最容易翻车的五个地方4.1 现象推荐结果全是同一部电影原因相似度矩阵对角线没置零或者推荐时没去掉已看过的电影导致某部热门电影因为评分人数多加权后分数永远最高。解决检查np.fill_diagonal(sim, 0)是否执行推荐函数里drop(labelslist(seen))是否生效。另外可以在排序前对分数做一次归一化除以该电影被推荐次数。4.2 现象数据库导入中文电影名变成乱码原因CSV 文件编码是 UTF-8但 SQLite 或 MySQL 连接时没指定编码或者建表时字符集用了 latin1。解决MySQL 建库时指定CHARACTER SET utf8mb4Python 连接时加charsetutf8mb4。SQLite 默认 UTF-8一般不会出问题但如果用 pandas 读 CSV加encodingutf-8。4.3 现象答辩时老师问「新用户怎么办」答不上来原因只做了协同过滤没考虑冷启动。新用户没有评分记录ItemCF 无法计算。解决在推荐函数里加分支新用户返回流行度最高的电影。更进一步可以在用户注册时让用户勾选几个喜欢的类型用内容推荐做初始推荐这样答辩时能多讲一层。4.4 现象源码在别人电脑上跑不起来原因依赖没写清楚或者用了绝对路径读数据文件。解决在项目根目录放requirements.txt列出 pandas、numpy、flask 等依赖及版本。数据文件路径用os.path.join(os.path.dirname(__file__), data, ratings.csv)不要写D:\毕设\data\ratings.csv。4.5 现象推荐结果每次刷新都不一样原因相似度计算时用了随机初始化或者推荐排序时分数相同的电影顺序不稳定。解决ItemCF 本身是确定性的检查有没有引入随机数。排序时如果分数相同加一个次级排序键比如按电影 ID 升序保证结果可复现。5. 把系统跑起来Flask 接口与前端展示的最小闭环5.1 用 Flask 暴露推荐接口毕设演示需要一个能点击的界面最省事的方案是 Flask 加几个 HTML 页面。下面是一个推荐接口接收 user_id返回 JSON 格式的推荐列表。from flask import Flask, request, jsonify import pandas as pd import numpy as np app Flask(__name__) # 启动时加载数据和相似度矩阵避免每次请求重算 ratings pd.read_csv(data/ratings.csv) sim_df item_similarity(ratings) app.route(/recommend) def recommend(): user_id int(request.args.get(user_id, 1)) movies recommend_for_user(user_id, ratings, sim_df, top_n10) return jsonify({user_id: user_id, movies: movies}) if __name__ __main__: app.run(debugTrue, port5000)逻辑说明item_similarity和recommend_for_user是前面定义的函数放在同一个文件或 import 进来。启动时算一次相似度请求时只做查表和排序响应能在毫秒级。参数说明debugTrue只在开发时用答辩演示可以关掉。port5000是 Flask 默认端口如果被占用改成 5001。5.2 前端页面与数据库查询的衔接前端用一个简单的 HTML 表格展示推荐结果通过 fetch 调用接口。数据库里的recommendation表可以存离线算好的结果页面加载时直接查表这样即使算法部分出问题演示也不会崩。fetch(/recommend?user_id1) .then(res res.json()) .then(data { const list document.getElementById(movie-list); data.movies.forEach(mid { const li document.createElement(li); li.textContent 电影 ID: mid; list.appendChild(li); }); });逻辑说明fetch是浏览器原生 API不需要额外引入 jQuery。拿到 movie_id 后可以再查电影表拿标题这里为了简洁只显示 ID。参数说明user_id可以从登录 session 里取演示时写死 1 也行。如果要显示电影名在接口里 join 一下 movie 表返回 title 字段。6. 让推荐结果经得起追问离线评估与参数调优的一个实用技巧答辩时老师最可能问的是「你怎么知道推荐得准」。工业界用 A/B 测试毕设没这个条件但你可以做离线评估。把评分数据按时间切分前 80% 做训练后 20% 做测试对每个用户预测他实际看过的电影算命中率Hit Rate和 NDCG。这两个指标不用手写scikit-learn没有现成的但逻辑不复杂对每个测试用户取推荐列表 top 10看有多少落在测试集里命中数除以测试集电影数就是 Hit Rate。我一般会调的一个参数是相似度矩阵的邻居数量 K。ItemCF 里不是所有相似电影都参与推荐只取最相似的 K 部。K 太小推荐多样性差K 太大噪声多。在 MovieLens 100K 上K 取 20 到 50 之间通常 Hit Rate 最高。你可以写个循环K 从 10 到 100 步长 10跑一遍看指标曲线把这张图放进论文里比任何文字都有说服力。def evaluate_hit_rate(ratings, sim_df, K30, top_n10): 简单离线评估按时间切分算 Hit Rate ratings ratings.sort_values(timestamp) split int(len(ratings) * 0.8) train, test ratings.iloc[:split], ratings.iloc[split:] hits, total 0, 0 for uid in test[user_id].unique(): recs recommend_for_user(uid, train, sim_df, top_n) actual set(test[test[user_id] uid][movie_id]) hits len(set(recs) actual) total len(actual) return hits / total if total else 0逻辑说明按时间切分比随机切分更接近真实场景因为推荐系统是用过去预测未来。total累加的是测试集里所有用户实际看过的电影数hits是推荐命中的数量。参数说明K在recommend_for_user里可以通过similar.head(K)控制这里为了简洁没展开。top_n取 10 是常见评估口径和答辩演示保持一致。最后说个血泪经验毕设代码一定要在答辩前一周完整跑一遍从建库、导数据、算相似度到启动服务每一步都截图存档。我见过太多人算法调通了但数据库连不上或者换了台电脑路径全错。把requirements.txt、建表 SQL、数据导入脚本放在一个setup文件夹里写个README说明执行顺序这比多写两百行算法代码更能让你顺利通过。希望帮到你。本文还有配套的精品资源点击获取