news 2026/6/9 22:47:54

基于Python的旅游景点推荐系统毕设:从数据建模到Flask部署的实战全流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于Python的旅游景点推荐系统毕设:从数据建模到Flask部署的实战全流程


基于Python的旅游景点推荐系统毕设:从数据建模到Flask部署的实战全流程


1. 毕设常见痛点:数据稀疏、冷启动、工程化缺失

做推荐系统毕设,最容易被导师三连问:

  • “数据这么稀疏,结果靠谱吗?”
  • “新用户来了,你推啥?”
  • “代码跑通就算完?能上线吗?”

多数同学直接调 Surprise 或 LightFM,AUC 好看,却忽视工程闭环:
– 离线脚本一把梭,路径写死;
– 模型 PKL 一扔,前端调不通;
– 评审现场 502,当场社死。

痛点总结:

  1. 数据稀疏:用户-景点评分矩阵密度 <1%,纯协同过滤易过拟合。
  2. 冷启动:新用户无行为,新景点无评分,直接空白。
  3. 工程化缺失:没有 REST 接口、没有缓存、没有异常兜底,演示时一并发抖。

2. 技术选型:为什么 UserCF + 内容标签,而非矩阵分解或深度模型

方案优点缺点毕设场景
矩阵分解 (SVD)精度高训练慢、黑盒、难解释导师看不懂
深度学习 (DSSM)能融合多模态数据量要大、GPU 贵服务器没有卡
UserCF + 内容简单、可解释、冷启动友好精度略逊够用、能讲清

结论:时间紧、资源少、要演示,UserCF 负责“老朋友喜欢啥我推啥”,内容标签负责“新景点/新用户兜底”,两者加权融合,5 天可撸完。


3. 核心实现拆解

3.1 数据预处理

原始爬来的 CSV 长这样:

user_id,spot_id,rating,tag u001,s010,5,"古镇,人文"

步骤:

  1. 去重:同一用户对同一景点多次评分,取均值。
  2. 归一化:Min-Max 缩到 [0,1],消除评分尺度差异。
  3. 构造“用户-景点”矩阵:pandas pivot_table,空值填 0,但保留 mask 供后续相似度计算忽略。

3.2 相似度计算

  • 余弦相似度:适合稀疏向量,计算快。
  • 皮尔逊修正:减去用户均值,抵消打分偏置。

代码片段(已 Clean Code):

def cosine_plus_pearson(mat: csr_matrix, u_vec: np.ndarray, u_mean: float): """计算修正后的余弦相似度""" u_vec_centered = u_vec - u_mean sim = mat.dot(u_vec_centered) / ( norm(mat, axis=1) * norm(u_vec_centered) + 1e-8 ) return sim

3.3 混合推荐权重

UserCF 得分 与 内容标签得分 线性加权:

final_score = α * usercf_score + (1-α) * content_score

α 按用户行为条数动态调整:
– 行为 ≥10,α=0.8;
– 行为 <10,α=0.3;
冷启动用户直接走内容推荐。


4. 完整可运行代码

项目结构:

travel-rec/ ├── app.py # Flask 入口 ├── rec/ │ ├── __init__.py │ ├── data.py # 数据加载 │ ├── model.py # 推荐逻辑 │ └── utils.py # 工具 └── data/ └── travel.csv

4.1 model.py(核心 50 行)

import pandas as pd import numpy as np from scipy.sparse import csr_matrix from sklearn.metrics.pairwise import cosine_similarity class HybridRec: def __init__(self, csv_path, top_k=20, alpha=0.7): self.top_k = top_k self.alpha = alpha self._load_data(csv_path) self._compute_similarity() def _load_data(self, path): df = pd.read_csv(path) self.user_mean = df.groupby('user_id')['rating'].mean().to_dict() self.mat = df.pivot_table(index='user_id', columns='spot_id', values='rating').fillna(0) self.spot_tags = df.groupby('spot_id')['tag'].first().to_dict() self.user_item = csr_matrix(self.mat.values) def _compute_similarity(self): self.sim_matrix = cosine_similarity(self.user_item) def recommend(self, user_id, n=5): if user_id not in self.mat.index: return self._cold_start(n) uid_idx = self.mat.index.get_loc(user_id) user_vec = self.mat.iloc[uid_idx].values u_mean = self.user_mean[user_id] sims = self.sim_matrix[uid_idx] # 找到最相似用户 top_users = np.argsort(sims)[-self.top_k:] # 加权平均得分 rec_scores = np.zeros(self.mat.shape[1]) for u in top_users: rec_scores += sims[u] * self.mat.iloc[u].values # 内容得分 content_scores = self._content_score(user_vec) # 融合 final = self.alpha * rec_scores + (1 - self.alpha) * content_scores # 排除已交互 seen = user_vec.nonzero()[0] final[seen] = -1 top_items = np.argsort(final)[-n:][::-1] return self.mat.columns[top_items].tolist() def _content_score(self, user_vec): """简单标签匹配:用户历史标签出现频次""" from collections import Counter visited = np.where(user_vec > 0)[0] tags = [] for idx in visited: spot = self.mat.columns[idx] tags.extend(self.spot_tags.get(spot, '').split(',')) user_tags = Counter(tags) content_vec = np.zeros(self.mat.shape[1]) for idx, spot in enumerate(self.mat.columns): spot_tag_list = self.spot_tags.get(spot, '').split(',') content_vec[idx] = sum(user_tags[t] for t in spot_tag_list if t in user_tags) return content_vec / (content_vec.max() + 1e-8) def _cold_start(self, n): """冷启动:返回热门景点""" return self.mat.sum().nlargest(n).index.tolist()

