资讯详情

酒店推荐系统毕设实战:协同过滤算法与前后端源码解析

📅 2026/10/11 22:08:32 | 华诺云谱 👁 阅读
酒店推荐系统毕设实战:协同过滤算法与前后端源码解析
简介这是一套面向高校学生与Python初学者的酒店推荐系统毕业设计完整源码包采用Django框架搭建前后端适合作为课程设计、毕业设计或Web开发练手项目。系统区分管理员与用户两类角色管理员可管理用户、客房类型、酒店客房、客房预定、入住登记、续订、退房、留言反馈及系统资料用户则可完成客房浏览预定、入住登记、续订与退房等操作业务闭环完整。压缩包共597个文件约33.83MB包含56个py后端源码、118个vue前端组件、63个js脚本、43个png与53个jpg界面素材以及sql建库脚本、bat启动脚本和项目说明文档前后端分离结构清晰。目前已有63人学习下载。随包附数据库文档、项目说明与演示文稿便于理解表结构设计、环境配置与模块划分可直接运行调试也能作为二次开发与答辩参考。1. 酒店推荐系统毕设从一份前后端源码里拆出可复现的推荐链路很多同学拿到「酒店推荐系统完整前后端源码LW」这类压缩包时第一反应是解压、跑起来、截图、写论文然后卡在答辩现场被问「你的推荐算法到底怎么算的」。我带过几届计算机毕业设计见过太多人把推荐系统做成了「酒店列表 一个随机排序按钮」。真正能撑住答辩追问的是你能说清楚用户画像怎么建、相似度怎么算、冷启动怎么兜底、前后端数据怎么串。这篇笔记就按这个思路把一份典型的 Python 酒店推荐系统前后端源码拆开讲——它适合正在做基于 Python 的毕业设计、想找一份能跑通又能讲明白的推荐系统源码的同学也适合已经拿到源码但不知道怎么改、怎么调参、怎么应对老师提问的人。下面从技术选型一路讲到排错尽量让你照着能复现。2. 推荐链路的技术选型为什么是协同过滤而不是深度学习2.1 毕设场景下推荐算法的现实约束先说结论绝大多数酒店推荐系统的毕业设计用协同过滤Collaborative Filtering就够了不要一上来就上深度学习。原因很实际——你的数据集规模通常只有几千条评分记录用户数和酒店数都有限神经网络在这种稀疏数据上不但训不出效果还会让你在答辩时被追问「为什么用 Transformer 做酒店推荐」时哑口无言。酒店推荐系统的核心数据一般是三类用户对酒店的评分或行为日志、酒店本身的属性价格、星级、位置、设施、用户的属性出行目的、预算区间。协同过滤分两种路线基于用户的协同过滤UserCF找「和你口味相似的人喜欢什么」基于物品的协同过滤ItemCF找「和你住过的酒店相似的酒店」。酒店场景下 ItemCF 更稳因为酒店数量相对固定物品相似度矩阵可以离线算好线上响应快。常见做法是离线用 ItemCF 算出酒店相似度矩阵存下来线上根据用户历史行为取 TopN 相似酒店做推荐再用规则做一层过滤比如价格区间、城市。这套链路在毕设里足够讲清楚也足够跑通。2.2 前后端分离的目录结构与依赖清单拿到源码包后先别急着跑花十分钟把目录结构看清楚。典型的 Python 酒店推荐系统前后端分离项目长这样hotel-recommend/ ├── backend/ │ ├── app.py # Flask/FastAPI 入口 │ ├── models/ # 数据模型 │ ├── recommend/ # 推荐算法核心 │ │ ├── item_cf.py # ItemCF 实现 │ │ └── data_loader.py # 数据加载 │ ├── requirements.txt │ └── data/ │ ├── hotels.csv │ └── ratings.csv ├── frontend/ │ ├── src/ │ │ ├── views/ # 页面 │ │ └── api/ # 接口封装 │ └── package.json └── README.md后端依赖一般就这几个装的时候注意 Python 版本建议 3.8 以上# 创建虚拟环境避免污染全局 python -m venv venv # Windows 激活 venv\Scripts\activate # Linux/Mac 激活 source venv/bin/activate # 安装依赖numpy 和 pandas 是推荐算法的基础 pip install flask pandas numpy scikit-learn # 如果前端要单独跑还需要 node 环境这里有个新手常踩的坑pip install报错多半是网络问题或 Python 版本不匹配。如果你在python安装numpy库的方法上卡住先确认 pip 版本再考虑换国内镜像源。虚拟环境这一步别省我见过太多人因为全局环境里包版本冲突调了一下午发现是 pandas 版本问题。2.3 数据表设计与接口约定推荐系统的数据表不用太复杂三张核心表就能撑起来表名关键字段说明useruser_id, username, city, budget用户基本信息hotelhotel_id, name, city, price, star, tags酒店属性ratinguser_id, hotel_id, score, timestamp评分行为接口约定上前端只需要调三个核心接口/api/hotels拿酒店列表、/api/recommend?user_idxxx拿个性化推荐、/api/rating提交评分。推荐接口返回的数据结构建议统一成{hotel_id, name, price, score, reason}其中reason字段用来解释推荐理由答辩时这个字段很加分。3. ItemCF 推荐算法落地从相似度矩阵到接口返回3.1 用 pandas 构建用户-物品评分矩阵ItemCF 的第一步是把评分数据转成矩阵。假设ratings.csv有三列user_id、hotel_id、score。import pandas as pd import numpy as np def build_matrix(ratings_path): # 读取评分数据 df pd.read_csv(ratings_path) # 用 pivot 构建用户-物品矩阵缺失值填 0 matrix df.pivot_table( indexuser_id, columnshotel_id, valuesscore, fill_value0 ) return matrix # 调用 matrix build_matrix(data/ratings.csv) print(matrix.shape) # (用户数, 酒店数)这段代码的逻辑很直白pivot_table把长表转成宽表行是用户、列是酒店、值是评分。fill_value0表示没评过分的填 0后面算相似度时会用掩码排除掉。参数上要注意如果你的评分是 1-5 分制0 表示未评分没问题但如果评分本身包含 0 分就得换个填充值比如 -1。矩阵稀疏度是你要心里有数的指标。酒店推荐场景下一个用户通常只评过个位数酒店矩阵稀疏度往往在 95% 以上。这也是为什么不用矩阵分解的原因——数据太稀疏分解出来的隐向量没意义。3.2 余弦相似度计算与 TopN 截断有了矩阵接下来算酒店之间的相似度。用余弦相似度from sklearn.metrics.pairwise import cosine_similarity def compute_similarity(matrix): # 转置让行变成酒店列变成用户 item_matrix matrix.T # 计算酒店之间的余弦相似度 sim cosine_similarity(item_matrix) # 转成 DataFrame 方便查 sim_df pd.DataFrame( sim, indexitem_matrix.index, columnsitem_matrix.index ) return sim_df sim_df compute_similarity(matrix)cosine_similarity返回的是对称矩阵对角线是 1。实际用的时候要做一个 TopN 截断——每个酒店只保留最相似的 K 个其余置 0。K 一般取 10 到 20太大推荐会发散太小又不够多样。我一般取 15你可以根据酒店总数调整酒店多就取大一点酒店少就取小一点。提示相似度矩阵算完建议存成 pickle 或 npy 文件不要每次请求都重算。线上接口只做查表和排序响应能控制在 50ms 以内。3.3 推荐接口的组装与冷启动兜底推荐接口的核心逻辑是拿用户评过分的酒店找到它们的相似酒店加权求和排序。def recommend(user_id, matrix, sim_df, top_n10): # 用户评过分的酒店 user_ratings matrix.loc[user_id] rated user_ratings[user_ratings 0].index.tolist() if not rated: # 冷启动返回热门酒店 return get_hot_hotels(top_n) # 累加相似度得分 scores {} for hotel in rated: sim_hotels sim_df[hotel].sort_values(ascendingFalse)[1:16] for h, s in sim_hotels.items(): if h in rated: continue # 跳过已评分的 scores[h] scores.get(h, 0) s * user_ratings[hotel] # 排序取 TopN result sorted(scores.items(), keylambda x: x[1], reverseTrue)[:top_n] return result冷启动是答辩必问的点。get_hot_hotels可以按评分均值和评分数量综合排序也可以按城市过滤后返回热门。参数上top_n控制返回数量前端一般一页展示 10 个相似酒店取 15 个是经验值你可以做成配置项。4. 前后端联调与部署把推荐结果送到页面上4.1 Flask 接口封装与跨域处理后端用 Flask 起服务推荐接口封装成 RESTful 风格from flask import Flask, request, jsonify from flask_cors import CORS app Flask(__name__) CORS(app) # 解决前端跨域 app.route(/api/recommend) def api_recommend(): user_id request.args.get(user_id, typeint) if not user_id: return jsonify({code: 400, msg: 缺少 user_id}) result recommend(user_id, matrix, sim_df) # 组装返回数据 data [] for hotel_id, score in result: hotel hotel_info[hotel_id] data.append({ hotel_id: hotel_id, name: hotel[name], price: hotel[price], score: round(score, 2), reason: f基于您住过的{hotel[similar_to]}推荐 }) return jsonify({code: 200, data: data})CORS(app)这行别漏前后端分离项目 90% 的联调失败都是跨域。reason字段我特意拼了一个推荐理由答辩时老师问「为什么推荐这个」你就有话可说。参数上user_id用typeint做类型校验避免前端传字符串导致查询报错。4.2 前端调用与推荐结果渲染前端不管用 Vue 还是 React核心就是发请求、渲染列表。以 Vue 为例// api/hotel.js import axios from axios export function getRecommend(userId) { return axios.get(/api/recommend, { params: { user_id: userId } }) } // 组件里调用 async loadRecommend() { const res await getRecommend(this.userId) if (res.data.code 200) { this.hotelList res.data.data } }渲染时把reason字段展示出来用户体验会好很多。如果推荐结果为空前端要有个兜底提示别让页面白屏。4.3 本地跑通的最小验证步骤跑通整个系统按这个顺序来别跳步后端单独启动python app.py确认 5000 端口能访问用 curl 或 Postman 测推荐接口curl http://localhost:5000/api/recommend?user_id1确认返回 JSON 有数据再启动前端前端npm run serve打开页面看推荐列表是否渲染提交一条评分刷新看推荐是否变化如果第 2 步返回空数组先检查ratings.csv里有没有这个 user_id 的记录。如果第 4 步页面报错打开浏览器控制台看是不是跨域或接口地址写错。5. 避坑与排查那些让毕设翻车的细节5.1 评分矩阵全为 0 导致相似度计算失效现象推荐接口返回空列表或者所有酒店得分一样。原因ratings.csv里 user_id 或 hotel_id 类型不一致pivot 后全是 0。解决读 CSV 时强制指定类型dtype{user_id: int, hotel_id: int}并在构建矩阵后打印matrix.sum().sum()确认非零。5.2 相似度矩阵内存溢出现象酒店数量上千后cosine_similarity直接卡死或内存报错。原因相似度矩阵是 N×N 的稠密矩阵N5000 时就是 2500 万个浮点数。解决改用稀疏矩阵存储或者只算 TopN 相似度不存全矩阵。毕设数据量一般不大但如果你爬了真实数据这个问题一定会遇到。5.3 前端跨域请求被拦截现象浏览器控制台报Access-Control-Allow-Origin。原因后端没开 CORS 或前端代理没配。解决后端加flask_cors前端在vue.config.js里配devServer.proxy。两个方案选一个就行别同时用。5.4 推荐结果重复或包含已住酒店现象推荐列表里出现用户已经评过分的酒店。原因累加得分时没排除已评分物品。解决在recommend函数里加if h in rated: continue这行代码看着简单漏了就是逻辑 bug。5.5 中文乱码导致酒店名显示异常现象页面酒店名显示成æéå。原因CSV 读取时编码不对。解决pd.read_csv(path, encodingutf-8)如果还乱码就试gbk。Windows 下 Excel 导出的 CSV 默认是 gbk这个坑我踩过不止一次。6. 让推荐结果可解释一个答辩加分的小技巧推荐系统最怕被问「你为什么推荐这个」。协同过滤本身是个黑匣子但你可以给它加一层解释。我的做法是在推荐接口里记录每个推荐结果的「贡献来源」——也就是哪个已住酒店贡献了最多相似度。def recommend_with_reason(user_id, matrix, sim_df, top_n10): user_ratings matrix.loc[user_id] rated user_ratings[user_ratings 0].index.tolist() scores {} sources {} # 记录来源 for hotel in rated: sim_hotels sim_df[hotel].sort_values(ascendingFalse)[1:16] for h, s in sim_hotels.items(): if h in rated: continue contribution s * user_ratings[hotel] scores[h] scores.get(h, 0) contribution # 记录最大贡献来源 if h not in sources or contribution sources[h][1]: sources[h] (hotel, contribution) result sorted(scores.items(), keylambda x: x[1], reverseTrue)[:top_n] # 附加解释 explained [] for hotel_id, score in result: src_hotel, _ sources[hotel_id] explained.append({ hotel_id: hotel_id, score: round(score, 2), reason: f因为您住过「{hotel_info[src_hotel][name]}」 }) return explained这段代码比基础版多了一个sources字典记录每个推荐酒店的最大贡献来源。返回给前端后页面上就能显示「因为您住过 XX 酒店为您推荐 YY 酒店」。答辩时老师问推荐依据你直接指着页面说比背算法公式有说服力得多。参数上sources只保留最大贡献如果你想展示多个来源可以改成列表存 Top3。性能上多了一个字典操作对响应时间影响可以忽略。验证这个方法很简单找一个测试用户手动看他评过分的酒店再对照推荐结果的reason字段确认逻辑对得上。如果 reason 里的酒店用户根本没住过说明rated列表取错了回去检查matrix.loc[user_id]的取值。我自己的习惯是每次改完推荐逻辑先跑一个固定用户做回归测试看 Top10 列表有没有异常变化。这个习惯帮我省了很多次答辩前夜的紧急调试。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