尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Python酒店推荐系统:Flask+SQLite实现可解释、可调试的全链路推荐
简介本资源是一套面向本科毕业设计与课程设计的Python全栈酒店推荐系统适用于计算机专业学生完成Web开发类实践项目解决酒店在线预订与后台管理一体化建模需求。压缩包共597个文件33.83MB涵盖63个JavaScript前端交互逻辑、118个Vue组件实现动态界面、56个Python后端Django视图与模型、2个SQL数据库初始化脚本及配套bat批处理如安装.bat、运行.bat、初始化hive数据库.bat等支撑环境一键部署与本地调试。已有63人学习下载适合需快速上手DjangoMySQLVue技术栈的学生可直接复用完整前后端源码、管理员与用户双角色功能模块、留言反馈交互流程以及含数据库设计、项目说明文档与PPT演示稿在内的全套毕业材料。1. 酒店推荐系统不是“猜你喜欢”而是用 Python 把用户画像、地理位置、价格敏感度和实时库存全链路串起来的真实业务闭环很多同学拿到“酒店推荐系统”这个毕设题目第一反应是套个协同过滤或 LightFM 模型喂点用户点击日志就跑出个 top-10 列表——结果答辩时被问“如果用户刚搜完‘带儿童游乐场的海景房’但系统推了家商务快捷酒店你这推荐逻辑怎么解释”当场卡壳。这不是算法不行是没把推荐系统当成一个可解释、可干预、可回溯的业务模块来设计。本项目标题里的“完整前后端源码LW”不是噱头它意味着前端能真实模拟用户筛选行为如滑动价格区间、勾选‘免费取消’、输入‘距地铁站≤500m’后端不只调 sklearn.fit()而是用 Flask 构建了带请求上下文的推荐路由数据库里存的不只是 hotel_id 和 score还有每条推荐结果的触发因子权重比如 35% 来自位置匹配28% 来自历史同类型订单19% 来自当前促销标签。适合两类人一是需要交出一份能现场演示、能讲清每步逻辑、能应对导师追问细节的毕设同学二是想从零理解“推荐系统如何落地成 Web 应用”的入门开发者——它不追求 SOTA 指标但每行代码都对应一个真实业务动作。2. 用 Flask SQLite 搭建最小可行推荐服务从数据建模到接口暴露的四步闭环2.1 数据库设计必须为推荐逻辑留出“解释入口”推荐系统最怕黑匣子。很多毕设项目用 pandas 直接读 CSV 做内存计算答辩时被问“这条推荐结果为什么排第3而不是第2”只能答“模型算的”。本方案强制在数据库层埋入可追溯字段。核心三张表结构如下SQLite 建表语句-- 酒店主表关键不是字段多而是字段可参与规则计算 CREATE TABLE hotels ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, lat REAL NOT NULL, -- 纬度用于 Haversine 距离计算 lng REAL NOT NULL, -- 经度 price_min REAL, -- 最低房价元/晚非字符串便于范围查询 price_max REAL, -- 最高房价 rating REAL DEFAULT 0, -- 用户评分0-5 review_count INTEGER DEFAULT 0, has_pool BOOLEAN DEFAULT 0, has_wifi BOOLEAN DEFAULT 1, cancel_policy TEXT CHECK(cancel_policy IN (free, 24h, 48h)), updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- 用户行为表不存原始日志而存聚合特征降低实时计算压力 CREATE TABLE user_profiles ( user_id INTEGER PRIMARY KEY, avg_price_preference REAL, -- 历史订单均价避免冷启动时用全局均值 preferred_districts TEXT, -- JSON数组[徐汇, 静安]用于区域加权 booking_frequency TEXT, -- high/medium/low影响新酒店曝光权重 last_active TIMESTAMP ); -- 推荐缓存表每次请求生成的推荐结果存这里带完整归因 CREATE TABLE recommendation_cache ( id INTEGER PRIMARY KEY, user_id INTEGER, hotel_id INTEGER, rank INTEGER, -- 最终排序位置 base_score REAL, -- 协同过滤原始分 location_boost REAL, -- 地理位置加成0~1 price_match_score REAL, -- 价格区间匹配度0~1 policy_boost REAL, -- 取消政策匹配加成0/0.3/0.6 generated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES user_profiles(user_id), FOREIGN KEY (hotel_id) REFERENCES hotels(id) );提示preferred_districts存 JSON 字符串而非关联表是因为毕设场景下用户量级小1000且需支持模糊匹配如用户常订“徐汇”则“长宁”“闵行”自动获得邻近区加权。生产环境应拆表此处为简化部署。2.2 Flask 后端用请求上下文驱动多策略融合推荐不是单模型打分而是根据请求参数动态组合策略。本方案定义三个基础策略函数再用recommend()主函数按权重融合# app.py from flask import Flask, request, jsonify import sqlite3 import json import math from datetime import datetime app Flask(__name__) def haversine_distance(lat1, lng1, lat2, lng2): 计算两点间球面距离米用于位置加权 R 6371000 # 地球半径米 phi1 math.radians(lat1) phi2 math.radians(lat2) delta_phi math.radians(lat2 - lat1) delta_lambda math.radians(lng2 - lng1) a math.sin(delta_phi/2)**2 math.cos(phi1)*math.cos(phi2)*math.sin(delta_lambda/2)**2 c 2 * math.atan2(math.sqrt(a), math.sqrt(1-a)) return R * c def get_user_profile(user_id): 获取用户画像含默认值兜底 conn sqlite3.connect(hotels.db) cursor conn.cursor() cursor.execute(SELECT * FROM user_profiles WHERE user_id ?, (user_id,)) row cursor.fetchone() if not row: return { avg_price_preference: 450.0, preferred_districts: [徐汇], booking_frequency: medium } return { avg_price_preference: row[1] or 450.0, preferred_districts: row[2] or [徐汇], booking_frequency: row[3] or medium } app.route(/recommend, methods[GET]) def recommend(): # 1. 解析请求参数真实业务中这些来自前端筛选器 user_id int(request.args.get(user_id, 1)) lat float(request.args.get(lat, 31.2)) # 用户当前位置纬度 lng float(request.args.get(lng, 121.4)) # 经度 max_price float(request.args.get(max_price, 800)) min_rating float(request.args.get(min_rating, 4.0)) has_pool request.args.get(has_pool, false).lower() true # 2. 获取用户画像 profile get_user_profile(user_id) # 3. 查询候选酒店先用 SQL 过滤硬性条件减少 Python 计算量 conn sqlite3.connect(hotels.db) cursor conn.cursor() query SELECT id, name, lat, lng, price_min, price_max, rating, has_pool, cancel_policy FROM hotels WHERE rating ? AND (price_max ? OR price_min ?) cursor.execute(query, (min_rating, max_price, max_price)) candidates cursor.fetchall() # 4. 多策略打分每项输出可解释的中间值 scored_hotels [] for hotel in candidates: hid, name, hlat, hlng, pmin, pmax, rating, pool, policy hotel # 策略1地理位置匹配度0~1 dist_m haversine_distance(lat, lng, hlat, hlng) location_boost max(0, 1 - dist_m / 5000) # 5km内线性衰减 # 策略2价格匹配度用户心理价位 vs 酒店价格带 user_pref profile[avg_price_preference] price_center (pmin pmax) / 2 price_diff abs(price_center - user_pref) price_match_score max(0, 1 - price_diff / 300) # 300元容忍差 # 策略3政策偏好用户高频选择 free 取消则加权 policy_boost 0.6 if policy free else 0.3 if policy 24h else 0 # 策略4区域偏好若酒店所在区在用户常订区列表中 districts json.loads(profile[preferred_districts]) # 此处需预置酒店所属行政区字段实际项目中从地址解析或GIS匹配 # 毕设简化假设酒店名含区名如徐汇万豪 → 匹配徐汇 district_boost 0.4 if any(d in name for d in districts) else 0 # 加权融合权重可配置此处固定 final_score ( 0.35 * location_boost 0.25 * price_match_score 0.20 * policy_boost 0.20 * district_boost ) scored_hotels.append({ hotel_id: hid, name: name, score: round(final_score, 3), location_boost: round(location_boost, 3), price_match_score: round(price_match_score, 3), policy_boost: policy_boost, district_boost: district_boost }) # 5. 排序并写入缓存表供后续审计 scored_hotels.sort(keylambda x: x[score], reverseTrue) top_10 scored_hotels[:10] # 写入 recommendation_cache 表省略插入代码实际需 execute many # ... INSERT INTO recommendation_cache ... return jsonify({ user_id: user_id, timestamp: datetime.now().isoformat(), results: top_10 })逻辑说明为什么用 SQLite 而非 MySQL毕设本地部署零依赖sqlite3是 Python 标准库无需额外安装服务。为什么价格匹配用(pminpmax)/2避免单看price_min导致低价房泛滥如青旅 80 元/晚 vs 商务酒店 500 元/晚用户历史均值 450后者更匹配。district_boost的实现是否简陋是但符合毕设定位——它用酒店名关键词匹配已足够支撑“区域偏好”功能演示生产环境应接入地址解析 API 或 GIS 空间索引。3. 前端交互不是静态页面用 Vue 3 Composition API 实现可调试的推荐筛选器3.1 筛选器组件让推荐逻辑“看得见、调得着”毕设答辩最加分的环节是导师说“我试试改个参数”你立刻在前端拖动滑块、勾选选项页面实时刷新推荐列表并在控制台打印出每条结果的各维度得分。本方案用 Vue 3 的ref和computed实现响应式联动!-- components/RecommendFilter.vue -- template div classfilter-panel h3推荐筛选条件/h3 !-- 价格区间滑块 -- div classslider-group label价格范围元/晚{{ priceRange[0] }} - {{ priceRange[1] }}/label input typerange min100 max2000 v-model.numberpriceRange[0] inputdebounceFetch / input typerange min100 max2000 v-model.numberpriceRange[1] inputdebounceFetch / /div !-- 评分筛选 -- div classrating-filter label最低评分/label select v-modelminRating changedebounceFetch option value3.03.0/option option value4.0 selected4.0/option option value4.54.5/option /select /div !-- 设施筛选 -- div classamenity-filter label设施要求/label labelinput typecheckbox v-modelfilters.has_pool changedebounceFetch/ 游泳池/label labelinput typecheckbox v-modelfilters.has_wifi changedebounceFetch/ 免费WiFi/label /div !-- 实时得分面板答辩神器 -- div classdebug-panel v-iflastResult h4最新推荐归因分析前3名/h4 div v-for(item, i) in lastResult.slice(0,3) :keyi classdebug-item strong{{ item.name }}/strong span总分: {{ item.score }}/span small位置: {{ item.location_boost }}, 价格: {{ item.price_match_score }}, 政策: {{ item.policy_boost }}/small /div /div /div /template script setup import { ref, computed, onMounted } from vue import { debounce } from /utils/debounce.js // 响应式筛选状态 const priceRange ref([200, 800]) const minRating ref(4.0) const filters ref({ has_pool: false, has_wifi: true }) // 请求参数计算所有筛选条件聚合成 URL 参数 const queryParams computed(() { return new URLSearchParams({ user_id: 1, lat: 31.2, lng: 121.4, max_price: priceRange.value[1].toString(), min_rating: minRating.value.toString(), has_pool: filters.value.has_pool.toString() }).toString() }) // 防抖请求避免拖动滑块时频繁调用 const debounceFetch debounce(() { fetchRecommendations() }, 300) // 模拟请求实际替换为 axios.get const lastResult ref(null) async function fetchRecommendations() { try { // const res await axios.get(/recommend?${queryParams.value}) // lastResult.value res.data.results // 毕设简化用 mock 数据演示归因 lastResult.value [ { hotel_id: 101, name: 徐汇万豪酒店, score: 0.872, location_boost: 0.92, price_match_score: 0.78, policy_boost: 0.6, district_boost: 0.4 }, { hotel_id: 102, name: 静安香格里拉, score: 0.815, location_boost: 0.85, price_match_score: 0.82, policy_boost: 0.6, district_boost: 0.4 } ] } catch (e) { console.error(推荐请求失败, e) } } onMounted(() { fetchRecommendations() }) /script参数说明debounce函数防抖 300ms避免滑块拖动时瞬间发 20 个请求queryParams用computed自动重组 URL确保任何筛选变更都实时生效debug-panel不是炫技而是把推荐系统的“可解释性”具象化——答辩时导师点开控制台就能看到location_boost0.92比说“我们用了地理加权”有力十倍。3.2 推荐结果卡片展示归因而非仅展示酒店前端卡片必须暴露推荐逻辑否则就是静态列表!-- components/HotelCard.vue -- template div classhotel-card h3{{ hotel.name }}/h3 div classscore-bar span综合匹配度{{ hotel.score }}/span div classbar div classfill :style{ width: ${hotel.score * 100}% }/div /div /div !-- 归因标签组 -- div classreason-tags span v-ifhotel.location_boost 0.8 classtag green 位置极佳{{ hotel.location_boost }}/span span v-ifhotel.price_match_score 0.75 classtag blue 价格高度匹配{{ hotel.price_match_score }}/span span v-ifhotel.policy_boost 0.6 classtag orange✅ 免费取消/span span v-ifhotel.district_boost 0 classtag purple️ 常订区域/span /div button clickviewDetails(hotel)查看详情/button /div /template注意标签文案直接显示数值如位置极佳0.92不是“位置匹配”这种模糊描述。这是毕设答辩的降维打击——当别人还在说“模型效果好”你已经把每个数字的来源和含义钉死在界面上。4. 推荐系统避坑指南5 个毕设高频翻车点与血泪解决方案4.1 现象本地运行正常部署到服务器后推荐结果全为空原因SQLite 数据库路径硬编码为相对路径如./data/hotels.dbFlask 在不同工作目录下启动时找不到文件。解决用os.path.dirname(__file__)动态获取当前文件所在目录import os DB_PATH os.path.join(os.path.dirname(__file__), data, hotels.db) # 然后在 connect 时用绝对路径 conn sqlite3.connect(DB_PATH)4.2 现象用户筛选“价格≤500”但结果里出现 680 元的酒店原因SQL 查询条件写成price_max ?但数据库中price_max字段为NULL表示无上限导致该行被过滤。解决修改查询条件允许price_max IS NULL的记录通过WHERE rating ? AND (price_max ? OR price_max IS NULL OR price_min ?)并在初始化数据时对无明确上限的酒店设price_max 9999业务上合理且避免 NULL 判断。4.3 现象Vue 前端跨域报错CORSFetch 请求被浏览器拦截原因开发时前端npm run dev启动在http://localhost:5173后端 Flask 在http://localhost:5000浏览器视为跨域。解决在 Flask 中启用flask-cors毕设轻量方案pip install flask-corsfrom flask_cors import CORS app Flask(__name__) CORS(app) # 允许所有源毕设够用 # 生产环境应指定 originCORS(app, origins[http://yourdomain.com])4.4 现象推荐结果排序不稳定两次请求同一参数返回不同顺序原因SQLite 的ORDER BY在分数相同时无稳定排序且未设置ROWID或id作为次级排序键。解决在最终排序 SQL 中添加确定性次级键# 查询后排序时 scored_hotels.sort(keylambda x: (-x[score], x[hotel_id])) # 或在 SQL 中ORDER BY score DESC, id ASC4.5 现象答辩演示时导师输入“朝阳区”但无结果查数据库发现酒店都在“北京市朝阳区”原因前端传参district朝阳区后端未做标准化处理直接用于字符串匹配。解决在get_user_profile或筛选逻辑中增加清洗def normalize_district(district): 统一区名格式去除市区省等冗余词 return district.replace(北京市, ).replace(上海市, ).replace(区, ).replace(省, ).strip() # 使用时 districts [normalize_district(d) for d in json.loads(profile[preferred_districts])] # 匹配时也对酒店名做同样清洗5. 毕设交付物检查清单从源码包到论文附录的 7 个硬核交付点5.1 源码包必须包含的 4 类文件缺一不可文件类型必含内容为什么重要数据库初始化脚本(init_db.py)包含CREATE TABLE语句 插入 20 条真实风格测试数据如上海外滩茂悦大酒店含经纬度、价格带、设施导师双击运行即生成可用数据库避免手动建表填数据的尴尬Flask 配置文件(config.py)定义DEBUGTrue开发、SECRET_KEYCSRF、DATABASE_PATH绝对路径体现工程规范非所有配置写死在app.pyVue 环境变量(.env.development)VUE_APP_API_BASE_URLhttp://localhost:5000前后端分离部署时只需改此文件无需改 Vue 源码中的 URLREADME.md分三节① 快速启动3 行命令② 接口文档/recommend?...参数说明③ 数据库 ER 图文字描述即可导师 30 秒内看懂项目结构不翻源码5.2 论文附录必须嵌入的 3 个技术截图推荐接口调试截图用浏览器直接访问http://localhost:5000/recommend?user_id1lat31.2lng121.4max_price800显示 JSON 响应体重点圈出location_boost、price_match_score等归因字段。前端筛选器操作截图显示滑块拖动后下方debug-panel中三条酒店的各维度得分实时变化证明逻辑联动。数据库查询验证截图用 DB Browser for SQLite 打开hotels.db执行SELECT * FROM recommendation_cache WHERE user_id1 ORDER BY rank LIMIT 5显示rank、location_boost、generated_at字段证明推荐结果持久化且可审计。5.3 答辩话术把技术选择转化为业务理解不要说“我用了 Flask 因为它简单”。要说“我选 Flask 而非 Django是因为毕设推荐系统的核心价值不在后台管理而在快速验证推荐逻辑——Flask 的路由函数能一行代码接收所有筛选参数让我把精力集中在location_boost和price_match_score的业务公式上而不是配置 Django 的视图类和序列化器。”不要说“我用了 SQLite 因为不用装数据库”。要说“SQLite 不是妥协而是精准匹配场景——毕设数据量百级零运维成本意味着导师随时可以双击init_db.py重置全部数据这对演示‘用户画像随订单更新’功能至关重要。换成 MySQL光是教导师配置my.cnf就要占用 5 分钟答辩时间。”我带过几届毕设最常看到的翻车是学生花两周调通一个复杂模型却在答辩时被问“你这推荐结果怎么验证好坏”哑口无言。所以我的习惯是从第一天写代码就在recommend()函数里埋print()输出各维度得分在README里写明‘查看终端日志可追踪每条推荐的归因’。这比任何 PPT 上的架构图都有力——因为导师看到的不是“系统应该怎样”而是“此刻它正在怎样工作”。希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

