☰
Python酒店推荐系统毕业设计:从算法选型到源码部署全解析
2026/9/26 10:59:28 网站建设 项目流程

简介:面向高校毕业设计或课程设计场景的酒店推荐系统完整项目,基于Python 3.7.7与Django框架开发,后端使用MySQL 5.7+存储,涵盖管理员与用户双端功能。管理员可管理用户、客房类型、酒店客房、预定审核、入住登记、续订退房、留言反馈及系统配置;用户端支持客房浏览、在线预定、入住登记、续订退房与个人资料维护,前后端源码齐全并附带LW文档。资源包共597个文件,体积33.83MB,主要包含Vue前端页面、svg图标、JavaScript脚本、Python逻辑代码、pyc编译文件、CSS样式以及数据库SQL脚本、启动运行bat和项目说明doc等,目录结构清晰,便于二次开发与部署调试。压缩包内含安装、运行、构建等bat脚本,可按照文档配置环境,快速跑通预定、入住、续订、退房全流程。目前已有63人学习下载,适合需要参考完整业务闭环或快速搭建毕设演示项目的Python初学者及应届生使用。

1. 酒店推荐系统是什么:先想清楚这个题目值不值得做

把「酒店推荐系统」这种题目做成 Python 毕业设计,最大的价值在于它同时压中了推荐算法、前后端分离开发、数据库建模三个考核点。用户打开页面看到一排“猜你喜欢”,背后是物品相似度矩阵、用户行为日志和冷启动兜底规则在共同起作用。这篇笔记按我实际做过的方案讲清楚:算法怎么选、源码怎么跑、数据怎么造,以及哪些坑会让答辩现场翻车。适合准备做 Python 方向选题、又不想只写 CRUD 的同学。

2. 推荐算法选型:协同过滤、基于内容还是混合策略最稳

2.1 协同过滤的两种形态:在酒店场景里该用 UserCF 还是 ItemCF

推荐系统最常见的落地方案是协同过滤,它不关心酒店长什么样,只关心“谁和谁的行为像”。核心输入是一张行为矩阵:行是用户,列是酒店,格子里是评分或行为加权值。矩阵里多数位置是空的,因为一个用户一辈子也住不了几家酒店。

UserCF 的思路是先找与目标用户行为最像的一批人,再把他们评分高的酒店加权推荐给目标用户;ItemCF 则反过来,先算酒店与酒店之间的相似度,再根据用户曾经产生过行为的酒店去扩展推荐。两者的中间结果都是相似度矩阵,区别只在矩阵的行列方向。

对比维度UserCFItemCF
计算对象用户与用户的相似度酒店与酒店的相似度
实时性用户行为变化会立刻影响邻居集合酒店属性稳定,相似度可离线算好
冷启动新用户无行为,完全失效新酒店无行为,完全失效
可解释性难以解释“为什么推这家”可用“你收藏过同价位酒店”解释
酒店场景结论不推荐做主算法推荐做主算法

酒店消费和买书、看视频不一样:用户订酒店大多依赖出差、旅行等具体场景,同一批人前后两次打开App的口味可能完全不同。UserCF 用“相似用户”去推,容易把用户过去某个场景下的偏好带到新场景里;而 ItemCF 只关心酒店本身像不像,价格区间、星级、位置这些属性不会因为用户心情变化而改变,所以更稳。

相似度计算多数项目用余弦相似度,代码很短:

import numpy as np import pandas as pd # rating_matrix: DataFrame, 行是用户, 列是酒店, 空位填 0 def item_similarity(rating_matrix): mat = rating_matrix.to_numpy(dtype=np.float64) # 按列计算模长, axis=0 表示沿着用户维度算每个酒店的向量长度 norm = np.linalg.norm(mat, axis=0) # 酒店与酒店的相似度 = 列向量两两点积 / 模长乘积 sim = mat.T @ mat / (norm[:, None] * norm[None, :] + 1e-8) return pd.DataFrame(sim, index=rating_matrix.columns, columns=rating_matrix.columns)

