每年到了毕设季,都会有一批同学被“Django+大模型+推荐系统”这类题目吸引,又不知道怎么下手。前几天有位学弟问我:题目是“基于Django和LLM大模型的智能旅游路线规划与个性化推荐系统”,听起来很高大上,但实际要做哪些模块?论文怎么组织?答辩怎么讲?
这其实就是今天我想聊的话题——做完一个同类毕设项目后,我把它拆成几层看:数据层怎么做、推荐怎么和LLM配合、路线规划怎么落地、数据分析怎么呈现,以及真正会踩的坑在哪里。这篇内容既适合选了这个题目、还没定技术方案的人参考,也适合已经写了部分代码、但发现系统“只是CRUD”的同学对照补全。
我尽量用做过一遍的实际视角来讲,不给你甩一堆论文腔,每一块都尽量落到能跑、能演示、能答得出来的具体程度。
1. 拆题与架构:这个毕设题目真正要交付的是什么
很多同学看到题目里有“LLM大模型”就直接往“聊天机器人”方向想,结果做出来就是套壳问答。这个题目的核心其实是一套完整的旅游服务闭环:用户来了、表达偏好、系统给推荐、系统给路线、用户看数据可视化、再反馈。大模型是中间增强体验的组件,不是全部。
1.1 题目里的四个关键词到底对应哪些交付物
“Django”对应一个完整体验的B/S系统,需要用户端、管理端、前端页面、后端API、数据库表;“LLM大模型”对应至少两个具体能力:理解用户的自然语言偏好、生成推荐理由或路线说明;“数据分析”对应统计页面,要能按热度、类别、时间等维度展示图表;“个性化推荐”对应一套能根据用户历史和行为产生排序结果的算法模块;“旅游路线规划”对应把多个景点排成合理行程的后端算法。
这五块不是并列关系,而是一条数据流水线:数据底座支撑推荐和路线,LLM又从数据里提取人的意图并做表达,最终在页面统一呈现。论文结构和个人工作日记的分节也可以照着这条逻辑写。
我之前看到有人只做了登录注册加景点列表,题目写上“智能推荐”,结果答辩论证算法时一句话都说不出。所以第一件事就是把每块交付物列清楚,别把题做窄了。
1.2 我推荐的系统技术栈与整体分层
技术选型决定了你开发效率、演示稳定性和答辩可聊深度。我的建议如下表,不一定是最新版本,但组合起来最稳妥:
| 层次 | 选型 | 说明 |
|---|---|---|
| 后端 | Django 4.x + Django REST Framework | 用DRF写API比直接render模板更干净,方便前端做交互 |
| 前端 | Bootstrap 5 或 Vue3 + Element UI | 熟悉HTML就选Bootstrap,想加分可以上Vue3,但时间成本高一截 |
| 数据库 | MySQL 8.0(开发期可用SQLite) | 毕设演示用SQLite最方便,提交文档写MySQL即可 |
| 缓存 | Redis | 缓存推荐结果和大模型回复,减少重复调用 |
| 可视化 | ECharts | 图表丰富,答辩时视觉效果最好 |
| 大模型 | 通用大模型API或本地开源模型 | 有API Key走API,没预算就本地部署一个开源对话模型 |
| 推荐算法 | 手写基于内容召回 + 协同过滤 | 不建议上来就用复杂深度学习,毕设手写经典算法更好讲 |
整体架构我习惯按四层分:展示层(页面+图表)、接口层(Django View/DRF)、业务层(推荐、路线、数据统计)、数据层(MySQL+Redis)。大模型服务单独封装成一个Client,不直接散落在业务代码里,这样它挂了也不会让整个系统崩掉。
这种分层的好处是:你答辩时问“某个模块怎么改”,你能说清楚哪一层动哪里。比如想换推荐策略,只换业务层里一个函数;想换大模型厂商,只换Client内部实现。这比把所有逻辑堆在一个view函数里好讲一百倍。
2. 数据底座:景点画像、用户行为与预处理,建模比写接口更早
不管是推荐、路线规划还是数据分析,最终都是吃数据的。很多同学一上来先写页面,写到一半发现没有数据可用,又回头造数据,这是最浪费时间的方式。我的建议是:开工第一周先把数据表和一批演示数据定下来。
2.1 核心数据模型:景点表、用户表、行为表怎么设计
这个系统至少需要以下几张表,我整理成可以直接参考的字段清单:
- 景点表:id、名称、城市、类别标签(自然风光/历史人文/亲子娱乐)、评分、门票价格、热度指数、经度、纬度、建议游玩时长、简介、封面图、开放时间。
- 用户表:id、昵称、注册时间、偏好标签(JSON字段,存“亲子”“轻松”“美食”这类关键词)。
- 行为表:id、用户id、景点id、行为类型(浏览/收藏/下单)、行为时间、行为分值。
- 路线表:id、用户id、生成方式(推荐算法/大模型调整)、景点id列表、每日安排、总时长、备注。
- 统计缓存表:id、指标类型、统计值、统计日期,用于加速数据面板。
景点表里的“经度、纬度、建议游玩时长”是路线规划的硬依赖,少了它们后面算法根本没法跑。行为表是推荐系统的数据来源,建议一开始就写上“行为分值”,比如浏览0.5分、收藏1分、下单2分,后面算用户偏好向量会非常顺手。
2.2 数据来源与预处理:经纬度、标签归一化、行为分布
数据来源这块,我建议优先使用公开的演示数据集,或者自己构造一批符合真实分布的模拟数据。自己造数据时要遵守上限:热门景点浏览量大、冷门景点少,评分符合长尾分布,不要每个景点数据都一样,否则推荐算法演示出来就是一潭死水。
预处理环节有几件事一定要做:
一是经纬度统一到小数点后6位,方便计算距离;二是类别标签做归一化,比如“自然”“自然风光”“山水”统一映射成“自然风光”,否则推荐召回全串戏;三是热度指数按Log变换压到0到100区间,避免个别极值拉爆比较;四是门票价格、评分字段做缺失值填充,一般用均值或类别最频繁值。
我实际做的时候发现,最容易被忽略的是“时间字段”。行为表必须保留时间,因为数据分析模块要画“旅游热度随月份变化”的折线图,没有时间你会卡在这里重新造数据。
2.3 用JSON偏好结构把自然语言需求“翻译”给系统
用户在前端输入的可能是一整句话:“我想带爸妈去某市玩3天,节奏轻松点,喜欢逛公园和古镇,预算别太高。”这段文本不能直接用于SQL查询,需要把它转成结构化偏好。
我在项目里定义了一个统一的偏好JSON,样例如下:
{ "destination": "某目标城市", "days": 3, "travel_type": "家庭游", "pace": "轻松", "budget_level": "中等", "preferred_tags": ["公园", "古镇"], "avoid_tags": ["爬山"], "must_visit": ["某海滨公园"], "start_location": "用户酒店经纬度" }这个JSON是最关键的中间结构。它同时喂给三处:后端查询条件、推荐算法的标签权重、路线规划的时间约束。所以你看,“大模型”不是终点,而是从一句话到结构化数据之间的转换器。
3. 推荐模块:协同过滤、内容召回与大模型精排的三种协作方式
推荐是这个题目的灵魂,也是最容易在答辩环节被追问的部分。你不需要做得多“深”,但要能清晰说出来:我的推荐系统分哪几步,每一步解决什么问题。
3.1 先看基础量:用户协同过滤和基于内容的召回怎么写
我建议做两个基础召回,不要只做一个。
基于内容的召回思路:把用户的历史行为标签加权汇总成用户偏好向量,再与景点标签向量做余弦相似度。一个可以跑的简化版本大概是:
import numpy as np def content_recall(user_pref_vec, spot_vectors, top_n=30): # user_pref_vec: 用户对标签的加权偏好向量 # spot_vectors: 景点标签向量矩阵 scores = np.dot(spot_vectors, user_pref_vec) # 归一化 scores = scores / (np.linalg.norm(spot_vectors, axis=1) + 1e-8) top_indices = scores.argsort()[-top_n:][::-1] return top_indices基于用户的协同过滤思路:先算出活跃用户之间的相似度,再找“和你喜欢相近景点”的人,把这些人额外喜欢的景点推荐出来。用户相似度可以用Pearson相关系数,也可以用余弦相似度,关键是要说明你选某个相似度指标的理由。毕设里用余弦相似度最简单,也最好解释。
实际执行时,两个召回各取Top30,然后合并去重,进入重排阶段。不建议直接对全量景点做排序,召回后再排序是业界通用方案,复杂度可控,效果也足够演示。
3.2 大模型在推荐里的三个真实用途(意图解析、重排、推荐理由)
大模型在推荐系统里不是替代算法,而是做三件算法做不好的事。
第一是意图解析。前面说的偏好JSON,就是由大模型从用户自然语言里抽取出来的。你需要写一个Prompt,要求模型输出固定JSON结构,然后后端用JSON解析入库。这个能力一旦跑通,整个系统立刻显得“智能”不少。
第二是基于规则的轻量重排。我当时的做法是:把召回阶段得到的Top候选和偏好JSON一起发给大模型,让它根据“是否避开必去景点、是否符合预算、是否符合标签偏好”做一次排序调整。注意这里是调整,不是让模型凭空创造景点,否则它可能给你编一个“某市梦幻城堡”这种不存在的景点。
第三是生成推荐理由。比如“为您推荐A景点,因为您过去经常浏览公园类景点,而且它评分高、适合慢节奏游玩”。这段文字如果用模板生成,会显得很机械;用大模型生成,答辩现场演示时观感很好。
3.3 混合推荐的执行流程与接口设计
我在业务层里写了一个统一的推荐入口函数,流程是这样:
- 判断用户是否有足够行为数据;如果行为少于3条,走冷启动策略,直接推平台热门Top。
- 有行为的用户,并行执行内容召回和协同过滤召回。
- 两个Top30合并去重,保留相关性分。
- 调用大模型意图解析,输出偏好JSON。
- 偏好JSON参与重排:规则过滤掉避开类别的景点,再按加权分排序。
- 取Top10返回前端,同时异步调用大模型给前3个景点生成推荐理由。
不要每次请求都实时跑一遍大模型。实测下来,大模型单次响应基本在1到3秒,用户体验很受影响。我当时做了一层Redis缓存:把同一用户的推荐结果缓存30分钟,大模型生成的推荐理由缓存到展示结束。这样演示时丝滑,也体现你考虑过性能设计。
接口设计建议给DRF写一个/api/recommend/,参数只要一个用户id或session标识,返回推荐景点列表、推荐理由、路线预览三块内容。一个接口搞定,前端调起来也省事。
4. 路线规划:景点排序不只是算法题,它是多个约束的取舍
路线规划是另一个容易做花哨但不落地的地方。很多同学一说路线规划就想上TSP、遗传算法,但对一个毕设系统来说,真正重要的是先把“约束”想全,再选择合适的算法。
4.1 硬约束:游玩时长、交通时间、开放时段、节奏偏好
我先列几个必须考虑的硬约束:
- 每日可用时间。比如一天从9点到18点,扣掉午餐,实际游玩窗口只有8小时。
- 景点建议游玩时长。每个景点在表里都有这个字段,累加不能超过窗口。
- 景点间交通时间。不能直接算直线距离,要按城市路况给一个换算系数。
- 开放时段。有些景点周中闭馆,或者下午才开,要考虑进去。
- 用户节奏偏好。节奏松则每天最多排2个核心景点加1个备用,紧凑可以加到4个。
- 起点终点。通常需要从用户入住的酒店位置出发,最后回到酒店。
好多人在算法上折腾很久,结果忽略了“用户第三天要返程”,路线硬排到了返程时间外。这种属于需求理解不到位,答辩时也很容易被问倒。所以开始写算法之前,先把约束写成配置,比如MAX_DAILY_HOURS=8、TRAFFIC_COEF=city_coef。
4.2 贪心构造与2-opt优化:一段可运行的核心逻辑
路线生成我推荐一个做法:先用贪心最近邻构造初始路线,再用2-opt做局部优化。不要一上来就写遗传算法,贪心加局部优化已经能覆盖大部分演示需求,而且每一段逻辑你都讲得清。
贪心构造思路:
- 从用户起点出发,计算候选景点距离。
- 优先选择“当前时间窗口内能到达且能游玩完毕”的景点。
- 选择综合分最高的景点加入路线,更新当前时间。
- 重复直到时间窗口用完或候选为空。
这里综合分可以定义为:score = 0.6 * 景点评分 + 0.3 * 偏好匹配度 - 0.1 * (交通时间/最大交通时间)。这个公式不用多复杂,关键是你要能解释每一项的意图。
2-opt优化核心是:依次尝试交换路线中两个景点的访问顺序,如果总交通时间变短且不违反约束,就保留交换,循环直到没有改进。距离计算可以用Haversine公式:
from math import radians, asin, sqrt, sin, cos def haversine(lon1, lat1, lon2, lat2): R = 6371.0 dlon = radians(lon2 - lon1) dlat = radians(lat2 - lat1) a = sin(dlat / 2) ** 2 + cos(radians(lat1)) * cos(radians(lat2)) * sin(dlon / 2) ** 2 return 2 * R * asin(sqrt(a))这个函数不光路线用,地图展示和分析模块也能复用,所以放一个独立的utils.py里。
跑完之后把路线存到路线表,生成行程单,格式类似“第一天:酒店-海滨公园(3小时)-老街(2小时)-酒店”,并附上每天的交通时间合计和总距离。这已经是完整可展示的结果了。
4.3 为什么最终展示路线要让大模型做“包装”而不是做“计算”
一开始我也想靠大模型直接规划路线,后来发现它最大的问题是“自信地编数据”。问它A景点到B景点要多久,它能一本正经给你一个很好看的数字,但实际可能完全相反。旅游路线这种涉及真实坐标、时间和距离的任务,模型算力再强也拼不过确定性算法。
所以我的分工很明确:算法负责精确计算和约束检查,大模型负责文案包装与用户沟通。算法生成的是“第1天 09:00-11:00 海滨公园”,大模型负责把它变成“上午先到海滨公园吹吹风,全程步行栈道,适合带长辈慢慢走,大约2小时,然后去老街吃午饭”。前者保证可靠性,后者提升体验。答辩的时候我把这个分工讲清楚,其实非常加分,说明你理解大模型的能力边界。
5. 数据分析模块:从数据库到ECharts的完整链路
数据分析不能只放几张静态截图。系统内要做成“数据一变,图表就变”的动态面板,才能体现Django后端处理和前端可视化的联动。
5.1 分析指标怎么选:热度、画像、季节性、区域分布
我建议从上到下列几个能撑起全程演示的指标:
- 目的地热度榜:按收藏量、浏览量和点评数加权排序的Top10景点。
- 景点类别占比:自然风光、历史人文、亲子娱乐等类别的收藏占比,用饼图。
- 月度热度趋势:近12个月的用户行为量变化,折线图,能看出节假日峰值。
- 用户画像分布:年龄段、出游类型(亲子/情侣/好友)的分布,柱状图。
- 区域热力地图:把景点经纬度打在目标城市地图上,用热力图展示热度聚集。
回答“为什么做这些指标”时要有个统一主线:它们分别服务推荐系统冷启动(热门榜)、路线偏好(类别占比)、运营趋势和用户理解。换句话说,数据分析不是孤立模块,它和推荐、路线是扣在一起的。
5.2 后端聚合查询与接口设计
我用了Django ORM的聚合查询,不需要写原生SQL:
from django.db.models import Count, Sum, F from .models import Behavior, Spot def hot_spot_data(): return ( Behavior.objects .values('spot__name') .annotate(cnt=Count('id')) .order_by('-cnt')[:10] )响应结构统一成{"categories": [...], "values": [...]},前端ECharts直接填充。区域热力图再单独给一个接口,返回景点经纬度和热度值的数组,前端用地图散点加聚合实现。
性能上,如果数据量小,每一页动态查询没问题;但为了答辩演示流畅,我当时对几个高频统计页做了缓存:每天早上计算一次聚合结果写入缓存表,页面刷新时先读缓存。这个设计在答辩里非常值得讲——它说明你考虑了真实业务场景下的数据时效和查询压力。
5.3 演示时最容易加分的数据问答与智能报告
数据分析也接上大模型,会让演示效果上一个档次。我的实现是:做几个预设的数据问题模板,比如“本季度热度最高的3个景点是什么”“哪类景点最受欢迎,为什么”,后端先把统计结果查出来,然后用大模型把数据组织成一段自然语言汇报。
这里注意:不要让大模型直接碰数据库。你把聚合好的数字放进Prompt,让模型做事实描述。比如“自然风光类景点收藏占比38%,排名第一;历史人文类31%排名第二。适合推荐给偏好自然景色的新用户作为冷启动候选”,这段文字既有数据支撑,又有人味。
我当时还在页面侧边加了一个“数据问答”输入框,用户输入问题,系统匹配预设模板、执行统计、交给大模型生成回答。虽然只支持有限的几个问题模板,但演示时几乎每次都有人追问“这是不是真的随便问都行”,你只要说清楚规则边界,就显得很扎实。
6. 从开发到答辩:这个题目常见的坑与应对方法
最后这部分是我最想写的,因为代码写不出来的坑、答辩被问倒的瞬间,往往比功能本身更折磨人。
6.1 环境与依赖的兼容性问题
Django 5和部分第三方库版本不一定完全兼容,尤其DRF版本、Python版本之间容易出幺蛾子。我踩过的坑:一开始装了Python 3.12加Django 5.0,结果某个后端依赖包没跟上,跑测试直接崩溃。后来老老实实退回Python 3.10加Django 4.2,一切就稳定了。
建议固定一套版本组合,写进requirements.txt,不要追求最新。另外开发期数据库用SQLite确实省事,但如果你选题里明确要求MySQL,最好早一点切换过去,别拖到快答辩才换,到时候编码、时区、大小写敏感问题一起冒出来,心态会崩。
6.2 大模型调用稳定性与成本控制
大模型API调用有几个实际烦恼:超时、限流、返回格式不稳定。我的处理办法是在llm_client.py里统一封装,设置超时时间为8秒,失败自动重试一次;重试仍失败就降级返回预设的模板内容,保证系统不白屏。
返回格式的稳定性更坑。你让模型输出JSON,它有时候在前头多一句“好的,根据您的需求生成如下结果”,导致json.loads直接报错。解决方式是Prompt里写死“只输出JSON,不要任何解释”,同时后端用正则把第一个{之前的内容去掉,再做解析。双保险之后,演示时基本不会翻车。
成本方面,演示时建议全程走API缓存命中,真要让模型实时生成,控制在几轮以内。本地部署开源模型是备选方案,但显存和推理速度你要提前测,别等答辩当天才首次启动。
6.3 演示数据与演示流程的准备
演示数据要“有故事感”。我造了一套目标城市的100个景点、500个模拟用户、2万条行为记录,数值都按真实分布拍过。这样推荐、路线、数据面板都能出现“合理的结果”,而不是十个景点全是5分。答辩场景切换也要排好顺序:先走用户注册和偏好填写,展示意图解析JSON;再看推荐列表和推荐理由;然后生成路线看行程单;最后切数据面板讲趋势。每步控制在1分钟以内,总共5分钟讲完主线。
还有一个容易被忽略的点:准备一套“用户无任何行为”的演示路径。专门展示冷启动推荐怎么给热门榜,既说明你处理过冷启动,又防止现场操作失误时没有退路。
6.4 答辩常见提问与回答思路
把自己当答辩老师,列出最可能被追问的几个问题,提前准备好回答:
- 为什么选择Django而不是Flask?答:Django自带ORM、Admin后台和完整生态,适合快速交付一个多模块业务系统;Flask轻量但需要自己拼装太多组件。
- 大模型在系统里具体服务哪个环节?答:意图解析、推荐理由生成、路线文案包装和数据问答,并强调不做数据计算。
- 推荐结果怎么评估?答:用离线数据做召回率、TopN命中率对比,在线部分用点击率、收藏率观测;毕设里可以做一个简单的A/B对比表。
- 冷启动怎么解决?答:行为不足时走热门榜兜底,同时用用户注册时填写的偏好标签做基于内容的推荐。
- 路线规划你的算法有没有证明最优?答:没有证明全局最优,但通过约束检查和2-opt局部优化,能在可行时间内得到接近最优的可行解,这对实际用户场景已经足够。
这些问题不需要背大段文字,关键是能脱稿讲清逻辑。我自己当时害怕被追问算法数学细节,后来发现只要能把“输入、输出、约束、为什么这么选”讲顺,老师基本不会再深挖。
做这个题目最大的体会是:技术栈本身不复杂,难的是把各模块串成一个闭环,并且给每一步选择留好理由。如果让我重新做一次,我会把更多时间花在数据质量和演示流程上,而不是多写几个看起来花哨但用不上的功能。你真正需要的是一个“每一步都能自证为什么”的完整系统——这正是答辩时最值钱的东西。