基于Python的旅游景点推荐系统毕设:协同过滤与数据库实战

发布时间:2026/10/12 11:43:52
基于Python的旅游景点推荐系统毕设:协同过滤与数据库实战 简介面向毕业设计、期末大作业与课程设计的Python旅游景点推荐系统完整项目基于Python语言开发包含可运行的推荐系统实现、MySQL数据库脚本与配套论文适合需要完整参考实现或快速交付成果的高校学生与开发者。系统代码内附详细注释从景点数据管理、用户兴趣建模、相似度计算到个性化推荐结果展示均有清晰实现新手可对照论文理解整体项目流程与关键算法。ZIP压缩包共712个文件大小约41.2MB文件构成以Python脚本、Vue/JS/CSS等前端资源、SQL数据库文件、论文文档以及GIF/JPG演示截图为主并带有安装与启动批处理脚本便于本地一键部署。目前已有526人学习/下载项目为个人手打的高分作品导师认可度高下载后只需简单配置运行环境即可使用也可作为答辩讲解、代码理解、二次开发或课程设计拓展的基础。1. 基于Python的旅游景点推荐系统毕设怎么做才能拿高分旅游景点推荐系统说白了就是让用户打开系统时不再面对一张冷冰冰的景点列表而是看到“猜你喜欢”的几处地方。做这个毕业设计核心不是网站界面多漂亮而是推荐算法有没有跑通、数据表设计得合不合理、评估指标能不能证明你的系统确实“推荐得准”。很多同学把精力花在注册登录和页面样式上结果答辩时被老师问一句“你的推荐系统和其他人有什么区别”就答不上来。这套系统最常用的技术路线是Python 负责推荐算法逻辑部分MySQL或 SQLite存用户、景点、评分行为三类核心数据前端用 Flask 或 Django 简单渲染页面即可。数据量不需要大几百个景点、几十个用户模拟行为就足以演示完整链路。适合正在做毕设、需要快速搭建一个能演示能答辩的完整系统的同学。本文把数据库设计、算法实现、评估指标、答辩高频问题一次讲透。2. 推荐系统的技术选型先想清楚用哪种算法再动手写代码2.1 基于内容推荐与协同过滤两种路线怎么取舍旅游景点推荐领域的核心问题只有一个如何在用户没有明确告诉系统“我喜欢什么”的情况下猜出他可能想去哪里。主流的做法有两种。第一种是基于内容的推荐Content-Based。核心逻辑是提取景点特征如景点类型、所在城市、门票价格区间、游玩时长再提取用户的历史偏好画像用户看过的景点有什么共同特征然后计算用户画像与景点特征之间的相似度。这种方式的优点是冷门景点也能被推荐缺点是新用户没有历史行为时推荐质量很差。第二种是协同过滤Collaborative Filtering又分基于用户User-CF和基于物品Item-CF两条子路线。User-CF 的思路是“和你相似的人喜欢的地方你也大概率喜欢”先算用户之间的相似度再把这个相似用户去过而你没去过的景点推给你。Item-CF 的思路是“和你喜欢的景点相似的景点你也可能喜欢”。实际毕设里Item-CF 的效果通常比 User-CF 更容易解释也更稳定。旅游场景下用户的兴趣会漂移但景点之间的相似关系相对固定因此 Item-CF 是更省心、答辩也更好讲的选择。我做毕设时直接选了混合推荐策略冷启动阶段用户没有评分记录用基于内容的推荐兜底积累到评分数据后用 Item-CF 做主力推荐。这个策略本身就是一个加分项论文里可以单独开一节说“如何解决冷启动问题”答辩时能讲的东西立刻多了一层。2.2 景点特征怎么提取从数据表设计开始推荐质量的上限在数据设计阶段就决定了。如果景点表里只有景点名称和城市没有任何可计算的特征字段基于内容的推荐就没有“内容”可用。常见的景点特征表设计如下景点ID主键自增景点名称字符串直接展示用城市字符串做用户地域偏好匹配景点类型字符串如“自然风光”“历史文化”“主题乐园”“海滨度假”等这是最重要的特征之一门票价格浮点数用于价格区间匹配建议游玩时长字符串如“2小时”“半天”用于时间偏好匹配评分均值浮点数由用户评分表聚合而来简介长文本存一段描述性文字后续可以做关键词抽取这里的重点是景点类型和城市两个字段必须按统一规范录入比如“自然风光”不能有的地方写成“自然”“历史文化”不能有的地方写成“古迹”。数据不干净后面算相似度时会出现大量本应相似却算成不相关的景点这是这个系统最常踩的数据坑。用户表进简化处理用户ID主键、用户名、密码必须做哈希存储不能明文、注册时间。推荐系统本身需要的行为数据来源是评分表和收藏表。评分表设计为用户ID、景点ID、评分值1~5、评分时间收藏表设计为用户ID、景点ID、收藏时间。评分表直接参与算法计算收藏表用于扩充隐式反馈。2.3 数据量不够怎么办如何生成模拟评分数据毕设场景下不太可能有真实的大量用户评分数据但协同过滤算法没有数据就转不起来。常见做法是写一个数据生成脚本模拟固定数量用户对随机景点进行评分。我一般会生成 50 个用户、200 个景点、每用户评分 10~30 条记录总评分量控制在 2000 条左右。这个量级在本地跑 Item-CF 是毫秒级出结果的也足够画出有说服力的评估曲线。生成时要控制随机性不是纯随机否则算法效果展示不出来——要模拟“真实用户的偏好”比如对“自然风光”类景点评分偏高、对“历史文化”类评分偏低这样协同过滤才能找到相似用户和相似物品推荐结果才有得讲。SQL 建库建表的语句和生成模拟数据的 Python 脚本在第三章直接给出完整实现。3. 数据库设计到 Python 算法实现推荐系统主流程完整走一遍3.1 建库建表MySQL 核心 SQL 脚本可直接复用数据库选型上毕设推荐 MySQL 5.7 以上版本。如果你机器上装 MySQL 费劲退而求其次用 SQLite 也行但论文里建议写 MySQL答辩时老师更容易认可这个方案的工程性。下面给出核心建表脚本。CREATE DATABASE IF NOT EXISTS travel_rec DEFAULT CHARACTER SET utf8mb4; USE travel_rec; CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(64) NOT NULL UNIQUE, password_hash VARCHAR(128) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE scenic ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(128) NOT NULL, city VARCHAR(64), category VARCHAR(32), price DECIMAL(8,2), duration VARCHAR(32), avg_rating FLOAT DEFAULT 0, description TEXT ); CREATE TABLE rating ( user_id INT NOT NULL, scenic_id INT NOT NULL, score TINYINT NOT NULL CHECK (score BETWEEN 1 AND 5), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (user_id, scenic_id), FOREIGN KEY (user_id) REFERENCES user(id), FOREIGN KEY (scenic_id) REFERENCES scenic(id) );这段脚本有三个点需要说明。第一DEFAULT CHARACTER SET utf8mb4必须指定否则插入中文景点名称时会存储失败这是 MySQL 在中文场景下的高频问题。第二rating表把主键设为(user_id, scenic_id)联合主键这能防止同一个用户对同一景点重复评分——重复评分会导致协同过滤的用户相似度计算翻车。第三场景表里单独留了avg_rating字段用于后续基于内容的推荐排序这个字段不需要手动插入由算法计算后回填。网上能找到的参考项目大多只给一个scenic表和rating表没有category和duration字段导致基于内容的推荐根本无法实施。你把这个三表结构写进论文本身就是对基础设计的完整性提升。3.2 基于内容的推荐计算用户偏好画像与景点相似度基于内容的推荐实现分两步。第一步是根据用户的评分历史构建用户偏好向量第二步是把偏好向量与每个景点的特征向量做相似度计算取 Top-N 返回。特征向量怎么构建把category字段做 one-hot 编码是一个简单有效的方案。比如只有“自然风光”“历史文化”“主题乐园”三种类型景点“西湖”的特征向量就是[1, 0, 0]假设用户对自然风光类景点的平均评分为 4.5对历史文化类平均评分为 3.0那用户偏好向量就是[4.5, 3.0, 0]。接下来用余弦相似度计算用户偏好向量与候选景点特征向量之间的距离。from math import sqrt def cosine_similarity(vec1, vec2): 计算两个向量的余弦相似度值越接近1表示越相似 dot sum(a * b for a, b in zip(vec1, vec2)) norm1 sqrt(sum(a * a for a in vec1)) norm2 sqrt(sum(b * b for b in vec2)) if norm1 0 or norm2 0: return 0 return dot / (norm1 * norm2) def build_user_profile(user_ratings, category_list): user_ratings: [{category: 自然风光, score: 5}, ...] category_list: 所有景点类型的列表用于保证向量维度一致 profile [0.0] * len(category_list) count [0] * len(category_list) for r in user_ratings: idx category_list.index(r[category]) profile[idx] r[score] count[idx] 1 # 取平均分没有评过的类型保持在0 for i in range(len(profile)): if count[i] 0: profile[i] / count[i] return profile这段代码的作用是构建用户兴趣画像向量。核心逻辑是把用户所有评分记录按景点类型分组累加分数后除以对应类型的评分次数得到的就是该类型下的平均兴趣强度。count数组防止某类型没有评分时出现除以零的错误。真实项目里这里还可以加入城市和价格区间的偏好原理一样扩展维度即可。很多现成代码直接用 sklearn 的cosine_similarity但手写这个函数不算浪费时间——答辩时老师很可能会问“余弦相似度的公式是什么”你直接在黑板上推导一遍比说“调了 sklearn 库”要有说服力得多。3.3 基于物品的协同过滤计算物品相似度矩阵并生成推荐Item-CF 的实现也分两步。第一步计算景点之间的相似度这里有个关键技巧不能直接拿景点的属性特征向量算相似度而是要看哪些用户同时给两个景点评过分用评分向量之间的相关性定义相似度。import pandas as pd import numpy as np def build_item_similarity(rating_df): rating_df: DataFrame至少三列 user_id, scenic_id, score 返回字典key为(景点A_id, 景点B_id)value为相似度 # 构造用户-物品评分矩阵行用户列景点缺失值为0 rating_matrix rating_df.pivot_table( indexuser_id, columnsscenic_id, valuesscore ).fillna(0) # 使用皮尔逊相关系数计算景点之间的相似度 item_sim_matrix rating_matrix.corr(methodpearson) item_sim_dict {} for i in item_sim_matrix.columns: for j in item_sim_matrix.columns: if i j: sim item_sim_matrix.loc[i, j] if np.isnan(sim): sim 0 item_sim_dict[(i, j)] sim item_sim_dict[(j, i)] sim return item_sim_dict这里用pivot_table把长表转成宽表是关键一步。缺失评分用 0 填充再用corr(methodpearson)直接得到景点间相关系数矩阵。相关系数比余弦相似度更合适的原因在于某些用户习惯打高分某些用户习惯打低分皮尔逊相关系数先给每列做了中心化处理抵消了用户评分尺度偏差计算出来更贴近“真实关联”。生成推荐列表时对用户已评分的景点找到它们各自的相似景点集合按相似度加权汇总后过滤掉已评分的景点取分数 Top-N 返回。def recommend_for_user(user_id, rating_df, item_sim_dict, top_n10): 根据用户历史评分和物品相似度矩阵生成推荐列表 user_rated set(rating_df[rating_df[user_id] user_id][scenic_id]) if len(user_rated) 0: return [] # 冷启动用户交给基于内容推荐处理 score_dict {} for scenic_id in user_rated: actual_score rating_df[ (rating_df[user_id] user_id) (rating_df[scenic_id] scenic_id) ][score].iloc[0] for (sim_item), sim_val in item_sim_dict.items(): if sim_item[0] scenic_id and sim_item[1] not in user_rated: score_dict.setdefault(sim_item[1], 0) score_dict[sim_item[1]] sim_val * actual_score ranked sorted(score_dict.items(), keylambda x: x[1], reverseTrue) return [item_id for item_id, _ in ranked[:top_n]]推荐分数的计算方式是用户对景点 A 打了 5 分景点 A 与景点 B 的相似度为 0.6则景点 B 的候选得分累加5 * 0.6 3.0。这样做的解释性最好——系统内部可以明确告诉用户“因为你对杭州西湖打了高分而灵隐寺与西湖有较高的相似度所以推荐给你”。top_n参数一般取 5 或 10。演示时取 5 比较适合前端展示评估时取 10 更合适后端可以把top_n做成可配置项论文里提一句“系统支持 Top-N 结果动态配置”即可。3.4 冷启动兜底策略新用户没有评分时推荐什么新用户注册后没有任何评分行为协同过滤直接返回空列表。这时候系统不能没有内容可展示通常用两个方案兜底一是按avg_rating倒序返回全局热门景点二是按当前用户注册时选择的兴趣标签如果有匹配景点类型。毕设里使用方案一最简单、演示效果最好。def cold_start_recommend(scenic_df, top_n10): 冷启动推荐直接取评分均值最高的景点 sorted_scenic scenic_df.sort_values(byavg_rating, ascendingFalse) return sorted_scenic[id].head(top_n).tolist()注意这里不要用“热门度”而用“评分均值”排序前者需要额外的浏览行为日志表支持后者只需要聚合rating表即可。热点词里有人提到的“数据库增删改查”其实在这个系统里最大的体现就在这里——avg_rating的更新是一个标准的 UPDATE 语句逻辑在写入新评分后触发刷新即可。这个兜底方案建议单独在论文里给一节标题叫“冷启动问题的缓解策略”把问题和解决方案的对应关系讲清楚属于老师认可的高频加分点。4. 推荐效果怎么量化评估精确率、召回率与 F1 的计算4.1 离线评估为什么比在线评估更适合毕设推荐系统做出来之后不能只说“感觉推荐得还行”论文里需要科学的评估数据。产业界的在线评估A/B 测试在毕设环境里不现实所以标准做法是离线评估把已有的评分数据切分为训练集和测试集用训练集训练推荐逻辑再在测试集上计算指标。切分方式有两种时间切分和随机切分。时间切分更贴近真实场景——用用户前 80% 时间的评分做训练、后 20% 的评分做验证模拟“用过去预测未来”。但如果评分数据生成时没有精心控制时间戳随机按行切分即可实现简单且不容易出错。4.2 评估指标的计算代码精确率和召回率一次算清评测协议定义为把测试集中每个用户的实际评分景点视为“真正感兴趣的景点”用推荐算法生成 Top-10 推荐列表然后计算命中情况。def evaluate_model(rating_df, item_sim_dict, test_users, top_n10): rating_df: 完整评分表 test_users: 测试用户ID列表 返回精确率、召回率、F1 precision_list [] recall_list [] for user_id in test_users: user_test_items set( rating_df[rating_df[user_id] user_id][scenic_id] ) if len(user_test_items) 0: continue recommended recommend_for_user( user_id, rating_df, item_sim_dict, top_ntop_n ) hit len(set(recommended) user_test_items) p hit / top_n r hit / len(user_test_items) precision_list.append(p) recall_list.append(r) avg_precision sum(precision_list) / len(precision_list) avg_recall sum(recall_list) / len(recall_list) f1 2 * avg_precision * avg_recall / (avg_precision avg_recall 1e-9) return avg_precision, avg_recall, f1这里有个参数容易踩坑recommend_for_user函数内部会再次查询rating_df的全部评分记录而不是仅训练集这意味着评估时发生了数据泄漏——训练和测试混合在同一个数据集里算出来的指标虚高。正确写法是在评估前把rating_df拆成train_df和test_df调用recommend_for_user时传入train_df。这是我见过的毕设代码里最隐蔽的问题你自己写时一定要分开。在数据量约 2000 条评分时Item-CF 的精确率一般落在 15%~30% 区间召回率在 10%~20% 区间这是正常水平。如果精确率超过 50%大概率是你把测试数据混入训练阶段了。4.3 覆盖率与多样性论文里可以追加的两个维度如果论文篇幅需要扩展可以再加两个维度指标。覆盖率表示推荐系统推荐的景点占全部景点的比例覆盖率低说明推荐结果过度集中在少数热门景点冷门景点永远没有曝光机会。多样性表示单个用户推荐列表中景点的类型分布是否丰富可以按category的熵值衡量。很多现成毕设只写精确率和召回率原因就是这两个指标计算最简单。加一个覆盖率会在论文工作量章节增加明显亮点。覆盖率直接用len(推荐集合) / len(全部景点)即可十几行代码就能完成性价比极高。5. 避坑指南毕设中推荐系统最容易翻车的四个环节5.1 中文乱码建库没指定字符集插入数据时直接报错现象向scenic表插入中文景点名称时报错Incorrect string value: \xE6\x9D\xAD... for column name。原因MySQL 数据库或表的默认字符集是latin1不支持中文字符。这是 MySQL 老版本默认配置导致的概率极高几乎所有第一次在 Ubuntu 上装 MySQL 做毕设的同学都会碰到。解决建库时加上DEFAULT CHARACTER SET utf8mb4。如果你已经建好了库执行ALTER DATABASE travel_rec CHARACTER SET utf8mb4;并重建表或者直接删库重建最快。删除前确认评分数据可以通过脚本重新生成别把手工录的数据白白丢掉。5.2 虚拟环境依赖冲突Flask 装好了导入时却报错现象按照教程pip install flask成功后运行python app.py提示ModuleNotFoundError: No module named flask。原因命令pip和python可能来自不同的 Python 环境。常见于本机装有多个 Python 版本或者在系统 Python 里安装包、在虚拟环境的 Python 里运行代码。解决毕设项目建立独立虚拟环境是必须养成的习惯。python -m venv venv创建虚拟环境后source venv/bin/activate激活再执行pip install flask Flask-SQLAlchemy pandas numpy。所有项目依赖固定写进requirements.txt换电脑部署时一条命令恢复环境。论文的“系统开发环境”章节里写清楚这一步可以避免答辩现场环境演示翻车。5.3 用户相似度全为 0评分矩阵太稠密导致的信息悖论现象基于用户的协同过滤推荐结果为空排查发现用户之间相似度全部为 0。原因pivot_table后评分矩阵的缺失值用 0 填充如果数据生成时每个用户都恰好对所有景点评过分矩阵稠密那么中心化后皮尔逊相关系数分母可能出现为零的情况导致结果全 NaN。更常见的原因是用户数量太少且都有某一项完全相同的评分模式。解决把数据生成脚本改为每个用户只随机评分 20% 的景点制造出合理的稀疏矩阵。真实的推荐系统评分矩阵远远比这个稀疏稀疏度太低反而不真实。这个经验放在你生成模拟数据的代码注释里将来答辩展示时也能顺手讲一句。5.4 评估时数据泄漏精确率高得不真实老师一问就露馅现象评估代码输出的精确率奇高达到 70%但推荐结果看起来质量并不好。原因验证集数据被直接用于计算推荐结果。典型写法是在recommend_for_user函数内查询所有评分——包括属于测试集的评分。这等于考试时把答案出在卷子上评估指标完全失真。解决严格把rating_df.split()为train_df与test_df推荐只用train_df评估只用test_df。代码里可以把evaluate_model的参数改为只接收训练集和测试集两个 DataFrame从函数签名层面杜绝类别泄漏。这个问题是论文评审老师的重点关注方向必须提前自查。6. 论文写作与答辩推荐系统的三个加分技巧和一段真实体会论文结构上推荐系统方向有一套约定俗成的写法可以在短期内借鉴摘要部分写清楚“针对传统景点推荐中存在的冷启动和数据稀疏问题设计并实现了一个基于混合推荐策略的旅游景点推荐系统”。背景章节不要大篇幅复制百度百科里“智慧旅游”的套话而是直接写当前旅游信息过载、用户决策成本高的痛点。系统设计章节把架构图画好把第三章的表结构和推荐流程完整贴进去。测试章节放第五章的评估数据并解释指标含义。这三个部分里流程图的规范度是很多人忽略的得分点。用 Visio 或 draw.io 画推荐系统整体架构图时从数据采集层到数据存储层再到推荐计算层最后到前端展示层层次分明即可不用过度追求图画复杂度。答辩 PPT 里一定要有一张“系统处理一次推荐请求的时序流程”图这比放十页数据库建表截图更能展示你对系统的理解。论文的创新点不需要真发明新算法“混合推荐策略融合基于内容与协同过滤各自的优势”是经得起检验的本科生毕设工作描述重点在于你能否用数据证明混合策略比单一策略在某些指标上更好。如果时间有余把精确率对比图做成折线图横轴是“用户评分数量”纵轴是精确率从数据上说明“协同过滤在数据丰富时优势明显”即可。最后分享一个做毕设最实用的统筹方法给自己定时间底线。我的习惯是第一周搞定数据库建表和模拟数据生成脚本第二周写基于内容的推荐和协同过滤模块第三周用 Flask 套一个最简页面所有景点列表页和推荐结果页就够了第四周留全给论文写作和指标计算。界面后期没有时间美化没有关系答辩前截图时换一套干净的前端模板视觉效果完全够了。核心永远在推荐算法是否能跑通、评估数据是否合理而不是页面的动画效果。如果推荐结果或评估数据不符合预期不要急着改算法。先检查数据是否稀疏、评分数据是否被人为污染、测试集和训练集是否分离绝大多数“推荐不准”都是数据问题而不是算法问题。保持一个能随时重新生成模拟数据的脚本就是你做完这个课题最大的后悔药。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询