这里有一个关键参数 1e-8,它是个极小的平滑项,避免某个酒店完全没有行为时模长为 0 导致除零报错。mat.T @ mat是一次矩阵乘法,也就是把所有酒店的共现次数一次性算完,比嵌套 for 循环快几个数量级。如果只用一个用户的两条记录调试,它跑得不如循环直观,但数据到几千个酒店时,矩阵乘法是唯一能扛住的做法。

2.2 基于内容与混合推荐:冷启动兜底怎么做

协同过滤有一个致命缺陷:新用户没有任何行为记录,系统就不知道他是谁;新酒店没有任何曝光,系统也不知道它该推给谁。毕业设计里最容易出现的翻车现场就是——注册一个新账号,首页推荐列表是空的。

基于内容的推荐能部分解决这个问题。它把酒店自身属性做成特征向量:城市、价格区间、星级、设施标签(如“免费停车”“近地铁”),然后计算酒店与酒店在特征空间里的距离,或者把用户的偏好规则化之后做匹配。比如用户看过的酒店都是 300 元以下的三星级,那就把同价位、同城市的酒店优先推出来。这个方案不依赖行为量,适合做冷启动阶段的兜底。

实际项目里很少只用一个算法,常见做法是混合加权。推荐分 = 0.6 × ItemCF 分 + 0.3 × 基于内容分 + 0.1 × 热门酒店分,权重在配置里写成常量,方便答辩时现场调。另一个必做的兜底是行为阈值判断:当用户历史行为少于 3 条时,跳过协同过滤,直接走城市热门榜,因为这时算出来的相似度几乎没有统计意义,硬推荐反而显得系统很蠢。

权重怎么调没有标准答案。我一般先拿离线数据算一遍各项指标的命中率,再人肉看几个典型用户的结果,确认不是“热门酒店霸榜”。如果发现 ItemCF 推荐的酒店全部集中在同一价位,就把内容分的权重往上提;如果推荐结果五花八门,说明基于内容的特征太粗糙,优先检查城市和价格有没有进特征。

3. 把源码跑通:从 SQL 导入到前后端联调的全流程

3.1 源码包结构:先分清 backend、frontend、sql、LW 各管什么

拿到这种毕业设计压缩包,第一件事不是急着跑,而是先看清目录结构。常见版本会分成四块:

  • backend:Python 后端,一般用 Flask 或 FastAPI 写,包含推荐算法、用户登录、酒店列表接口。
  • frontend:Vue 前端,负责页面展示和请求后端接口。
  • sql:数据库初始化脚本,建表语句加初始数据。
  • LW:毕业设计论文和演示说明文档,答辩前重点看这里的技术描述。

有经验的开发者拿到后先做三件事:看根目录有没有 README,看 requirements.txt 里缺不缺库,看 sql 脚本能不能直接导入。很多包在网上传来传去,原作者的 Python 版本是 3.7,你的机器是 3.11,直接跑必然报错,这不是代码问题,是版本兼容问题。

3.2 环境准备:Python 虚拟环境和 Vue 依赖一次装齐

强烈建议先建虚拟环境,不要图省事直接往全局环境里装依赖。酒店推荐系统的依赖不算多,但 Flask、pandas、scikit-learn 这些包互相之间有版本约定,项目放到别的机器上答辩时,虚拟环境能帮你复现一模一样的运行状态。

python -m venv venv # Windows 系统执行 venv\Scripts\activate # macOS / Linux 执行 source venv/bin/activate pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple

Python 版本最好用 3.8 到 3.10 之间,太新的 3.11、3.12 有时会遇到某些依赖库还没发对应版本。-i参数指定的是清华 PyPI 镜像,国内下载速度快很多。装完后执行pip list核对关键包版本,pandas 和 numpy 是计算相似度的基础,scikit-learn 是可选加速项。

前端部分如果是常见 Vue 项目,在 frontend 目录下执行:

npm install