飞行力学Matlab仿真全流程:配平、线性化与模态分析实战

飞行力学Matlab仿真全流程:配平、线性化与模态分析实战

简介:面向北京理工大学2025春季学期飞行力学课程的研究型学习大作业,资源提供了一套可直接运行的完整项目方案,案例数据无需额外配置即可复现。内容覆盖导弹六自由度运动建模与关键参数验证、不同导引方法的导引弹道运动学分析等核心专题&…

📅 2026/10/20 23:49:39
SCI365深度解析:学术信息聚合工作台,而非论文生成器

SCI365深度解析:学术信息聚合工作台,而非论文生成器

上周组会上,A同学举着手机问我:“师兄,SCI365是不是那种输入一个标题,就能自动生成整篇论文的工具?”我第一反应是摇头,但细一想,这真不怪他。短视频里但凡提到科研效率工具,十个有八…

📅 2026/10/20 23:49:39
Deepagents自动化测试实战:从断言设计到稳定性体系建设

Deepagents自动化测试实战:从断言设计到稳定性体系建设

做过AI Agent相关开发的朋友应该都有同感:写一个能跑通的代理不难,难的是让它稳定地跑完一百遍。LLM天然带有随机性,同样的输入,今天输出这个结果,明天就可能换个路径,哪怕最终答案都对,中间调用…

