ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于Python的智能旅游推荐系统,毕设实现与答辩攻略

基于Python的智能旅游推荐系统,毕设实现与答辩攻略 简介这份Python实战项目资源以智能旅游推荐系统为核心面向需要完成毕业设计、课程设计或期末项目的计算机相关专业学生解决从需求分析、数据库设计到前后端联调、运行演示的完整流程。项目基于Python 3.7开发采用HTML搭配Vue构建前端界面MySQL存储业务数据包含项目源码、数据库脚本及配套工具功能完善、界面直观适合中等基础学习者直接运行体验也可作为二次开发的原型。资源包大小为20.19MB共797个文件以45个Python后端源码、53个Vue/HTML前端页面、53个CSS样式、53个JavaScript脚本为主体并配SQL数据库文件、图标图片及说明文档。目录内附安装、运行等批处理脚本便于快速搭建环境。目前已有132人学习下载该案例覆盖典型管理系统常见模块适合想完整走一遍项目开发流程、提升数据库操作与全栈整合能力的读者。1. 智能旅游推荐系统这个毕设选题为什么每年都有人做拿到这套“Python毕业设计-基于python的智能旅游推荐系统”压缩包的人多半是正在为选题发愁的学生。智能旅游推荐系统恰好卡在一个舒服的位置它不像纯管理系统那样只有增删改查没亮点也不像深度学习课题那样对数据和硬件要求高算法上有一个协同过滤就够讲工程上又有 Web、数据库、爬虫三块可展示答辩老师能问的每一层都有东西可答。简单说这个标题对应的是一款基于 Python Web 框架、内置推荐算法、带 MySQL 数据库的景点推荐应用做完它能覆盖一个标准毕设的完整链路需求分析、数据库设计、算法实现、系统测试。适合三类人想省选题时间的、想低成本拿高分的、以及准备转行做数据或后端、需要一个完整项目来练手的新手。这套资源里打包的东西基本也按这三块走源码负责功能、数据库脚本负责数据地基、教程负责让你在答辩前能把它讲圆。2. 先把系统拆开功能边界、技术选型与最小项目骨架2.1 功能边界能参加答辩的旅游推荐系统最少要有哪几块很多毕设翻车不是因为算法太烂而是功能边界没划清楚做着做着就把系统做成“景点信息管理系统”——那跟旅游推荐已经没关系了。一个能站住脚的智能旅游推荐系统至少要包含下面五块按优先级排列模块核心功能是否必须答辩常见追问点用户模块注册、登录、个人信息必须密码怎么存的session 怎么管理景点模块景点列表、详情、搜索、分类筛选必须分页怎么做搜索用没用到索引行为采集模块收藏、评分、浏览记录必须行为数据存哪张表怎么去重推荐模块个性化推荐、热门推荐必须算法原理、冷启动怎么处理后台管理景点 CRUD、用户管理建议权限怎么控制有没有做防注入用户模块和景点模块是一切的底座行为采集模块是推荐算法的数据来源推荐模块才是你区别于“管理系统”的核心亮点。我见过不少学生把大量时间花在后台上结果推荐模块只写了一个“按评分排序”——那是热门榜不是推荐系统评委一句话就能问穿。2.2 技术选型Flask 还是 Django推荐算法用什么切入这个标题限定死了 Python剩下的选型只要围绕“与毕设匹配”来定没必要追求生产级。我一般这样搭Web 框架用 Flask。它轻、路由直观、适合把推荐算法单独拆成模块来写学生也容易在教程里讲清楚 Django 的 admin 后台虽然省事但算法逻辑和框架耦合太深答辩时容易被追问“ORM 底层怎么实现的”Flask 配合原生 SQL 反而更好讲。数据库用 MySQL。它是最常见的毕设数据库面试也认千万别为了省事用 SQLite答辩老师会直接问“为什么不用 MySQL是不是不会装”。用 MySQL 还能顺带讲清楚连接池、字符集这些加分点。推荐算法用协同过滤基于物品的 ItemCF而不是深度学习。原因很实际毕设的数据量撑不起深度学习而 ItemCF 原理简单、可解释性强你能在五分钟内把“因为你看过 XX所以推荐 YY”讲明白。深度学习模型在黑匣子里评委一问参数就卡壳。前端用服务端模板 Bootstrap不做前后端分离。少一套跨域和鉴权问题把精力留给推荐算法。VSCode 里如果配了 Python 环境但跑不起来多半是解释器选错了这个放到第 5 章讲。2.3 最小可运行的项目结构与启动顺序这类毕设包解压后代码目录我习惯组织成下面的样子也建议你一旦自己动手就按这个结构建travel_recommend/ ├── app.py # Flask 应用入口路由全部在这 ├── config.py # 配置数据库地址、密钥、推荐参数 ├── models.py # 数据访问层连接池 增删改查 ├── recommend.py # 推荐算法ItemCF 热度兜底 ├── requirements.txt # 依赖清单 ├── sql/ │ └── init.sql # 建库建表脚本含种子数据 ├── data/ │ └── scenic.csv # 景点数据源爬虫或手工整理 ├── templates/ │ ├── base.html │ ├── index.html # 首页 推荐结果页 │ └── scenic_detail.html └── static/ └── css/ js/ # Bootstrap 和前端资源先装环境再建库最后启动顺序别反cd travel_recommend python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install -r requirements.txt mysql -uroot -p sql/init.sql python app.py两条踩坑提示第一pip install装的是哪个 Python 的包取决于你激活的是哪个虚拟环境VSCode 右下角解释器选错会出现“明明装了却 import 不到”的玄学问题第二mysql -uroot -p sql/init.sql这行命令在 Windows 的 CMD 里如果报mysql 不是内部或外部命令说明 MySQL 的 bin 目录没加进系统 PATH用全路径执行或者直接在 Navicat 里导入脚本。等看到Running on http://127.0.0.1:5000项目就算跑通了。先跑通再改代码这是所有项目的第一条纪律。3. 数据库设计与数据准备推荐系统真正的地基3.1 三张核心表用户表、景点表、行为表的设计要点推荐系统的数据库设计和普通管理系统有个关键差异一定要有一张独立的“行为表”记录用户和景点之间的交互。只把收藏操作挂在用户表下面推荐算法取数会非常痛苦。核心三张表这样建CREATE DATABASE IF NOT EXISTS travel_recommend DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE travel_recommend; CREATE TABLE user ( user_id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, password_hash VARCHAR(255) NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE scenic ( scenic_id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL, city VARCHAR(50) NOT NULL DEFAULT 未知, category VARCHAR(50) DEFAULT 自然风光, price DECIMAL(10,2) DEFAULT 0, rating FLOAT DEFAULT 0, description TEXT, image_url VARCHAR(255), created_at DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_city (city), KEY idx_category (category) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE user_behavior ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, scenic_id INT NOT NULL, behavior_type ENUM(view,collect,score) NOT NULL, score TINYINT DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_user (user_id), KEY idx_scenic (scenic_id), KEY idx_user_scenic (user_id, scenic_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;建表时有三个点值得多写两行说明。一是字符集必须用 utf8mb4只写 utf8 的话遇到生僻地名或特殊符号会直接报错二是user_behavior表尽量加一个(user_id, scenic_id)联合索引推荐算法要反复按用户取历史行为这个索引能省一半时间三是我没有建物理外键只保留逻辑关联。毕设阶段物理外键会拖慢批量写入而且删除景点时容易因为外键约束报错逻辑外键在代码里控制就够了这个取舍答辩时可以主动讲给评委听。ENUM类型建议保留它把行为类型限定死在 view浏览、collect收藏、score评分三种代码里不用写一堆 if 判断来兜非法值。score字段只在 behavior_type 是 score 时才有意义浏览和收藏行为它是 0这样设计是为了后面推荐算法取数时统一口径。3.2 连接池与增删改查PyMySQL 连接 MySQL 的标准姿势毕设里最常见的低分写法是每次请求都pymysql.connect()一次用完再 close。这在小流量下能跑但被问到“数据库连接池怎么优化”就哑火了。用 DBUtils 的 PooledDB 把连接缓存起来代码量只多三行面试能多聊五分钟# models.py import pymysql from dbutils.pooled_db import PooledDB pool PooledDB( creatorpymysql, maxconnections10, mincached2, maxcached5, blockingTrue, host127.0.0.1, port3306, userroot, password你的密码, databasetravel_recommend, charsetutf8mb4, cursorclasspymysql.cursors.DictCursor ) def get_scenic_by_id(scenic_id): conn pool.connection() try: with conn.cursor() as cursor: cursor.execute(SELECT * FROM scenic WHERE scenic_id%s, (scenic_id,)) return cursor.fetchone() finally: conn.close() # 注意这里不是真断开是还给连接池参数说明maxconnections是连接池能维护的最大连接数Flask 开发模式下开 10 足够mincached是启动时就预热的空闲连接数设 2 避免第一个请求进来时现场建连blockingTrue表示连接被借完时请求排队等待而不是直接报错。SQL 里的参数一律用%s占位符传值绝对不要拼字符串这是防 SQL 注入的基本功。增删改查的另外三个操作同理只是把 SQL 换成INSERT、UPDATE、DELETE。推荐算法取数时最常用的一条 SQL 是“按用户取行为”这里有个细节同一用户可能对同一景点既收藏又评分推荐算法取数要用GROUP BY user_id, scenic_id配合MAX(score)取一条不然后面算相似度矩阵时会踩“重复索引”的坑。3.3 数据从哪来爬虫采集与 CSV 导入的取舍景点数据是推荐系统的原料没有真实景点数据整个系统就是个空壳。常见做法是爬马蜂窝或携程的城市景点页但反爬强度这几年一直在涨。我给出的建议是分两步走先用小规模爬虫验证流程再用手工整理 CSV 补充。爬虫骨架长这样# spider.py示意选择器按实际页面结构调整 import requests import time import pandas as pd from bs4 import BeautifulSoup HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0 Safari/537.36, Accept-Language: zh-CN,zh;q0.9, } def fetch_city_spots(city_code, pages3): rows [] for page in range(1, pages 1): url fhttps://example.com/poi/{city_code}?page{page} resp requests.get(url, headersHEADERS, timeout10) if resp.status_code ! 200: continue soup BeautifulSoup(resp.text, html.parser) for item in soup.select(.poi-item): # 选择器要按实际页面改 name item.select_one(.name).get_text(stripTrue) rows.append({name: name, city: city_code}) time.sleep(1.5) # 频率控制别用 0.01s 这种自杀间隔 return pd.DataFrame(rows)爬虫里最容易被忽略的是频率控制。目标站点封你通常不是因为你用了 Python而是因为每秒十几次请求太像扫描器。time.sleep(1.5)是底线追求稳妥用 2~3 秒爬个三五百条景点数据也就多等几分钟。BeautifulSoup 的选择器没有统一的写法F12看真实页面结构再改.poi-item、.name这种类名就是全部工作量。如果爬虫中途被验证码拦了别硬刚反爬直接换手工整理把景点的 name、city、category、price 填进 CSV用 pandas 清洗后导入import pandas as pd df pd.read_csv(data/scenic.csv) df df.drop_duplicates(subset[name]) df[city] df[city].fillna(未知) df[price] pd.to_numeric(df[price], errorscoerce).fillna(0) df df[df[price] 0] # indexFalse别把行号写进库里utf-8-sig给 Excel 打开不乱码 df.to_csv(data/scenic_clean.csv, indexFalse, encodingutf-8-sig)清洗这一步是给你兜底的后悔药跑推荐算法前数据必须干净drop_duplicates(subset[name])去掉同名景点to_numeric(...errorscoerce)把“免费”“待定”这种非数值价格变成 NaN 再填 0。别小看这几行很多人的推荐结果里出现“价格 0 的 5A 级景区排名第一”问题不是算法是数据里混了一堆脏值。4. 推荐算法落地从协同过滤公式到可调用的接口4.1 基于物品的协同过滤ItemCF原理与最小实现基于物品的协同过滤是毕设里性价比最高的算法核心思想就一句话如果用户喜欢 A 景点而 B 和 A 被同一批用户喜欢过那就把 B 也推荐给他。注意这里的“同一批用户”不是地域或分类相同而是历史行为模式相似——这是协同过滤和纯分类推荐最本质的区别。实现分三步构造用户-景点行为矩阵、计算景点间相似度、按用户历史行为加权生成推荐列表。上代码# recommend.py import numpy as np import pandas as pd from sklearn.metrics.pairwise import cosine_similarity def build_similarity_matrix(behavior_df): behavior_df: 必须含 user_id, scenic_id, score 三列 pivot behavior_df.pivot_table( indexuser_id, columnsscenic_id, valuesscore, aggfuncmax ).fillna(0) # 关键一步按用户均值中心化消除“有人全打5分有人全打3分”的尺度差 centered pivot.sub(pivot.mean(axis1), axis0) # 转置后算景点与景点的余弦相似度 sim cosine_similarity(centered.T) sim_df pd.DataFrame(sim, indexpivot.columns, columnspivot.columns) # 热门惩罚行为数越多的景点相似度权重压得越低避免推荐结果被头部景点霸占 popularity pivot.astype(bool).sum(axis0) penalty pd.Series(1.0 / np.log1p(popularity), indexpivot.columns) sim_df sim_df.multiply(penalty, axis0).multiply(penalty, axis1) return np.clip(sim_df.to_numpy(), 0, 1), list(pivot.columns)三个参数和运算细节要弄清楚。aggfuncmax是对同一用户同一景点的多条行为取最大评分避免收藏和评分两条记录导致透视表报重复索引错误sub(pivot.mean(axis1), axis0)是按行减均值把用户的评分习惯归一否则一个爱打低分的用户会拉低所有他喜欢景点的相似度最后的np.clip(..., 0, 1)把负相似度截断为 0负相关在推荐里没有意义只会干扰排序。有了相似度矩阵推荐生成就快了def item_based_recommend(user_id, behavior_df, sim_matrix, scenic_ids, top_n10): history behavior_df[behavior_df[user_id] user_id] scores {} for _, row in history.iterrows(): sid row[scenic_id] if sid not in scenic_ids: continue idx scenic_ids.index(sid) # 当前景点和所有候选景点的相似度权重 × 用户对它的评分 for cand_idx, sim_val in enumerate(sim_matrix[idx]): cand_id scenic_ids[cand_idx] if cand_id sid or sim_val 0: continue scores[cand_id] scores.get(cand_id, 0.0) sim_val * row[score] # 过滤掉看过的按累积分排序取前 N seen set(history[scenic_id]) ranked sorted( ((sid, s) for sid, s in scores.items() if sid not in seen), keylambda x: x[1], reverseTrue ) return [sid for sid, _ in ranked[:top_n]]打分逻辑是标准 ItemCF对用户历史里的每个景点把“它和目标候选景点的相似度”乘以“用户对它的评分”再累加。用户给过 5 分的景点它的相似景点会得到更大权重这符合直觉。过滤掉seen里的景点是必须的否则用户会反复收到自己已经收藏过的东西演示时极其尴尬。4.2 冷启动兜底热度推荐与规则候选集的配合协同过滤的致命弱点是冷启动——新用户一条行为都没有history为空scores为空返回空列表。这不是 bug是算法边界所以接口层必须做兜底。我一般用热度推荐来扛冷启动规则很简单按“有多少个不同用户收藏或评分过”排序而不是按平均评分排序。def hot_recommend(cursor, top_n10): sql SELECT scenic_id, COUNT(DISTINCT user_id) AS user_cnt FROM user_behavior WHERE behavior_type IN (collect, score) GROUP BY scenic_id ORDER BY user_cnt DESC, AVG(score) DESC LIMIT %s cursor.execute(sql, (top_n,)) return [row[scenic_id] for row in cursor.fetchall()]为什么用COUNT(DISTINCT user_id)而不是AVG(score)因为评分均值会被极少数高分刷上去一个只有两条 5 分的新景点会排在有一千条 4.8 分老景点前面这是推荐系统最常见的翻车现场之一。用“人数排序 均值做第二排序键”能同时保证覆盖面和口碑。推荐接口里把两条路合起来def recommend_for_user(user_id, behavior_df): if behavior_df[behavior_df[user_id] user_id].empty: return hot_recommend(), hot recs item_based_recommend(user_id, behavior_df, sim_matrix, scenic_ids) if not recs: return hot_recommend(), hot return recs, itemcf返回的第二个标识位是给前端和答辩演示用的告诉页面这次推荐是“热门推荐”还是“猜你喜欢”这也让评委一眼看到冷启动处理逻辑。注意一个边界老用户产生了新兴趣ItemCF 结果可能很短甚至为空这时也要回落热度推荐不能硬展现一个空列表。4.3 把推荐结果接到 Web 接口与页面展示算法算出来的是一串scenic_id要变成用户能看的页面还需要回查景点信息并渲染。Flask 接口这样接# app.py from flask import Flask, session, jsonify, render_template from models import pool, get_scenic_by_id from recommend import recommend_for_user, build_similarity_matrix, load_behavior app Flask(__name__) app.secret_key set-a-random-key-here app.config[JSON_AS_ASCII] False # 否则接口返回中文变 \uXXXX app.route(/api/recommend) def api_recommend(): user_id session.get(user_id) if not user_id: return jsonify({code: 401, msg: 请先登录}) behavior_df load_behavior() rec_ids, source recommend_for_user(user_id, behavior_df) items [get_scenic_by_id(sid) for sid in rec_ids] return jsonify({code: 200, items: items, source: source})app.config[JSON_AS_ASCII] False不加的话接口返回的景点名全是\uXXXX编码前端能用但调试时肉眼看不了属于开发期恶心你、答辩时丢分的小坑。load_behavior()把行为表全量读成 DataFrame在数据量小于几万行时完全够用不用搞实时流式计算那个是面试官才追问的话题。前端展示用一个简单的 Jinja2 模板把推荐理由直接写在卡片上!-- templates/index.html 片段 -- div classrow {% for item in items %} div classcol-md-4 card onclicklocation.href/scenic/{{ item.scenic_id }} h4{{ item.name }}/h4 span classbadge{{ item.city }}/span span classbadge{{ item.category }}/span p评分 {{ item.rating }} · 门票 ¥{{ item.price }}/p p classtext-muted{{ item.description[:50] }}.../p /div {% endfor %} /div不要试图在推荐卡片里塞太多字段用户扫一眼能认出“城市、分类、评分”就够了。真正的加分项是加一行“推荐理由”比如“因为你收藏了西湖所以推荐西溪湿地”——这个在答辩时比任何公式都有说服力第 6 章会讲具体做法。5. 常见问题排查与避坑记录5.1 环境与运行时的四个高频坑现象一pip install -r requirements.txt之后python app.py报ImportError: cannot import name soft_unicode from markupsafe。原因老版本 Werkzeug 依赖的 MarkupSafe 接口在新版被移除常见于 Python 3.9 配了最新版依赖。解决固定住兼容组合pip install werkzeug2.3.8 markupsafe2.0.1然后重启。这类坑属于“能跑就行别追新”毕设项目锁死依赖版本是最稳的。现象二代码里import MySQLdb报ModuleNotFoundError。原因MySQLdb 是 Python 2 时代的库Python 3 下没有官方包。解决统一用 PyMySQL并在app.py或models.py开头加一行pymysql.install_as_MySQLdb()这样老代码里所有MySQLdb的引用都能跑通不用逐行改。现象三VSCode 里运行项目明明pip list里有 Flask运行时却报ModuleNotFoundError: No module named flask。原因左下角选中的 Python 解释器不是虚拟环境里的那个装包的 Python 和跑代码的 Python 根本不是同一个。解决打开命令面板CtrlShiftP输入Python: Select Interpreter选venv目录下的解释器命令行一律用python -m pip install而不是裸pip install。这条能救回一半以上的“环境玄学”问题。现象四mysql -uroot -p sql/init.sql在 Windows 下报mysql 不是内部或外部命令。原因MySQL 的 bin 目录没进系统 PATH。解决用全路径执行比如C:\Program Files\MySQL\MySQL Server 8.0\bin\mysql.exe -uroot -p sql/init.sql或者打开 Navicat 手动执行这条 SQL 脚本。另外注意SQL 文件路径里如果含中文CMD 可能识别不了把项目路径改成纯英文最省事。5.2 推荐效果与数据质量的三个坑现象五导入数据库后页面上景点名全是问号。原因导入 SQL 脚本时客户端连接没指定字符集数据被按 latin1 存了。解决命令行导入时加--default-character-setutf8mb4连接串里也手动指定charsetutf8mb4——只靠建表语句写 utf8mb4 不够连接层也要对齐。已经导进脏数据的DROP DATABASE重建库重新导入别想着ALTER TABLE转换血的教训不值得重演。现象六跑build_similarity_matrix时报ValueError: cannot reindex on duplicate labels。原因user_behavior 表里存在同一个人对同一景点的多条记录pivot_table 不知道取哪条。解决读取时先behavior_df behavior_df.groupby([user_id,scenic_id], as_indexFalse)[score].max()保证每个用户-景点对只有一行再去 pivot。这句去重逻辑建议直接写进load_behavior()一劳永逸。现象七推荐结果全是热门景点十个里有八个是同一个城市的 5A 级。原因没做热门惩罚和评分中心化热门景点和所有景点都相似打分时永远排在前面。解决回看第 4.1 节的中心化和log1p惩罚两项缺一不可。只加惩罚不加中心化少数高分用户的偏好会被放大只做中心化不加惩罚头部景点照样通吃。5.3 排查工具与调试习惯遇到推荐算法结果不对时先别改参数先看数据链路。我习惯按三层查第一层查行为表里的原始记录用SELECT user_id, COUNT(*) FROM user_behavior GROUP BY user_id LIMIT 10确认有用户真有行为数据第二层查相似度矩阵直接打印sim_df.loc[西湖].sort_values(ascendingFalse).head(10)看看西湖的相似景点里有没有明显不相关的第三层查推荐打分抽一个用户手动算一条比对着代码查是哪里多乘了或少加了权重。把这三条 SQL 和调试打印固化成一个小脚本debug_recommend.py每次改完算法跑一遍比每次都用浏览器点半天快得多。推荐算法是个黑匣子不假但你可以主动把黑匣子的中间输出拿出来看不要靠肉眼猜结果。最后能稳定复现的坑永远是“数据问题优先于算法问题” —— 先确认行为数据干净再动算法参数。6. 答辩前把系统从“能跑”提到“能讲”6.1 演示前先算两个指标覆盖率与多样性很多人的毕设演示只展示“推荐列表挺像样”但评委一句“你这个推荐效果到底怎么量化”就卡住了。论文里没必要上离线 AUC——你的数据量根本没建模测试集——但有两个指标在答辩现场算得出来又有说服力覆盖率推荐结果能覆盖多少不同景点和类别多样性推荐结果在多少个分类间分布。十行代码能出结果def coverage_and_diversity(rec_ids): total_scenic scenic_count() # 景点总数 rec_scenic len(set(rec_ids)) # 推荐结果里不同景点数 coverage rec_scenic / total_scenic # 多样性类别数量 / 推荐总数按 category 字段去重 distinct_cats distinct_category_count(rec_ids) diversity distinct_cats / max(len(rec_ids), 1) return round(coverage, 3), round(diversity, 3)这两个数不追求高追求的是“有数可讲”。覆盖率低说明推荐集中你可以用热门惩罚解释多样性低说明用户当前兴趣聚焦你可以用协同过滤本身的特性解释。手里有指标任何追问都能接住。6.2 一个让评委点头的“推荐理由”组件在推荐卡片上补一行字权重比翻十倍“因为你去过/收藏了 A所以把相似的 B 推荐给你”。实现方式很简单在item_based_recommend里记录每个候选景点的最大相似来源回传前端展示。这一行字直接命中评委最想听的“可解释性”——它证明你不是拿了个黑匣子糊弄而是真的理解 ItemCF 的推荐链路。我带去答辩的几个项目评委每次都在这个组件上多问了两分钟然后转向下一项——这已经是毕设答辩里很好的结果了。最后说一个习惯拿到这类毕设包我从来不会解压完就冲动改代码而是先按第 2 章的启动顺序跑通一遍再按第 3、4 章的人肉跑一遍数据链路最后才动手加自己的功能。代码能跑不代表你懂它答辩翻车的从来不是不会写的人而是没想清楚就上台的人。希望你把这个项目当成一块跳板跑通之后自己加一个“相似景点地图”或“行程天数筛选”的小功能把它变成你自己的作品。希望帮到你。本文还有配套的精品资源点击获取
返回列表