这个命令按 package.json 里的声明拉取依赖。如果 node_modules 已经存在,先删掉再重装,不然经常出现“当时能跑,现在报错”的玄学问题。npm install 是前端项目跑不动的第一排查点。

3.3 数据库初始化:导入 SQL 和改连接配置

数据库是整个系统的地基,推荐算法读的数据全在这里。先用命令行建库,再导入脚本:

mysql -uroot -p create database hotel_recommend default charset utf8mb4;

字符集一定要用 utf8mb4,而不是 utf8。utf8mb4 是 utf8 的超集,能正确存储 Emoji 和生僻字,酒店名里偶尔出现的特殊符号只有它能扛住。建完库后找到 sql 目录下的 init.sql,用 source 命令导入:

mysql -uroot -p hotel_recommend < sql/init.sql

导入完成后回到后端目录,改连接配置。常见做法是把配置集中在一个 config.py 文件里:

DB_CONFIG = { "host": "127.0.0.1", "port": 3306, "user": "root", "password": "你的密码", "database": "hotel_recommend", "charset": "utf8mb4" }

密码是个人环境差异最大的点,原作者上传代码时多半会把密码脱敏成 123456 或者 root,你不改就跑不通。连接串里的 charset 轻易别省,否则会出现“数据库里显示正常,Python 读出来变成乱码”的问题。

3.4 启动后端与前端:两个终端,一个地址

后端和前端是两个独立进程,一个负责算法和接口,一个负责页面。先启动后端:

cd backend python app.py

看到日志输出Running on http://127.0.0.1:5000说明后端起来了。如果端口被占用,常见的处理是换一个端口启动,但要记得前端转发配置里的目标端口也要同步改。

再开一个终端启动前端:

cd frontend npm run serve

前端默认跑在 8080 端口,浏览器打开 http://127.0.0.1:8080 就能看到页面。此时前后端端口不一样,浏览器直接访问前端页面时,页面里的 ajax 请求会跨域,Vue 项目的开发环境一般会在 vue.config.js 里配置转发:

module.exports = { devServer: { proxy: { '/api': { target: 'http://127.0.0.1:5000', changeOrigin: true } } } };

这段配置的意思是:前端把以 /api 开头的请求转发到后端 5000 端口,浏览器认为请求是同源的,跨域问题就消失了。如果项目里没有这个文件,就手动建一个,改完配置必须重启 npm run serve 才会生效。

4. 数据建模与特征设计:行为权重、时间衰减和表结构的一次到位设计

4.1 四张核心表:用户、酒店、行为、推荐日志

跑通源码只是起点,真正决定推荐效果的是数据库里长什么样。很多毕业设计源码自带的 SQL 里只有几十条假数据,算法怎么调都像在猜拳。先看四张最核心的表怎么建:

CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE, city VARCHAR(50) COMMENT '常用城市', created_at DATETIME ); CREATE TABLE hotel ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100), city VARCHAR(50), price DECIMAL(10, 2), star INT, score DECIMAL(2, 1), tags VARCHAR(255) COMMENT '设施标签, 逗号分隔' ); CREATE TABLE behavior ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT, hotel_id INT, action TINYINT COMMENT '1浏览 2收藏 3下单', created_at DATETIME, INDEX idx_user (user_id), INDEX idx_hotel (hotel_id) ); CREATE TABLE recommend_log ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT, hotel_id INT, strategy VARCHAR(20) COMMENT 'itemcf/content/hybrid/hot', score FLOAT, created_at DATETIME );

行为表是整个推荐系统的燃料,三个 action 值分别代表浏览、收藏、下单,没有这张表,协同过滤就是无米之炊。推荐日志表最容易被人忽略,但它答辩时价值极大——你可以指着日志说“这个用户前一天浏览了 3 家经济型酒店,今天系统给他推了同价位的新店”。

4.2 行为数据转评分:权重、时间衰减和 TopN 怎么设