📅 2026/10/20 23:44:38
MORE NEWS

更多资讯

📰

AI不止于编码:如何用AI贯穿研发全流程实现真正提效

1. 为什么只盯着AI Coding会看漏真正的变化1.1 一个被过度简化的叙事过去一年多,关于AI在软件研发中的讨论,几乎被压缩成了一个词:AI Coding。各种演示里,AI在编辑器里补全代码、生成函数、修复报错,看起来效率提升立竿…

📰

C++动态分析实战:ASan、perf与覆盖率工具如何定位内存与性能问题

做了这么多年 C 开发,我有一个越来越强烈的感受:代码能编译通过、单元测试全绿,离"真没问题"还差着十万八千里。上个月排查一个服务偶发崩溃,崩溃点在一个我自认为写得很严谨的模块里,反复看源码看不出毛病&…

📰

安卓开发没凉,但面试变了:岗位拆解与高频考点全解析

我经常被问到一个问题:安卓开发是不是已经凉了?每次听到这个,我都想反问对方是在什么语境下得出的结论。作为一个在这个行业里写了快八年安卓,从业务功能到性能优化、从项目负责人到面试官都做过的人,我可以负责任地说…

📰

结构化图表设计:从信息建模到视觉编码的工程方法论

1. 项目概述:这不是画图,是用结构化思维重建信息表达逻辑“diagram-design”这个词组乍看像一个工具功能菜单里的二级选项,但实际拆开来看,“diagram”不是简单指“示意图”,而是特指具备明确语义关系、可被机器识别或…

📰

深入解析镜像加载原理:分层、联合文件系统与内容寻址

1. 从一次镜像拉取失败说起:为什么需要搞懂镜像加载原理很多人第一次接触容器技术,卡住的地方不是写配置文件,也不是跑命令,而是某天执行拉取镜像时突然报错,或者发现本地磁盘被一堆看不懂的层占满了,删也不…

📰

构建MCP Server:让LLM实时搜索与抓取网页

LLM 最尴尬的时刻,就是你问它一件上周刚发生的事,它却一本正经地对你说“我的知识有截止日期”。模型再聪明,本质上也只是一本训练期定稿的“百科全书”,它永远不知道训练集之外的世界发生了什么。这几年大家一直在喊“给 AI 装上…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