简介:这是一套面向高校计算机专业本科生的Python毕业设计实战项目,聚焦智能旅游推荐场景,适用于Python课程设计、期末大作业及毕设开题参考。系统基于Django框架开发,采用HTML+Vue+CSS+JS构建前后端分离界面,集成用户管理、景点推荐、评分反馈、数据库交互等核心功能,具备完整业务闭环与良好用户体验。资源包共797个文件,涵盖45个Python后端逻辑文件、53个Vue组件、53个HTML页面、164个JS脚本、162个SVG图标及配套SQL建库脚本、Bat运行/安装脚本等,整体压缩包20.19MB,结构清晰、模块分明,便于学习源码组织与工程化部署。已有227人下载学习,提供经调试可直接运行的完整环境(PyCharm+Navicat),附带数据库初始化方案与常见启动问题说明,助力快速上手与二次开发。
1. 为什么你的旅游推荐系统总被导师打回?——Python毕业设计里最常被忽略的「数据闭环」陷阱
你手里的.zip包写着“智能旅游推荐系统”,但打开后只有Flask路由、几个HTML模板、一个空荡荡的recommend.py和三张表结构截图。这不是代码缺陷,是设计断层:推荐系统不是“把景点按热度排序”,而是“用用户行为反推兴趣权重,再让新行为验证权重”。我带过27届毕业设计,83%的学生卡在“推荐结果永远固定”——不是算法写错了,是数据库没活起来:用户点击没入库、评分没触发重训练、冷启动时连默认标签都填不全。这个项目真正要交付的,不是能跑通的网页,而是一个可验证、可迭代、带日志回溯的最小数据闭环。它适合两类人:一是需要交差但不想答辩翻车的本科生,二是想用真实场景练手推荐系统工程链路的转行者。别急着改算法,先让MySQL里每条user_behavior记录都能对应到一次recommender.update()调用——这才是源码里最值钱的部分。
2. 从.zip解压到可运行:三步还原真实开发环境(含数据库初始化脚本)
2.1 解压后必须立刻执行的校验清单
拿到基于python的智能旅游推荐系统.zip,别直接跑python app.py。先做这四件事:
- 检查
requirements.txt是否含pymysql或mysql-connector-python:很多学生用SQLite开发,但答辩要求MySQL部署,缺驱动会报ModuleNotFoundError: No module named 'MySQLdb'; - 确认
config.py里DATABASE_URI格式:常见错误是写成mysql://root:123456@localhost:3306/travel_db(缺+pymysql),正确应为mysql+pymysql://root:123456@localhost:3306/travel_db; - 核对
data/目录下是否有scenic_spots.csv和user_ratings.csv:这两个文件是冷启动数据源,缺失会导致INSERT INTO scenic_spots报错; - 运行
python db_init.py前先手动建库:MySQL命令行执行CREATE DATABASE travel_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;,否则脚本会因权限拒绝失败。
提示:
db_init.py不是万能钥匙。它只建表、插基础数据,但不会创建用户。务必用CREATE USER 'travel_app'@'localhost' IDENTIFIED BY 'your_password'; GRANT ALL ON travel_db.* TO 'travel_app'@'localhost';配好应用账号。
2.2 数据库初始化脚本的隐藏逻辑
db_init.py的核心不是SQL语句,而是数据注入顺序。看这段关键代码:
# db_init.py 片段 def init_database(): # 1. 先建景点表(主表) cursor.execute(""" CREATE TABLE scenic_spots ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, category ENUM('自然', '人文', '美食', '购物') DEFAULT '自然', avg_rating FLOAT DEFAULT 0.0 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; """) # 2. 再建用户表(关联表) cursor.execute(""" CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL, last_login DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; """) # 3. 最后建行为表(依赖前两张表) cursor.execute(""" CREATE TABLE user_behaviors ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, spot_id INT NOT NULL, behavior_type ENUM('click', 'collect', 'rate') NOT NULL, rating_score TINYINT DEFAULT NULL, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (spot_id) REFERENCES scenic_spots(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; """)为什么顺序不能颠倒?
user_behaviors表的外键user_id和spot_id必须指向已存在的表;- 如果先建
user_behaviors,MySQL会报ERROR 1005 (HY000): Can't create table 'travel_db.user_behaviors' (errno: 150); ON DELETE CASCADE是关键:当用户注销时,自动清理其所有行为记录,避免脏数据堆积。
2.3 Flask服务启动的最小可行配置
app.py里常被删掉的create_app()函数才是核心:
# app.py 片段 def create_app(): app = Flask(__name__) app.config.from_object(Config) # 加载config.py中的DATABASE_URI # 初始化数据库连接池(不是单次连接!) db = SQLAlchemy(app) migrate = Migrate(app, db) # 支持后续表结构变更 # 注册蓝图(分离路由逻辑) from routes.recommend import recommend_bp app.register_blueprint(recommend_bp, url_prefix='/api') return app if __name__ == '__main__': app = create_app() app.run(debug=True, host='0.0.0.0', port=5000) # 注意host=0.0.0.0用于局域网访问参数说明:
debug=True仅限开发环境,答辩演示时必须设为False,否则暴露代码路径;host='0.0.0.0'让同寝室同学用手机访问http://192.168.x.x:5000测试,比localhost更贴近真实部署;SQLAlchemy初始化时会自动检测DATABASE_URI,若连接失败会在控制台打印sqlalchemy.exc.OperationalError,这是排查数据库问题的第一线索。
3. 推荐算法落地:不用复杂模型,用协同过滤+规则引擎撑起毕业设计
3.1 为什么毕业设计不该硬上深度学习?
看到“智能推荐”就搜tensorflow recommend?大错特错。毕业设计评审关注的是逻辑可解释性和工程完整性,不是AUC值。我让学生做过对比实验:
- 基于用户的协同过滤(UserCF):用
scikit-surprise库,50行代码实现,推荐理由可追溯到“和你相似的3个用户也收藏了XX”; - 神经协同过滤(NCF):需GPU、数据量>10万条、训练时间>2小时,答辩时导师问“为什么选ReLU不选Sigmoid”,当场哑火。
结论:用UserCF+规则兜底,既满足“智能”要求,又留出调试空间。
3.2 UserCF算法的Python实现与数据库联动
recommend.py里最关键的函数不是get_recommendations(),而是update_user_similarity():
# recommend.py 片段 from sklearn.metrics.pairwise import cosine_similarity import numpy as np def update_user_similarity(): """ 每24小时调用一次,更新用户相似度矩阵 数据来源:user_behaviors表中近30天的rate行为 """ # 1. 从数据库拉取评分矩阵(用户×景点) query = """ SELECT u.id as user_id, s.id as spot_id, ub.rating_score FROM users u JOIN user_behaviors ub ON u.id = ub.user_id JOIN scenic_spots s ON ub.spot_id = s.id WHERE ub.behavior_type = 'rate' AND ub.timestamp > DATE_SUB(NOW(), INTERVAL 30 DAY) ORDER BY u.id, s.id """ df = pd.read_sql(query, db_engine) # db_engine来自config.py # 2. 构造稀疏评分矩阵(避免内存爆炸) user_ids = df['user_id'].unique() spot_ids = df['spot_id'].unique() user_to_idx = {uid: i for i, uid in enumerate(user_ids)} spot_to_idx = {sid: i for i, sid in enumerate(spot_ids)} rating_matrix = np.zeros((len(user_ids), len(spot_ids))) for _, row in df.iterrows(): i = user_to_idx[row['user_id']] j = spot_to_idx[row['spot_id']] rating_matrix[i, j] = row['rating_score'] # 3. 计算余弦相似度(只算有交集的用户对) similarity_matrix = cosine_similarity(rating_matrix) # 4. 存入数据库供API实时查询 conn = db_engine.connect() for i, uid in enumerate(user_ids): similar_users = sorted( [(user_ids[j], similarity_matrix[i][j]) for j in range(len(user_ids)) if i != j], key=lambda x: x[1], reverse=True )[:10] # 取最相似的10个用户 # 插入similarity_cache表(提前建好) conn.execute( "INSERT INTO similarity_cache (user_id, similar_user_id, similarity_score) VALUES (%s, %s, %s)", [(uid, suid, score) for suid, score in similar_users] ) conn.close()逻辑说明:
- 不直接计算全量相似度,而是用
DATE_SUB(NOW(), INTERVAL 30 DAY)限定数据范围,防止冷数据污染; cosine_similarity输入是二维数组,输出是方阵,但只存Top10相似用户,节省90%存储空间;similarity_cache表结构需包含user_id、similar_user_id、similarity_score三字段,索引建在user_id上。
3.3 规则引擎兜底:解决冷启动和长尾景点曝光
UserCF在新用户或小众景点上必然失效,这时用规则引擎补位:
# recommend.py 片段 def get_fallback_recommendations(user_id, limit=5): """ 当UserCF无结果时,返回规则推荐 优先级:1. 用户历史行为类别 → 2. 当地热门景点 → 3. 新上线景点 """ # 1. 查用户最近3次行为的景点类别 recent_cats = db.session.execute(""" SELECT DISTINCT s.category FROM user_behaviors ub JOIN scenic_spots s ON ub.spot_id = s.id WHERE ub.user_id = :user_id AND ub.behavior_type IN ('click', 'collect', 'rate') ORDER BY ub.timestamp DESC LIMIT 3 """, {'user_id': user_id}).fetchall() if recent_cats: cat_filter = " OR ".join([f"category='{cat[0]}'" for cat in recent_cats]) fallback_sql = f"SELECT * FROM scenic_spots WHERE ({cat_filter}) ORDER BY avg_rating DESC LIMIT {limit}" else: # 2. 无历史行为时,取本地热门(按点击量) fallback_sql = f""" SELECT s.* FROM scenic_spots s JOIN ( SELECT spot_id, COUNT(*) as click_cnt FROM user_behaviors WHERE behavior_type='click' GROUP BY spot_id ORDER BY click_cnt DESC LIMIT 10 ) t ON s.id = t.spot_id LIMIT {limit} """ return db.session.execute(fallback_sql).fetchall()参数说明:
limit=5是硬性要求:答辩演示时推荐列表必须固定长度,避免UI错位;avg_rating字段在scenic_spots表中,由user_behaviors中rate行为的平均分实时更新;click_cnt统计用子查询而非JOIN,避免笛卡尔积导致慢查询。
4. 避坑指南:毕业设计答辩前必须修复的5个致命问题
4.1 现象:推荐结果每次刷新都一样
原因:update_user_similarity()未定时执行,similarity_cache表数据永久不变。
解决:在app.py中添加APScheduler定时任务:
from apscheduler.schedulers.background import BackgroundScheduler scheduler = BackgroundScheduler() scheduler.add_job(func=update_user_similarity, trigger="interval", hours=24) scheduler.start() # 注意:Flask的debug模式下APScheduler会启两次,需加锁4.2 现象:用户登录后推荐列表为空
原因:user_behaviors表中user_id和users表的id不匹配,常见于用Excel导入用户时ID自增被跳过。
解决:检查users表主键是否为AUTO_INCREMENT,执行ALTER TABLE users MODIFY id INT AUTO_INCREMENT;重置自增序列。
4.3 现象:中文景点名显示为乱码(如“西湖”变“西湖”)
原因:MySQL连接未指定字符集,pymysql默认用latin1。
解决:在config.py的DATABASE_URI末尾加?charset=utf8mb4,并确保my.cnf中[client]和[mysqld]节均配置default-character-set=utf8mb4。
4.4 现象:点击“收藏”按钮无反应,控制台报400 Bad Request
原因:前端AJAX请求未传X-CSRFToken,Flask-WTF启用CSRF保护后拒绝无令牌请求。
解决:在HTML模板中加入<meta name="csrf-token" content="{{ csrf_token() }}">,JS中读取:
fetch('/api/collect', { method: 'POST', headers: { 'X-CSRFToken': document.querySelector('meta[name="csrf-token"]').getAttribute('content') } })4.5 现象:答辩现场演示时MySQL连接超时
原因:云服务器安全组未开放3306端口,或本地MySQL绑定127.0.0.1导致远程无法访问。
解决:
- 修改
/etc/mysql/mysql.conf.d/mysqld.cnf,将bind-address = 127.0.0.1改为bind-address = 0.0.0.0; - 执行
GRANT ALL ON travel_db.* TO 'travel_app'@'%' IDENTIFIED BY 'your_password'; FLUSH PRIVILEGES;; - 防火墙放行:
sudo ufw allow 3306。
5. 让推荐系统“活”起来:用行为日志构建可验证的推荐效果评估体系
5.1 不靠准确率,用三个业务指标定义“推荐有效”
毕业设计答辩不考算法理论,考业务价值可验证性。我让学生在admin/dashboard.html里加这三个指标面板:
| 指标名称 | 计算逻辑 | 达标线 | 验证方式 |
|---|---|---|---|
| 推荐点击率(CTR) | COUNT(click行为 WHERE 来源=推荐列表) / COUNT(推荐展示次数) | ≥15% | 在user_behaviors表中加source字段(值为'recommend'或'search') |
| 收藏转化率 | COUNT(collect行为 WHERE 来源=推荐列表) / COUNT(click行为 WHERE 来源=推荐列表) | ≥8% | 后端API返回推荐时,记录recommend_id到行为表 |
| 长尾景点曝光占比 | COUNT(推荐列表中category='购物'的景点) / 总推荐数 | ≥20% | scenic_spots表中category字段需覆盖全部类型 |
为什么选这三个?
- CTR证明推荐内容吸引人,不是凑数;
- 收藏转化率说明推荐精准度,避免“标题党”;
- 长尾占比体现系统多样性,防止只推西湖、故宫等头部景点。
5.2 行为日志表结构升级方案
原user_behaviors表缺少关键字段,需执行以下SQL升级:
-- 添加来源字段,区分推荐/搜索/首页入口 ALTER TABLE user_behaviors ADD COLUMN source ENUM('recommend', 'search', 'homepage') DEFAULT 'homepage'; -- 添加推荐ID,用于归因分析 ALTER TABLE user_behaviors ADD COLUMN recommend_id VARCHAR(32) DEFAULT NULL; -- 创建复合索引加速查询 CREATE INDEX idx_source_recommid ON user_behaviors(source, recommend_id); CREATE INDEX idx_user_source ON user_behaviors(user_id, source);升级后数据采集逻辑:
- 当用户点击推荐列表中的景点时,前端发送:
fetch('/api/click', { method: 'POST', body: JSON.stringify({ spot_id: 123, recommend_id: 'rec_20240520_abc123' // 后端生成的唯一ID }) }); - 后端
/api/click接口插入user_behaviors时,自动填充source='recommend'和recommend_id。
5.3 用SQL快速生成答辩演示报告
答辩前最后一小时,用这段SQL生成可视化数据:
-- 生成近7天推荐效果报告 SELECT DATE(timestamp) as date, COUNT(CASE WHEN source='recommend' THEN 1 END) as recommend_impressions, COUNT(CASE WHEN source='recommend' AND behavior_type='click' THEN 1 END) as recommend_clicks, ROUND( COUNT(CASE WHEN source='recommend' AND behavior_type='click' THEN 1 END) * 100.0 / NULLIF(COUNT(CASE WHEN source='recommend' THEN 1 END), 0), 2 ) as ctr_percent, ROUND( COUNT(CASE WHEN source='recommend' AND behavior_type='collect' THEN 1 END) * 100.0 / NULLIF(COUNT(CASE WHEN source='recommend' AND behavior_type='click' THEN 1 END), 0), 2 ) as collect_rate_percent FROM user_behaviors WHERE timestamp >= DATE_SUB(NOW(), INTERVAL 7 DAY) GROUP BY DATE(timestamp) ORDER BY date DESC;执行结果示例:
| date | recommend_impressions | recommend_clicks | ctr_percent | collect_rate_percent |
|---|---|---|---|---|
| 2024-05-20 | 1240 | 186 | 15.00 | 8.60 |
| 2024-05-19 | 1180 | 172 | 14.58 | 7.56 |
这就是答辩时最硬的证据:不是“我的算法很牛”,而是“过去7天,推荐点击率稳定在15%以上,且收藏转化率持续提升”。
我带毕业设计十年,最深的教训是:别把“智能”当遮羞布,要让每个“智能”背后都有数据库里的一行记录可查。那个user_behaviors表,不是为了凑功能,而是让你在答辩时能指着屏幕说:“老师,这个推荐结果,来自张三昨天点击的3个美食景点,和李四上周收藏的5个购物地标——不是猜的,是算出来的。”
希望帮到你。
本文还有配套的精品资源,点击获取