行为表里的原始记录不能直接当作评分喂给协同过滤。浏览、收藏、下单是三种强度完全不同的行为,常见的权重设置是浏览 0.3、收藏 0.8、下单 1.0。下单权重远大于浏览,因为用户真的掏钱了;收藏介于中间,说明用户感兴趣但还在比较。按用户和酒店分组求和后,就得到一张行为评分矩阵:

def build_rating_matrix(behavior_df): # 行为权重映射 weight_map = {1: 0.3, 2: 0.8, 3: 1.0} behavior_df["weight"] = behavior_df["action"].map(weight_map) # 同一用户对同一酒店的多次行为累加 rating = behavior_df.groupby(["user_id", "hotel_id"])["weight"].sum().unstack().fillna(0) return rating

权重是推荐系统里第一个需要整数值的参数。遇到过有人把浏览设成 1.0、下单也设成 1.0,结果刷页面比真实下单更影响推荐结果,这就是典型的没想清楚行为含义。求和而不是求平均也很重要——同一家酒店用户既浏览又收藏,说明兴趣在加强,用平均反而把强度抹平了。

时间衰减是另一个容易被忽视的参数。三个月前的收藏和昨天的收藏,对当前兴趣的指示意义完全不同。常见做法是按天做指数衰减:

import pandas as pd # 衰减系数 0.98, 时间越久权重越低 behavior_df["decay"] = 0.98 ** ((pd.Timestamp.now() - behavior_df["created_at"]).dt.days) behavior_df["weight"] *= behavior_df["decay"]

0.98 表示每过一天权重打 98 折,30 天后还剩约 55%,半年后几乎归零。这个值不是玄学,要结合业务看:酒店预订的决策周期很短,用户不会半年前收藏一家酒店到半年后才下单,所以衰减要快一些。如果换成电影推荐,衰减系数就可以放到 0.995 附近。

TopN 推荐个数一般设 10 到 20。推荐列表一屏展示大概 10 个卡片,N 太小覆盖不到长尾酒店,N 太大用户根本划不到底部。答辩时如果被问“为什么是 10”,可以从用户体验和数据覆盖率两个角度解释,而不要只说“照着别人写的”。

4.3 相似度计算的输入数据清洗

评分矩阵建好后还有一个隐蔽问题:矩阵里绝大多数是 0,0 在余弦相似度里会被当作“负向打分”参与计算,把两个只是都没看过某家酒店的用户算得很像。解决方式有三种:一是把 0 替换为 NaN,在计算相似度时忽略空值;二是对矩阵做物品均值中心化,减去每个酒店被打分的平均值;三是用 top-N 共现过滤,只统计两个酒店同时被同一个人打过分的次数。

其中第二种最常用:

# 只对非零位置做均值中心化 mask = rating > 0 rating_centered = rating - rating.mean(axis=0) rating_centered[~mask] = 0

中心化的含义是:把“每个酒店的平均分”作为基线,只看用户打分偏离基线的程度。比如一家酒店平均分是 4.5,用户给了 5,说明是真爱;另一家平均分是 2,用户给了 3,虽然分低但相对积极。不做中心化,高分酒店永远霸占相似度榜首,推荐结果会向高分店严重倾斜。

5. 避坑指南:五个让酒店推荐系统当场翻车的常见问题

5.1 数据造得假,推荐结果像在猜拳

现象:系统能跑,但推荐结果完全没有逻辑,比如一个只看过 200 元招待所的用户被推了 2000 元豪华酒店。 原因:初始化 SQL 里造数据时,所有用户的行为完全随机,没有遵循“用户喜欢什么价位、什么城市”的规律。 解决:造数据要带业务逻辑。先给每个用户设定一个偏好价位区间,再从他所在城市随机挑酒店生成行为,浏览、收藏、下单按比例分配。数据量不用大,20 个用户、100 家酒店、500 条行为就足够演示效果。

5.2 双重循环算相似度:用户一多直接卡死

