简介这份资源面向计算机专业毕业生与推荐系统入门者提供一套可直接运行的电影推荐系统完整实现同时覆盖基于用户与基于物品两种协同过滤算法适合用作毕业设计参考或推荐算法练手项目。系统采用Python3.7、Django2.2.1与MySQL/SQLite搭建前端基于Bootstrap3数据源自MovieLens数据集并附有豆瓣爬虫脚本用于扩充影片信息。压缩包共83个文件、约8.41MB以28个Python源码文件为核心配合16个HTML模板、4个CSV数据文件、数据库文件及论文PDF、readme技术文档另含少量缓存与配置文件目录按应用模块划分清晰。目前已有10613人学习下载。读者可从中获得完整的协同过滤推荐流程实现、可直接部署的工程结构、配套论文与数据库文件以及爬虫与数据填充脚本便于对照理解算法细节、快速复现系统并在此基础上做二次开发。1. 一个能跑通的推荐系统比十个算法Demo更值钱很多同学做毕业设计时卡在的不是算法公式而是“推荐结果怎么从数据库里取出来、怎么在页面上展示、用户点完评分后怎么立刻影响下一次推荐”。这套基于协同过滤的电影推荐系统用 Python 3.7 Django 2.2.1 MySQL 把这条链路完整串了起来用户注册登录、浏览电影、打分评论、后台管理推荐模块根据评分矩阵算出相似用户和相似电影再把结果渲染到首页。它适合两类人一是需要一份结构完整、能演示、能答辩的推荐系统项目二是想搞懂协同过滤工程落地细节的开发者。下面按“能干什么、怎么跑、怎么改”的顺序拆开讲。2. 技术栈与目录结构Django 2.2.1 下的推荐系统骨架2.1 为什么是 Django 2.2.1 MySQL 这套组合这个项目的技术选型很典型Django 负责路由、模板、ORM 和后台管理MySQL 存用户、电影、评分三类核心数据协同过滤算法用 Python 原生实现不依赖重型机器学习框架。Django 2.2.1 是 LTS 版本urls.py还支持path()写法ORM 的aggregate、annotate足够支撑评分统计。MySQL 选 5.7 或 8.0 都行字符集建议utf8mb4否则电影名里的特殊符号会翻车。常见做法是把推荐算法放在独立的recommend应用里和movie、user应用解耦。这样算法改起来不影响业务表结构也方便单独写测试。2.2 目录结构长什么样一个能跑起来的 Django 项目目录大致如下应用名按常见命名movie_recommend/ ├── manage.py ├── movie_recommend/ # 项目配置 │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── user/ # 用户注册登录、个人信息 │ ├── models.py │ ├── views.py │ └── urls.py ├── movie/ # 电影列表、详情、评分 │ ├── models.py │ ├── views.py │ └── urls.py ├── recommend/ # 协同过滤算法与推荐视图 │ ├── cf.py # 核心算法 │ ├── views.py │ └── urls.py ├── templates/ # 页面模板 ├── static/ # CSS、JS、图片 └── requirements.txtsettings.py里要改三处DATABASES指向本地 MySQL、INSTALLED_APPS注册三个应用、TEMPLATES的DIRS指向templates。STATICFILES_DIRS也要配上不然页面样式全丢。2.3 数据模型三张表撑起推荐推荐系统的数据模型不复杂但字段设计直接影响算法能不能算。核心是用户、电影、评分三张表# movie/models.py from django.db import models from django.contrib.auth.models import User class Movie(models.Model): title models.CharField(max_length200, verbose_name电影名) genre models.CharField(max_length100, verbose_name类型) year models.IntegerField(verbose_name年份) cover models.CharField(max_length300, blankTrue, verbose_name封面地址) description models.TextField(blankTrue, verbose_name简介) def __str__(self): return self.title class Rating(models.Model): user models.ForeignKey(User, on_deletemodels.CASCADE) movie models.ForeignKey(Movie, on_deletemodels.CASCADE) score models.FloatField(verbose_name评分) # 1~5 created_at models.DateTimeField(auto_now_addTrue) class Meta: unique_together (user, movie) # 一个用户对一部电影只留一条评分unique_together是关键没有它同一用户重复打分会让评分矩阵出现重复行相似度计算直接失真。score用FloatField而不是整数是为了后续做归一化时保留小数。on_deletemodels.CASCADE表示删用户或删电影时评分一起清掉避免脏数据。3. 协同过滤核心实现从评分矩阵到推荐列表3.1 评分矩阵怎么构建协同过滤的第一步是把数据库里的评分转成矩阵。用户数多的时候不能全量加载常见做法是先取“活跃用户”或“有评分记录的用户”。这里用 Django ORM 一次性拉出评分再用字典构建# recommend/cf.py from movie.models import Rating def build_matrix(): 返回 {user_id: {movie_id: score}} 的嵌套字典 matrix {} for r in Rating.objects.values(user_id, movie_id, score): matrix.setdefault(r[user_id], {})[r[movie_id]] r[score] return matrix用嵌套字典而不是 NumPy 矩阵是因为电影 ID 不连续、用户评分稀疏字典能省内存也方便按 key 取。values()只取三列避免把整行对象加载进来。数据量上万条时这个查询要加索引Rating表的user和movie外键 Django 默认建索引够用。3.2 用户相似度余弦相似度的工程写法基于用户的协同过滤核心是算两个用户共同评分过的电影集合上的相似度。用余弦相似度import math def cosine_sim(u1, u2, matrix): u1、u2 为 user_id返回相似度 m1, m2 matrix.get(u1, {}), matrix.get(u2, {}) common set(m1) set(m2) if not common: return 0.0 dot sum(m1[m] * m2[m] for m in common) norm1 math.sqrt(sum(v * v for v in m1.values())) norm2 math.sqrt(sum(v * v for v in m2.values())) if norm1 0 or norm2 0: return 0.0 return dot / (norm1 * norm2)common为空直接返回 0这是稀疏数据下最常见的分支必须提前短路否则后面除零。norm用该用户全部评分算而不是只算共同项这是标准余弦定义如果只算共同项相似度会虚高。实际跑的时候用户两两比较是 O(n²)用户上千就会慢常见做法是只对“和目标用户有共同电影”的用户算相似度先做一次集合过滤。3.3 生成推荐加权评分排序取 TopN拿到相似用户后把相似用户评过、目标用户没看过的电影按相似度加权求和排序取前 Ndef recommend_for_user(user_id, matrix, top_n10, k20): 基于用户的协同过滤推荐 sims [] for other in matrix: if other user_id: continue sim cosine_sim(user_id, other, matrix) if sim 0: sims.append((other, sim)) sims.sort(keylambda x: x[1], reverseTrue) sims sims[:k] # 只取最相似的 k 个用户 scores {} watched set(matrix.get(user_id, {})) for other, sim in sims: for movie_id, score in matrix[other].items(): if movie_id in watched: continue scores[movie_id] scores.get(movie_id, 0) sim * score ranked sorted(scores.items(), keylambda x: x[1], reverseTrue) return [mid for mid, _ in ranked[:top_n]]k20是邻居数上限控制计算量top_n10是最终推荐条数。watched集合用来过滤已看过的电影不然推荐里会出现用户刚打过分的那部。加权用sim * score相似度越高、对方打分越高推荐权重越大。这个函数返回的是电影 ID 列表视图里再用Movie.objects.filter(id__in...)取详情。3.4 基于物品的协同过滤换个角度算相似电影除了推给用户详情页常需要“相似电影”。这时算物品相似度更合适两部电影被同一批用户打分的模式越像越相似。实现上把矩阵转置逻辑和用户相似度一致只是 key 换成电影 ID。工程里一般把物品相似度离线算好存表详情页直接查避免每次请求都重算。4. 从零跑起来环境、数据库与启动步骤4.1 环境准备与依赖安装Python 3.7 现在装起来要留意建议用 conda 建虚拟环境避免和系统 Python 冲突conda create -n movie_rec python3.7 conda activate movie_rec pip install django2.2.1 mysqlclient1.4.6mysqlclient在 Windows 上编译容易报错备选是pymysql在movie_recommend/__init__.py里加两行import pymysql; pymysql.install_as_MySQLdb()即可。Django 2.2.1 对mysqlclient版本有要求1.4.x 比较稳。4.2 数据库配置与迁移先在 MySQL 里建库CREATE DATABASE movie_rec DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;然后改settings.pyDATABASES { default: { ENGINE: django.db.backends.mysql, NAME: movie_rec, USER: root, PASSWORD: 你的密码, HOST: 127.0.0.1, PORT: 3306, OPTIONS: {charset: utf8mb4}, } }执行迁移python manage.py makemigrations python manage.py migrate python manage.py createsuperuser迁移会建出auth_user、movie_movie、movie_rating等表。createsuperuser建的后台账号用来录电影数据没有数据推荐模块跑不出结果。4.3 灌数据与启动验证最少要灌几十部电影、几个测试用户和一批评分协同过滤才有邻居可算。可以在 Django shell 里批量造# python manage.py shell from django.contrib.auth.models import User from movie.models import Movie, Rating import random users [User.objects.create_user(fu{i}, password123456) for i in range(1, 6)] movies [Movie.objects.create(titlef电影{i}, genre剧情, year2020) for i in range(1, 21)] for u in users: for m in random.sample(movies, 8): Rating.objects.create(useru, moviem, scorerandom.randint(1, 5))启动python manage.py runserver访问首页看推荐列表是否出来。如果推荐为空先确认评分表有数据、当前登录用户有评分记录——新用户没有评分时基于用户的协同过滤算不出邻居这是算法本身的冷启动问题不是 bug。5. 避坑与排查跑推荐系统最容易翻车的几处5.1 推荐结果为空现象登录后首页推荐区空白。原因通常是当前用户没有任何评分matrix.get(user_id)为空相似用户集合为空。解决给新用户做热门电影兜底按平均评分排序取 TopN或者在页面上引导先打分。5.2 相似度全是 0 或 NaN现象推荐列表顺序随机。原因多为评分字段存了 0 或空值或者norm计算时某用户评分为全 0 导致除零。解决入库时校验score在 1~5算法里对norm 0提前返回 0并在build_matrix里过滤掉非法评分。5.3 中文乱码现象电影名在页面显示问号。原因是数据库或连接字符集不是utf8mb4。解决建库时指定字符集settings.py的OPTIONS加charset已有表用ALTER TABLE ... CONVERT TO CHARACTER SET utf8mb4修。5.4 迁移报错表已存在现象migrate提示表已存在。原因多是之前手动建过表或迁移记录不一致。解决开发环境可以删库重建生产环境用--fake标记已应用的迁移别直接删表。5.5 推荐接口响应慢现象用户量上来后首页加载好几秒。原因是每次请求都全量算相似度。解决把用户相似度离线算好存缓存或表里请求时只做加权排序或者限制参与计算的用户数只取最近活跃的一批。6. 二次开发把推荐从“能跑”改到“能看”这套项目的改造空间主要在推荐质量和工程结构两处。先说算法侧基于用户的协同过滤在用户少时效果还行用户一多就慢我一般会加一层基于物品的推荐做互补详情页用物品相似度、首页用用户相似度两边结果合并去重。冷启动用热门兜底别让新用户看到空白页。评分数据可以加时间衰减score * exp(-λ * days)让近期评分权重更高这个改动只在build_matrix里加一行不影响其他模块。工程侧建议把算法调用从视图里抽出来做成recommend/services.py视图只负责取参数和渲染。这样换算法、加缓存都不用动views.py。缓存用 Django 自带的cache框架把recommend_for_user的结果按user_id存 10 分钟用户打分后手动清掉对应 key。验证方法很简单造两个评分模式明显不同的用户看推荐结果是否分化再给同一用户重复打分确认unique_together生效、推荐不重复。有个血泪经验改算法前先把当前推荐结果存一份快照改完对比 TopN 重合率重合率骤降往往说明相似度计算被改坏了而不是“效果变好”。从那以后我每次动协同过滤的相似度公式都强制走一遍“快照对比 小样本人工核对”的流程省得答辩时被问“为什么推荐的全是同一部”。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?