4.2 app.py(20 行)

from flask import Flask, request, jsonify from rec.model import HybridRec app = Flask(__name__) rec = HybridRec('data/travel.csv') @app.route('/rec', methods=['GET']) def recommend(): user_id = request.args.get('user_id') if not user_id: return jsonify({'error': 'missing user_id'}), 400 spots = rec.recommend(user_id) return jsonify({'spots': spots}) if __name__ == '__main__': app.run(debug=False, threaded=True)

启动:

export FLASK_APP=app.py flask run --host=0.0.0.0 --port=5000

5. 性能与安全

  1. 缓存:
    使用 Flask-Caching 把/rec结果缓存 5 min,避免重复计算。
  2. 请求幂等:
    GET 接口天然幂等,不加副作用。
  3. 隐私脱敏:
    返回包只给 spot_id,不把用户历史明文带出;日志打码 user_id 后四位。

6. 生产环境避坑指南

现象解决
本地路径硬编码换电脑就 FileNotFoundpathlib.Path(__file__).parent动态拼
NaN 未处理推荐结果空列表fillna(0)后统一转 int
Flask 开发服务器并发压测 20 线程直接卡死上 Gunicorn + Gevent:gunicorn -k gevent -w 4 app:app
矩阵太大内存炸10 万用户 8 G 爆只保留交互 ≥3 的用户,稀疏度降到 0.3%

7. 效果演示

本地起服务后,前端 Vue 调/rec?user_id=u001,200 ms 内返回 5 个景点,导师现场点赞。


8. 后续可玩:实时行为日志怎么接?

目前模型离线天级更新。想再卷一点,可以:

  1. 用 Nginx + Lua 把用户点击日志实时打到 Kafka;
  2. Flink 流处理 5 min 窗口,更新用户最近交互向量;
  3. 通过 Redis Pub/Sub 通知 Flask 热更新相似度缓存。

一句话:先把离线链路跑通,再逐步“流式化”,毕设就能从“能跑”进化到“像真的”。


把代码推到 GitHub,README 写清依赖版本,答辩时亮出 Swagger 文档和缓存命中率,老师基本不再追问。
动手跑一遍,你会发现:推荐系统其实也没那么玄学,工程闭环才是硬通货。


版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/5 21:30:15

从原理到实践:基于STM32的智能小车毕业设计技术全解析

从原理到实践&#xff1a;基于STM32的智能小车毕业设计技术全解析 一、背景痛点&#xff1a;毕设高频踩坑的三座大山 硬件兼容性 淘宝套件“爆款”泛滥&#xff0c;STM32F103C8T6 与 GY-521 共用 3.3 V 电源轨&#xff0c;结果 MPU6050 的 IC 上拉电阻与板载 USB-TTL 芯片冲突&…

作者头像 李华
网站建设 2026/6/8 18:39:48

协议演进史:从MultiWii到iNavFlight的MSP DJI协议兼容性挑战

协议演进史&#xff1a;从MultiWii到iNavFlight的MSP DJI协议兼容性挑战 无人机飞控系统的通信协议一直是开源社区与商业硬件整合的关键桥梁。当DJI的数字图传系统需要与开源飞控深度交互时&#xff0c;MSP&#xff08;MultiWii Serial Protocol&#xff09;协议的兼容性设计便…

作者头像 李华
网站建设 2026/6/5 21:21:43

基于YOLO的罐装饮料智能识别:从数据集构建到工业应用实战

1. 罐装饮料识别技术背景与YOLO优势 罐装饮料自动识别在智能零售和工业质检领域需求日益增长。传统人工盘点方式效率低下&#xff0c;误差率高&#xff0c;而基于深度学习的视觉识别技术能实现毫秒级响应。YOLO&#xff08;You Only Look Once&#xff09;作为单阶段目标检测算…

作者头像 李华
网站建设 2026/6/6 8:23:20

Android跨进程图片传输实战:当ParcelFileDescriptor遇上Glide

Android跨进程图片传输实战&#xff1a;ParcelFileDescriptor与Glide深度整合指南 在移动应用开发中&#xff0c;跨进程图片共享是多媒体处理场景下的常见需求。无论是社交应用的内容分享、电商平台的商品详情展示&#xff0c;还是企业应用的文档协作&#xff0c;高效安全的图…

作者头像 李华
网站建设 2026/6/6 8:19:39

从零构建:如何用开源协议栈在Linux上打造ESP32蓝牙适配器

从零构建&#xff1a;如何用开源协议栈在Linux上打造ESP32蓝牙适配器 1. 开源蓝牙协议栈与ESP32的完美结合 在嵌入式开发领域&#xff0c;将ESP32配置为Linux系统的蓝牙适配器正成为一种经济高效的解决方案。相比商用蓝牙适配器&#xff0c;这种方案不仅成本更低&#xff0c;…

作者头像 李华
网站建设 2026/6/6 7:17:40

解决 ‘cosyvoice no module named torchaudio‘ 的 AI 辅助开发实战指南

解决 cosyvoice no module named torchaudio 的 AI 辅助开发实战指南 摘要&#xff1a;在 AI 辅助开发过程中&#xff0c;cosyvoice no module named torchaudio 是开发者常遇到的依赖问题&#xff0c;尤其在跨平台或新环境部署时。本文将深入分析该错误的根源&#xff0c;提供…

作者头像 李华