现象:数据量到几百个酒店时,点击推荐要等 5 秒以上。 原因:很多源码的相似度计算用了嵌套 for 循环对每个酒店两两求余弦,复杂度是 O(n²),n 到 1000 后请求必然超时。 解决:用矩阵乘法一次性算完,就是上面 2.1 节那十几行代码。另外 ItemCF 的酒店相似度矩阵变化很慢,完全可以在项目启动时加载到内存,或者每周算一次存 Redis,接口只做读取,不要每次请求都现场算矩阵。

5.3 中文乱码、端口被占、跨域不通:联调三连坑

现象:页面能打开,但酒店名称全是“???”,或者接口请求报 404、503。 原因:三级错误对应三个不同位置。中文乱码看建库语句是不是 utf8mb4,以及 pymysql 连接串里有没有 charset;404 看前端转发配置有没有把 /api 指向后端端口;503 看后端进程是不是没起来,或者端口被其他服务占用。 解决:按顺序排查。命令行执行mysql -uroot -p进去看表里数据是否正常,确认数据没问题再看后端启动日志,最后看浏览器 Network 面板里请求的实际地址。前端转发配置改完要重启,这个忘了就白改。

5.4 冷启动不兜底,接口返回空列表

现象:用新注册的账号登录,首页 “猜你喜欢” 区域一片空白。 原因:新用户没有行为记录,协同过滤计算出空矩阵,接口返回了空列表,前端又没有做空态处理。 解决:三层兜底。行为少于 3 条的用户直接返回城市热门酒店;热门酒店也要为空时返回全站评分最高的酒店;后端无论如何不返回 500 错误,空列表也要给个 200 状态码,前端用“暂无推荐,先看看热门”引导用户去产生行为。

5.5 答辩被问“指标多少”答不上来

现象:演示很好,老师一句“你的推荐准确率是多少”直接卡壳。 原因:项目里完全没有评估模块,自己都没跑过任何量化指标。 解决:写一个三十行的离线评测脚本,把数据按时间切分成训练集和测试集,算 Precision@10 和 Recall@10。数值不需要好看,关键是能说出来“在 100 个测试用户上,推荐列表里有 12% 的酒店命中用户实际下单的酒店”,这句话比任何演示都有说服力。

6. 验证推荐效果:最小的离线评测代码和一个能写进论文的指标

6.1 用留一法切分用户行为,计算 Precision@K

留一法是最省事的评测方案:对每个用户,把最后一次行为当作测试集,之前的行为当作训练集,看系统在前 10 个推荐里有没有命中用户最后选的酒店。代码如下:

def evaluate(train_df, test_df, top_k=10): # 假设 recommend() 返回按得分排名的酒店 id 列表 hit = 0 for user_id, row in test_df.iterrows(): rec_list = recommend(user_id, train_df, top_k) if row["hotel_id"] in rec_list: hit += 1 precision = hit / len(test_df) recall = hit / len(test_df) # 每个用户只取一条测试记录, 二者相等 print(f"Precision@{top_k}: {precision:.4f}, 命中用户数: {hit}")

Precision@10 的含义是推荐列表里真正被用户选中的比例,答辩时重点解释这个指标就行,不需要展开讲公式。推荐效果和冷启动兜底策略强相关,如果测试用户里有很多行为极少的新用户,指标会被拉低,这是正常的,评分时老师更关心你是否理解为什么低。

6.2 一个能写进论文的进阶方向

如果代码和指标都有了,想再拉开差距,可以尝试把 ItemCF 的相似度矩阵从向量空间换成近邻图,或者用 LightFM 同时建模用户侧和物品侧特征。但对于酒店推荐系统,最实际的进阶是把推荐原因写进接口,比如“因为你收藏过同价位的 XX 酒店”,可解释性比再调高一个百分点的准确率更打动答辩老师。

我做这类选题吃过最大的亏就是前期只顾跑通代码,没想清楚“怎么证明它在工作”。后来养成一个习惯:任何推荐项目先建 recommend_log 表,每次推荐都记录策略和得分。这样无论出了什么问题,第一反应不是猜,而是先看日志,再做离线评测。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询