Django旅游推荐系统毕设实战:协同过滤算法与MySQL部署避坑指南 简介这是一套面向计算机相关专业毕设学生与Python项目实战学习者的旅游推荐数据分析可视化系统采用pythonDjangomysql技术栈并引入协同过滤算法实现个性化推荐可直接作为毕业设计、课程设计或期末大作业使用。压缩包共422个文件约9.36MB以174个js、59个css、40个json、27个py、18个html等为主涵盖前端页面样式、后端业务逻辑、数据配置与静态资源另附sql数据库脚本、docx部署说明及md文档结构完整便于二次开发。系统分为用户与管理员两端用户可完成登录注册、个人信息管理、景区浏览、评分收藏与智能推荐管理员可对景区信息、景区类型分类及用户资料进行增删改查与行为记录管理。项目经过严格调试确保可运行已有135人学习下载适合需要完整源码、数据库与部署文档的读者快速搭建环境、理解推荐算法落地流程并完成毕设交付。1. 旅游推荐系统拆包一份能跑通的 Django 毕设长什么样很多做毕设的同学都遇到过这种局面选题定了“旅游推荐数据分析可视化”网上搜到的源码包要么跑不起来要么只有一堆前端 CSS 文件后端逻辑和数据库全是空的。我最近拆了一个 Python 项目实战包标题是旅游推荐数据分析可视化技术栈是 python Django mysql 协同过滤算法压缩包里带了源码、数据库和部署说明。打开一看前端样式文件确实不少——bootstrap.css、bootstrap.rtl.css、bootstrap.min.css、remixicon.css、boxicons.css、bootstrap-icons.css、skin.css、bootstrap-grid.rtl.css 这些都在说明界面层是认真搭过的不是随便糊一个页面。这个包适合两类人一类是计算机相关专业正在做毕设、需要一套完整可运行系统的学生另一类是想拿一个真实 Django 项目练手、把协同过滤从公式落到代码里的 Python 学习者。它解决的核心问题不是“教你 Django 语法”而是给你一套已经调通的骨架让你把精力放在理解推荐逻辑和改造成自己课题上。2. 环境搭建与数据库导入从解压到登录后台2.1 依赖清单与版本选择拿到压缩包后第一件事不是急着python manage.py runserver而是先确认 Python 版本和依赖。Django 项目对版本比较敏感尤其是涉及 mysqlclient 和协同过滤里用到的 numpy 时。我一般会先建一个独立虚拟环境避免和系统里其他包打架。# 创建虚拟环境python 版本建议 3.8 到 3.10 python -m venv venv # 激活虚拟环境 # Windows venv\Scripts\activate # macOS / Linux source venv/bin/activate # 安装核心依赖版本按部署说明里的 requirements 为准 pip install django mysqlclient numpy pandas这里有几个参数要留意。mysqlclient是 Django 连接 MySQL 最常用的驱动比 pymysql 性能好但在 Windows 上编译需要本地有 MySQL 开发库装不上时可以临时换pymysql并在__init__.py里做兼容。numpy和pandas不是 Django 本身需要的而是协同过滤计算相似度矩阵时会用到如果部署说明里没列也要提前装上否则推荐模块一跑就报ModuleNotFoundError。2.2 数据库配置与导入项目用的是 MySQL部署说明里一般会给一个.sql文件。常见做法是先在 MySQL 里建一个空库字符集用utf8mb4然后导入数据。-- 建库字符集必须用 utf8mb4否则中文景区名会乱码 CREATE DATABASE travel_recommend DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; -- 导入数据在命令行执行 -- mysql -u root -p travel_recommend travel_recommend.sql导入完成后打开 Django 的settings.py找到DATABASES配置段把 NAME、USER、PASSWORD、HOST、PORT 改成你本地的。这里有个容易翻车的点如果.sql文件里已经建了库你再手动建一次会冲突导入前先确认文件头部有没有CREATE DATABASE语句。另外Django 的migrate命令和直接导入.sql是两条路如果.sql里已经包含全部表结构和数据就不需要再执行migrate否则可能报表已存在。# settings.py 数据库配置示例 DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: travel_recommend, # 数据库名 USER: root, # 你的 MySQL 用户名 PASSWORD: your_password, # 你的密码 HOST: 127.0.0.1, PORT: 3306, OPTIONS: {charset: utf8mb4}, # 防止中文乱码 } }配置改完后执行python manage.py runserver浏览器打开127.0.0.1:8000能看到登录页就说明环境通了。管理员账号一般在.sql里已经预置或者用python manage.py createsuperuser新建一个登录后台就能看到景区管理、用户管理这些入口。3. 协同过滤推荐模块用户评分数据怎么变成推荐列表3.1 协同过滤在旅游场景里的选型理由旅游推荐和电商推荐有个明显区别用户对景区的评分行为比买东西稀疏得多。一个人可能一年只去几个地方评分矩阵非常空。项目用的是协同过滤算法具体来说是基于用户的协同过滤UserCF或基于物品的协同过滤ItemCF两者在这个场景下各有取舍。UserCF 的思路是“找和你口味相似的人把他们去过但你没去过的景区推给你”。优点是能发现跨类型的惊喜推荐缺点是用户数量一多相似度矩阵计算量会爆炸。ItemCF 的思路是“你给某个景区打了高分找和它相似的景区推给你”优点是物品数量通常比用户少矩阵更稳定缺点是推荐结果容易局限在同一类型里。这个项目里景区数量是可控的ItemCF 更常见但具体用哪种要看源码里recommend模块的实现。3.2 评分矩阵构建与相似度计算协同过滤的核心是把用户-景区评分表转成矩阵然后算相似度。下面这段代码是我根据这类项目常见写法还原的逻辑是先查出所有评分记录构建用户-物品矩阵再用余弦相似度算景区之间的相似度。import numpy as np from django.db.models import Q from .models import Rating, ScenicSpot def build_similarity_matrix(): 构建景区相似度矩阵基于用户评分 # 取出所有评分记录 ratings Rating.objects.all().values(user_id, spot_id, score) # 获取所有景区 id 和用户 id spot_ids list(ScenicSpot.objects.values_list(id, flatTrue)) user_ids list(set(r[user_id] for r in ratings)) # 建立 id 到索引的映射 spot_index {sid: i for i, sid in enumerate(spot_ids)} user_index {uid: i for i, uid in enumerate(user_ids)} # 初始化评分矩阵行是用户列是景区 matrix np.zeros((len(user_ids), len(spot_ids))) for r in ratings: matrix[user_index[r[user_id]], spot_index[r[spot_id]]] r[score] # 计算景区之间的余弦相似度 # 对列向量做归一化避免量纲影响 norm np.linalg.norm(matrix, axis0) norm[norm 0] 1 # 防止除零 normalized matrix / norm similarity np.dot(normalized.T, normalized) return spot_ids, similarity这段代码有几个参数和边界要说明。matrix是用户数乘景区数的二维数组实际项目里这个矩阵可能非常稀疏大部分位置是 0。norm计算的是每个景区列向量的模长归一化后再做点积得到的就是余弦相似度取值范围在 -1 到 1 之间旅游评分通常是 1 到 5 的正数所以相似度基本落在 0 到 1。norm[norm 0] 1这行是后悔药防止某个景区没有任何评分时除零报错。相似度矩阵是对称矩阵对角线是景区和自己的相似度恒为 1推荐时要把自己排除掉。3.3 生成推荐列表与冷启动处理拿到相似度矩阵后推荐逻辑就是对用户已评分的景区找到最相似的 K 个景区按相似度加权打分取 TopN 返回。def recommend_for_user(user_id, top_n10, k5): 给指定用户生成推荐列表 spot_ids, similarity build_similarity_matrix() spot_index {sid: i for i, sid in enumerate(spot_ids)} # 用户已评分的景区 rated Rating.objects.filter(user_iduser_id).values_list(spot_id, score) rated_dict {r[0]: r[1] for r in rated} if not rated_dict: # 冷启动没有评分记录时返回评分最高的景区 return ScenicSpot.objects.order_by(-avg_score)[:top_n] scores {} for spot_id, score in rated_dict.items(): if spot_id not in spot_index: continue idx spot_index[spot_id] # 取相似度最高的 k 个景区 sim_row similarity[idx] top_k_idx np.argsort(sim_row)[::-1][1:k1] # 跳过自己 for i in top_k_idx: candidate spot_ids[i] if candidate in rated_dict: continue # 已经评过的不再推荐 scores[candidate] scores.get(candidate, 0) sim_row[i] * score # 按得分排序取前 top_n sorted_spots sorted(scores.items(), keylambda x: x[1], reverseTrue)[:top_n] return ScenicSpot.objects.filter(id__in[s[0] for s in sorted_spots])top_n控制返回多少个推荐k控制参考多少个相似景区这两个参数直接决定推荐结果的多样性和准确度。k太小推荐容易集中在少数几个热门景区k太大计算量上升且可能引入不相关的噪声。冷启动那段是很多毕设里被忽略的新用户没有任何评分协同过滤算不出来常见做法是退化成热门推荐按景区平均分排序。这个逻辑在答辩时经常被问到提前想清楚能省不少事。4. 避坑与排查部署和运行中最容易翻车的五个点4.1 中文乱码景区名变成问号现象是导入.sql后后台看到的景区名称全是???或者乱码。原因通常是建库时字符集用了latin1或者utf8而 MySQL 的utf8只支持三字节存不下某些生僻字。解决办法是建库时强制utf8mb4并且在 Django 的DATABASES里加OPTIONS: {charset: utf8mb4}。如果已经导入错了需要重建库再导一次改字符集不会自动修复已有数据。4.2 静态文件 404页面样式全丢现象是登录页能打开但 bootstrap 样式没生效页面排版全乱。原因是 Django 在生产模式下不会自动托管静态文件开发模式下需要确认settings.py里DEBUG True并且INSTALLED_APPS里有django.contrib.staticfiles。如果还是 404检查STATIC_URL和STATICFILES_DIRS是否指向了正确的static目录。压缩包里那一堆bootstrap.css、remixicon.css、boxicons.css应该放在static/css/下模板里用{% static css/bootstrap.css %}引用。4.3 协同过滤报 ModuleNotFoundError现象是点开推荐页面直接 500控制台提示No module named numpy或pandas。原因是部署说明里只列了 Django 和 mysqlclient没提科学计算库。解决办法是手动pip install numpy pandas。如果安装 numpy 时编译报错可以换用预编译的 wheel 包或者降级到和 Python 版本匹配的 numpy 版本。4.4 数据库连接被拒绝现象是runserver启动时报django.db.utils.OperationalError: (2003, Cant connect to MySQL server)。原因通常是 MySQL 服务没启动或者HOST填了localhost但 MySQL 只监听127.0.0.1再或者端口不是默认的 3306。解决办法是先确认 MySQL 服务在运行然后把HOST改成127.0.0.1端口按实际配置填。如果用了 Docker 或者远程数据库还要检查防火墙和用户权限。4.5 推荐结果为空或重复现象是推荐列表要么空白要么反复推同一个景区。原因是相似度矩阵计算时没有排除用户已评分的景区或者top_k_idx没有跳过自身。解决办法是在生成推荐时加判断if candidate in rated_dict: continue并且在取相似景区时用[1:k1]跳过索引 0 的自己。另外如果某个景区评分人数太少相似度会不稳定可以在计算前过滤掉评分次数低于阈值的景区。5. 从跑通到改出彩把毕设做出差异化的三个技巧5.1 给推荐结果加可解释性答辩时老师最爱问的一句话是“你为什么推荐这个”。协同过滤本身是个黑匣子但你可以把相似度分数和参考景区暴露出来。比如在推荐列表里加一列“因为你去过 XX 景区”把sim_row[i]最高的那个已评分景区作为推荐理由。实现上就是在recommend_for_user里多存一个reason字段返回时带上景区名。这个改动不大但能让你的系统从“能跑”变成“能讲”。5.2 用评分数量做加权纯余弦相似度有个问题两个景区如果都只有一条评分且分数碰巧一样相似度会算成 1但这明显不可靠。常见做法是引入显著性加权相似度乘以min(共同评分用户数, 阈值) / 阈值共同评分用户越多相似度越可信。这个公式在推荐系统里叫“显著性加权”改起来就几行代码但能明显减少冷门景区之间的虚假相似。# 显著性加权示例common_users 是共同评分用户数 threshold 5 weight min(common_users, threshold) / threshold similarity[i][j] similarity[i][j] * weight5.3 验证推荐效果的一个笨办法没有真实用户日志的情况下怎么证明你的推荐比“按平均分排序”好我一般会做离线评估把评分数据按 8:2 切成训练集和测试集用训练集算相似度对测试集里的用户生成推荐看推荐列表里有多少景区是用户实际评过分的。这个指标叫命中率Hit Rate虽然粗糙但比空口说“推荐效果不错”有说服力。具体做法是随机留出每个用户 20% 的评分不参与计算然后看 Top10 推荐里命中几个。命中率能到 10% 以上在毕设里就算能交代过去了。从那以后我每次拿到这类源码包都强制先跑一遍数据库导入和推荐接口确认评分矩阵不是空的、相似度不是全零再去看前端样式。希望帮到你。本文还有配套的精品资源点击获取