
每年一到毕业季就会有很多同学来问我选题的事情。计算机类的毕设最怕的不是难而是方向选得太虚要么全是增删改查没有技术亮点要么一上来就啃大模型、分布式这种连自己都讲不清楚的课题。今天聊的这个项目——Python基于Django与协同过滤的旅游推荐系统是一套“看一眼就知道工作量在哪、答辩有东西可讲、代码还能跑得动”的经典组合。它把requests爬虫、协同过滤算法、ECharts可视化、Django后台管理这条数据流水线完整串了起来解决的是实打实的问题旅游信息分散、用户决策成本高系统通过采集公开景点数据、分析用户历史行为给每个访客生成个性化榜单。不管你是想找个稳妥的毕设题目还是打算把推荐逻辑延展到小程序、APP方向这篇拆解都值得你从头看到尾。1. 项目概览与技术选型为什么这个组合能打1.1 毕设选题的真实逻辑很多人选题只看“技术新不新”却忽略了毕业设计本质上是一次完整的工程实践考核。评委老师打分时核心看三件事工作量是否饱满、技术栈是否成体系、系统逻辑能否自圆其说。旅游推荐系统恰恰在这三项上都有天然优势数据来源丰富景点信息、天气、用户评论、门票价格都属于公开数据爬虫模块有真实对象可抓。算法有深度协同过滤不是“Hello World”级别的玩具它有相似度计算、评分预测、冷启动处理足够撑起论文的核心章节。展示效果好地图分布、热门排行、用户画像这些可视化图表一放出来答辩现场观感直接拉满。更关键的是这个课题的难度梯度是平滑的。基础版可以只做基于物品的协同过滤进阶版可以加用户协同、混合加权想做亮点还能接个WebSocket实时推送。这意味着不管你是什么水平都能找到一个适合自己的落点不至于做到一半放弃。1.2 技术栈逐项拆解选型这事我见过太多人栽在“过度设计”上。上来就搞微服务、消息队列、容器编排最后连环境都跑不起来。这套系统的技术栈是我认为最适合毕设阶段的一个组合每一项都有明确的存在理由技术选型核心用途选择理由Python 3.x主开发语言生态完整爬虫、算法、Web框架通吃写起来效率高Django 3.2Web后端与后台管理自带ORM、Admin、认证体系开发速度快文档成熟requests数据采集轻量、稳定配合BeautifulSoup就能完成大部分静态页面抓取协同过滤推荐引擎算法原理清晰可解释性强毕业论文有东西可写ECharts数据可视化图表丰富支持地图、热力图、大屏纯前端引入方便Redis缓存加速缓存推荐结果和热门榜单体现系统性能优化意识MySQL / SQLite数据存储小项目SQLite够用写论文推荐MySQL更规范有人可能会问为什么不用FlaskFlask确实更轻但Django自带Admin后台和完整的用户体系这在毕设里是实打实的加分项。你不需要额外写管理界面Django Admin一开数据的增删改查、用户管理、权限控制全都有了省下来的时间可以去打磨算法和可视化。另一层考虑是Django的项目结构更规整models、views、urls分得清清楚楚论文里的系统设计章节直接照着目录写就行。1.3 关于“深度学习”和“agent”的理性回归标题里提到的“深度学习”“agent”我得说句实在话毕设阶段除非你已经有扎实的基础否则不要轻易把主线押在这些方向上。深度学习模型训练需要数据量和算力旅游推荐这种场景下你很难拿到像样的训练集而agent方向的复杂度更高涉及大模型调用和Prompt设计做到后面很容易变成“调包侠”讲不清楚原理反而被评委追问到卡壳。我建议把协同过滤作为核心算法深度学习作为论文里的“拓展讨论”方向提一嘴如果学有余力可以在数据量充足的情况下尝试用FM因子分解机或简单的神经网络做对比实验但一定不要本末倒置。稳妥推进、能完整交付永远是毕设的第一原则。2. 系统架构与数据流设计2.1 整体模块划分整个系统可以拆成四个层次每一层各司其职数据在层与层之间单向流动逻辑非常清晰数据采集层通过requests抓取公开旅游网站的热门景点信息、图片链接、评分、评论内容存入原始数据表。数据处理层对采集到的数据进行清洗去重、字段规范化、中文分词与关键词提取生成结构化数据。推荐引擎层基于用户的历史浏览、收藏、评分行为通过协同过滤算法计算相似度生成TopN推荐列表。业务展示层Django负责路由与视图前端页面展示景点详情、推荐结果、可视化图表后台管理维护数据。这个分层的核心思想是解耦。每一层都可以独立测试出了问题也不用把整个项目翻个底朝天。比如推荐结果不对劲你只需要检查推荐引擎层的输入输出不需要动爬虫代码。这种工程习惯在论文的“系统设计”章节里也特别好写直接按模块画数据流图描述即可。2.2 数据从哪来requests爬虫的合规采集爬虫模块是很多同学觉得“高大上”的部分但它也是翻车重灾区。我给你一个稳妥的操作思路选目标站点要克制。优先选择结构简单、内容公开的景点信息类站点比如一些旅游门户网站公开的景点列表页、天气网站的城市天气数据。不要碰需要登录、有严格反爬、涉及用户个人隐私的站点。抓取字段要有明确边界。景点名称、所在城市、景点等级、门票参考价、简介、图片URL、用户评分这些属于公开信息够用了。绝对不碰用户手机号、身份信息等敏感字段。务必遵守网站的robots协议控制请求频率。我习惯在请求头中设置合理的User-Agent并在两次请求之间加time.sleep(1.5~3)秒。这不是怂是基本的网络礼仪也是为自己省去被封IP的麻烦。下面是一段我在项目里实际用过的爬虫伪代码负责采集旅游网站公开列表页的结构化数据import requests import time from bs4 import BeautifulSoup headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 } def fetch_poi_list(start_page, end_page): results [] for page in range(start_page, end_page 1): url fhttps://example-public-travel-site.com/poi/list?page{page} try: resp requests.get(url, headersheaders, timeout10) resp.encoding utf-8 soup BeautifulSoup(resp.text, html.parser) for item in soup.select(.poi-item): name item.select_one(.poi-name).text.strip() city item.select_one(.poi-city).text.strip() level item.select_one(.poi-level).text.strip() score item.select_one(.poi-score).text.strip() results.append({ name: name, city: city, level: level, score: float(score), url: item.select_one(a).get(href) }) except Exception as e: print(f第{page}页采集失败{e}) time.sleep(2) # 控制请求频率做有礼貌的爬虫 return results注意我在异常处理上加了容错单页失败不会中断整个任务。实际项目中我还会把已采集的URL存一份方便断点续爬。这些细节在写论文“爬虫模块实现”时都能作为功能点展开。2.3 数据建模与数据库设计数据库设计直接决定推荐算法的实现难度。我的建议是先用Django的ORM建好模型不要上来就写SQL。核心数据表有这几张from django.db import models from django.contrib.auth.models import User class ScenicSpot(models.Model): name models.CharField(max_length128, verbose_name景点名称) city models.CharField(max_length64, verbose_name所在城市, db_indexTrue) level models.CharField(max_length16, verbose_name景区等级, blankTrue) score models.FloatField(default0.0, verbose_name评分) intro models.TextField(blankTrue, verbose_name简介) image_url models.URLField(blankTrue, verbose_name图片链接) created_at models.DateTimeField(auto_now_addTrue) class Meta: ordering [-score] class UserBehavior(models.Model): 用户行为表收藏 / 浏览 / 评分 都统一记到这里 user models.ForeignKey(User, on_deletemodels.CASCADE, related_namebehaviors) spot models.ForeignKey(ScenicSpot, on_deletemodels.CASCADE, related_namebehaviors) behavior_type models.CharField( max_length16, choices[(view, 浏览), (fav, 收藏), (rate, 评分)], db_indexTrue, ) rating models.FloatField(nullTrue, blankTrue, verbose_name评分值) created_at models.DateTimeField(auto_now_addTrue) class Meta: unique_together [(user, spot, behavior_type)]这套模型有两个设计上的考量行为统一表浏览、收藏、评分放在一张表里用behavior_type区分。这样数据采集和用户操作都写到一个地方后面算协同过滤时可以直接按类型加权。外键的on_delete策略景点的on_deletemodels.CASCADE用户删除时行为记录跟着删避免产生孤儿数据。这里有个常见的坑就是Django执行删除对象时如果有关联表引用默认会报保护错误所以建表时就要想清楚删除策略。我还加了一张推荐结果缓存表字段大致是user、itemsJSON格式、strategy、expire_time。推荐结果先查缓存命中了就直接返回命中不了再走算法这样页面响应速度能提升一个量级。3. 协同过滤推荐算法核心代码与参数调优3.1 两种协同过滤怎么选协同过滤是推荐系统的老牌算法核心思想就一句话物以类聚人以群分。但具体实现有两个分支它们在毕设里的出场方式和适用场景差别很大算法类型核心思想适用场景毕设中的演示效果基于用户的协同过滤找到和你兴趣相似的人推荐他们喜欢的东西用户量少、物品量大的社区型产品更容易讲清“人以群分”的故事基于物品的协同过滤找到你喜欢的物品的相似物品推荐给你的物品数量相对稳定、用户行为丰富的电商类产品计算量小结果稳定好调试在旅游场景里我推荐你主做基于物品的协同过滤。原因很朴素景点数量相对固定用户行为随时在涨。物品相似度矩阵可以离线计算并定时更新用户在线的实时推荐直接查矩阵性能负担小。而且旅游推荐的核心逻辑是“你看过杭州西湖我就给你推乌镇”这种基于物品相似的解释用户一听就懂答辩演示时说服力非常强。3.2 相似度计算与评分预测相似度计算是协同过滤的数学地基。我习惯用余弦相似度公式长这样sim(i, j) (Σ ru_i * ru_j) / (√(Σ ru_i^2) * √(Σ ru_j^2))这里的ru_i表示用户u对景点i的评分或行为权重。如果行为类型是浏览权重记1收藏记2评分直接打分。实际写代码时不用手工算平方根可以用numpy直接操作矩阵还有一个小优化点先对向量做L2范数归一化再进行内积运算性能可以快不少。import numpy as np from collections import defaultdict def build_user_item_matrix(behaviors): 行为记录 - 用户-物品评分矩阵 behaviors: list of (user_id, spot_id, weight) data defaultdict(dict) for uid, sid, weight in behaviors: data[uid][sid] weight user_ids list(data.keys()) spot_ids list({sid for row in data.values() for sid in row.keys()}) uid2idx {uid: i for i, uid in enumerate(user_ids)} sid2idx {sid: j for j, sid in enumerate(spot_ids)} matrix np.zeros((len(user_ids), len(spot_ids))) for uid, items in data.items(): for sid, weight in items.items(): matrix[uid2idx[uid]][sid2idx[sid]] weight return matrix, user_ids, spot_ids, sid2idx def compute_item_similarity(matrix): 基于物品的余弦相似度矩阵 # 先用L2范数归一化再用矩阵乘法算相似度 norm np.linalg.norm(matrix, axis0, keepdimsTrue) norm[norm 0] 1e-10 norm_matrix matrix / norm sim_matrix np.dot(norm_matrix.T, norm_matrix) return sim_matrix评分预测我一般采用加权平均目标用户对景点i的预测评分 Σ(景点i与景点j的相似度 × 用户对j的评分) / Σ(相似度)。这里有个细节容易被忽略分母不能取零所以相似度矩阵的对角线要置零避免自己和自己算进去。我还会设一个相似度阈值低于0.3的邻居就不参与计算这能把那些“弱相关”的噪音去掉推荐质量提升很明显。3.3 冷启动问题与混合策略冷启动是每个推荐系统都躲不开的坎也是答辩老师最爱问的点。系统刚上线新用户没有任何行为记录新景点也没有任何评分怎么办我给项目设计了三条兜底路径代码里写了注释论文里也单独开了一节用户冷启动如果用户没有任何行为记录直接返回全局热门榜单Top10。热门榜可以是浏览量最高的景点也可以是评分加权后的综合排序实现简单、效果不差。物品冷启动新入库的景点没有评分就先用规则打个底分——比如按景区等级5A给4.5分、4A给4.0分加城市热度修正让它先进入候选池积累到一定行为数之后再参与协同计算。混合加权等用户行为积累到一定数量后我按7:3的比例融合“协同过滤结果”和“热门榜结果”。这个比例可以根据实际数据微调测试时发现旅游场景下7:3比纯协同过滤的点击率高了不少。我在项目里把这套混合策略封装成一个函数输入用户ID输出推荐结果列表。调试时可以直接在Django shell里调用非常方便。这部分代码也是论文里“算法改进与优化”的核心素材千万别省。4. 可视化与后台交互让毕设“有颜有料”4.1 可视化方案怎么选可视化是毕设最容易做出“高级感”的部分。ECharts是当前最合适的选手图表类型全、交互流畅、社区案例多。我在项目里做了四个核心可视化页面热门景点TOP10柱状图按浏览量或评分排序颜色渐变观感直接拉满。景点地理分布地图用ECharts的地图组件按省份维度展示景点数量和热度颜色深浅表示密度答辩演示时一放大屏很有冲击力。用户游览偏好雷达图按景点类型自然风光、历史文化、主题乐园、美食探店等统计用户的偏好分布直观展示用户画像。每日访问趋势折线图展示系统用户访问量和推荐点击量的时间趋势体现“数据分析”这个关键词。有同学喜欢搞可视化大屏把几个图表拼在一整屏里上面放关键指标下面放地图和榜单做成一个数据驾驶舱。这个思路在毕设里非常加分ECharts做起来也不难几张图表加上自适应布局就够。但是记住大屏是锦上添花核心功能推荐、搜索、详情必须稳定可用别花太多时间在炫技上。4.2 Django与前端交互的两种方式页面数据怎么从后端到前端我推荐你分两步走先易后难第一步是常规的模板渲染加AJAX。Django的模板里直接渲染景点卡片列表搜索和筛选用jQuery发AJAX请求视图返回JSON数据前端用JavaScript动态更新页面。这套方案最稳也最容易调试适合作为系统的主干。第二步是实时数据推送。如果想让系统“高级”一点可以引入WebSocket。比如管理员在后台更新了景点数据前端页面不用刷新就能收到通知或者用户正在浏览推荐列表时系统实时推送新的推荐榜单。实现这个功能需要用到Django Channels它把Django的视图层扩展到了WebSocket协议。我简单给你一个最小实现框架# routing.py from django.urls import path from channels.routing import ProtocolTypeRouter, URLRouter from .consumers import RecommendConsumer websocket_urlpatterns [ path(ws/recommend/, RecommendConsumer.as_asgi()), ] application ProtocolTypeRouter({ websocket: URLRouter(websocket_urlpatterns), })WebSocket消息格式我用JSON包含recommend列表和更新时间戳。前端用WebSocket对象监听消息拿到数据就重新渲染推荐模块。这段代码在答辩时演示“实时更新”效果会比静态图生动很多。4.3 Redis缓存与可视化工具推荐结果算好了没必要每次都重算。Redis在这里的作用主要是两层缓存推荐结果用户请求先查Redis命中直接返回推荐引擎只在缓存过期时重新计算。缓存热门榜单用ZSet存储景点浏览量排行增量更新前端展示的热门榜永远只有一次内存访问的代价。开发阶段我强烈建议装一个可视化工具来看Redis里的数据。RedisInsight和Another Redis Desktop Manager都是不错的选择。尤其是你调试缓存过期时间时能看到key的TTL和value的值比在黑框里敲命令行直观太多了。在论文的“性能优化”部分我会写“引入Redis缓存后推荐接口平均响应时间从350ms降低到40ms左右”这样的数据是评委喜闻乐见的实测结论。5. 毕设实操中的高频问题与排查实录5.1 常见问题速查表这个项目踩坑的地方不少我把高频问题整理成一个速查表方便你开发时排查问题现象可能原因解决思路爬虫采集时请求超时或返回403请求头缺少User-Agent或频率过高触发反爬完善请求头增加随机延时检查目标站点的robots策略Django运行时提示“django TypeError: Field ‘id’ expected a number”传入了字符串类型的ID数据清洗不到位检查ORM字段类型统一使用int()转换后再入库删除景点时外键关联导致删除失败on_delete策略设置不对在ForeignKey中设置on_deletemodels.CASCADE协同过滤相似度矩阵过大内存爆掉景点数量大时矩阵呈平方级增长对相似度矩阵做截断只保留TopK相似物品或改用稀疏矩阵存储推荐结果全是热门景点个性化不足冷启动兜底策略权重太大调低热门榜融合比例加大协同过滤置信度ECharts地图显示空白缺少地图GeoJSON数据按需引入china.js或使用各省市地图JSON注册WebSocket连接一直断开Channels的routing配置或ASGI服务没启动检查application配置确保用uvicorn或daphne启动而不是runserver5.2 环境配置与部署避坑环境配置对新手来说是一道坎。我给你一套稳的流程Python版本不要用最新的Python 3.13建议用3.10或3.11兼容性最好Django、Channels、mysqlclient这些库都跟得上。虚拟环境项目目录下执行python -m venv venv然后在Windows用venv\Scripts\activate激活Mac/Linux用source venv/bin/activate。这是老生常谈但真有人直接在全局环境装包最后依赖冲突装到怀疑人生。依赖导出项目跑通后执行pip freeze requirements.txt换机器部署时一条pip install -r requirements.txt搞定。这一点看似简单但对答辩现场演示来说至关重要——别等老师说“跑一下看看”时才暴露环境问题。静态文件Django中要配好STATIC_URL和STATICFILES_DIRSECharts的js文件放进去生产模式下才能正常加载。ALLOWED_HOSTS本地开发时可以设为[*]但记得这只是开发方便真正部署到服务器上需要写自己的域名或IP。还有一个真实经历有个学生把Django的DEBUGTrue状态直接部署到服务器上结果页面一报错就弹出完整的堆栈信息这在我们行业里叫“安全裸奔”。答辩前一定把DEBUG改成False并配置好ALLOWED_HOSTS和错误日志。5.3 后台管理优化的小技巧Django自带Admin虽然方便但默认界面确实有些朴素。如果你想让系统观感提升可以用django-unfold或django-grappelli这类后台美化组件。django-unfold是我近期比较看好的方案安装很简单替换admin的site header之后后台的整体质感立刻不一样。论文截图时漂亮的界面能让页面美观度和专业性增加不少这属于低成本、高回报的投巧点。6. 答辩前的最后一公里6.1 答辩常见陷阱与应对话术答辩本质上是一场“你对自己项目理解多深”的检验问来问去无非那几个方向。提前准备好应答思路现场就不会慌“为什么用协同过滤而不用深度学习”——答协同过滤的可解释性强在数据量有限的场景下性价比更高深度学习在本项目里作为拓展方向在论文中讨论条件允许可做对比实验。这是实事求是的回答比硬吹深度学习效果好得多。“推荐结果如何保证准确性”——答基于有明确权重的行为数据计算相似度并做混合加权同时通过离线实验对比了纯热门榜、纯协同过滤和混合策略的点击率混合策略有明显提升。“数据来源是否合规”——答采集自公开页面遵循robots协议和抓取频率限制仅用于学术研究不采集个人隐私数据。这个答案要背熟体现你的工程伦理意识。“系统还能怎么扩展”——答可以接入实时爬虫增量更新景点数据引入更丰富的用户画像特征做深度学习排序也可以把推荐服务化供小程序、APP多端调用。6.2 论文写作要点论文写作的顺序我建议先写系统设计和算法部分再补背景和需求分析。核心章节的逻辑主干大致是选题背景与意义 → 相关技术综述Django、协同过滤、爬虫、可视化 → 需求分析用户、管理员、推荐系统的功能性需求与非功能性需求 → 详细设计架构图、数据库ER图、接口设计 → 系统实现每个模块的关键代码和截图 → 系统测试功能测试用例、性能测试结果 → 总结与展望。技术类毕业论文最怕“全是代码没有分析”也怕“全是截图没有解释”。每一张截图旁边都要配一段话说明“这里实现了什么、难点在哪、我用了什么方案解决”。比如推荐页截图的旁边写清楚相似度计算流程、冷启动处理策略、缓存命中率爬虫模块旁边放请求延时控制逻辑和去重策略。这种“截图技术解释”的写法让论文看起来既真实又有深度评委挑不出毛病。6.3 一个实用建议把系统做成可演示闭环毕设答辩的演示环节千万不要只展示静态页面。我建议你准备一条完整的演示路径打开系统首页展示可视化大屏几秒钟让评委对项目形成第一印象。注册一个新用户没有历史行为演示冷启动场景推荐结果应该是热门榜讲解冷启动策略。用测试账号模拟浏览几个景点再刷新推荐页展示协同过滤带来的个性化变化强调“看完杭州后推荐了苏州园林”。进入后台管理演示数据维护和统计功能可以顺手用WebSocket推一个新数据展示前端实时更新。这条链路走下来整个项目的技术点全都覆盖了而且时间控制在五分钟以内节奏紧凑。很多同学答辩时喜欢把所有页面都点一遍结果时间超了重点反而不突出。记住演示是讲故事不是遍历功能清单。把项目做完之后我的几点实在话如果从头做下来这个项目的代码量大概在三千到五千行之间听起来不多但真正花时间的不是写代码而是调试协同过滤的效果和打磨页面展示。我见过不少同学在算法部分死磕想把准确率调到极致结果忽略了系统稳定性最后演示时页面报错前面的努力全部归零。所以我的建议一直是“先跑通再调优最后才加花活”每一步都要保证能随时拿出可演示的中间结果。另外不用纠结“推荐效果不够智能”。毕设考察的核心是完整的工程能力和清晰的逻辑表达。你把爬虫怎么抓的、数据怎么清洗的、推荐怎么算的、结果怎么展示的讲清楚这就是一个合格的优秀项目。至于后面想继续演进可以考虑接入大模型做旅行攻略问答或者把协同过滤升级成矩阵分解模型这些都是自然的后续迭代方向——但那是下一个项目的故事了。