资讯动态

基于Spark的旅游推荐系统开发全解析:从ALS算法到Spring Boot架构

发布时间:2026/10/20 14:52:31 来源:尧图企业网站定制
做毕设选题目的时候很多同学会跑过来问我什么样的题目不踩坑、答辩能讲出东西又不会把自己困在死胡同里。我给的答案一直是同一个方向垂直场景 大数据技术 算法落地。说直白点就是类似“基于Spark的旅游推荐系统”这种题。它不是一个简单的XX管理系统也不是纯算法论文而是前后端业务、数据清洗、分布式计算、推荐引擎全部串起来的东西。Spring Boot负责对外接口Spark负责离线计算和模型训练前端做一些可视化展示整套做完技术栈完整、业务闭环、算法真实评分自然不会差。这篇文章我会把这个项目的完整设计思路、数据与算法落地细节、常见翻车点和答辩准备都梳理出来重点回答几个大家最关心的问题为什么这套技术栈搭在一起是合理的、推荐结果是怎么真正算出来的、以及Spark和Spring Boot之间应该怎么配合而不是硬耦合。1. 先从技术选型说起Spark为什么值得放进毕设里1.1 管理系统和推荐系统评委想看的东西完全不同你去看历年的毕设题最扎堆的就是学生信息管理、图书馆管理系统、停车场管理系统这类。这类系统不是不能做但问题在于逻辑太“平”增删改查四个字能概括全部数据库设计再规范也体现不出太多区分度。答辩时老师问一句“有什么技术难点”你很难接住。推荐系统不一样。它天然自带三个讨论点一是用户行为数据怎么收集和建模二是推荐算法如何选型和评价三是大数据框架在什么环节真正发挥作用。这三个问题每一个都可以展开讲、有得聊而且都能用“我做了实验对比”“我做了数据评估”来支撑。评委感兴趣的点一旦被勾起答辩节奏就会被你带跑。再加上“旅游”这个场景它比电商、电影推荐更贴近日常生活评委对需求理解没有门槛数据也容易拿到。用户想找景点、想规划路线、想避开人群这些都是真实需求比虚拟的业务场景更有说服力。1.2 Spark在整个项目里到底负责什么我见过不少同学把Spark硬塞进项目里比如用Spark RDD排序一个几百条的数据然后说“我用到了大数据技术”。这个思路很容易在答辩时被打穿因为Spark的优势在于分布式并行计算如果你处理的数据量连单机内存都放得下那Spark就是多余的。所以在设计之初就要想清楚Spark承担的必须是“单机处理起来吃力”或者“需要周期性重算”的任务。在我们这个旅游推荐系统里Spark主要做了三件事用户行为日志的离线清洗和特征统计。用户浏览景点、收藏地点、搜索景区、下单旅行产品的日志经过预处理后按用户维度、景点维度聚合生成评分矩阵和用户画像标签。基于协同过滤的推荐模型训练。Spark MLlib里的ALS矩阵分解算法在分布式环境下可以处理远大于单机运行的用户-物品矩阵这也是整个推荐系统的核心。周期性的批量任务调度。离线建模并不是只跑一次随着用户行为不断累计模型需要定时更新比如每天凌晨跑一次全量任务这种周期计算场景正是Spark的强项。要特别说明的是Spark和Hadoop中的MapReduce/compute engine不同它更擅长内存迭代计算正好匹配ALS这种需要反复迭代的矩阵分解算法。这也是不选MapReduce而选Spark的原因。如果你答辩时把这段常规思路讲出来老师的第一个追问基本上就被你提前预判了。1.3 Spring Boot和Spark为什么不是二选一有的同学把Spring Boot理解为后端框架把Spark理解为计算引擎然后困惑“那我到底用哪个”。其实这两者是上下游关系而不是并列关系。Spring Boot跑在在线服务层负责接收前端请求、查到推荐结果、返回JSON数据。它不负责“算出”推荐结果。Spark在离线计算层负责大规模数据的处理和模型训练产出结果后写回到数据库。用户在网页上的操作只是去查数据库里已经算好的结果。我把它们比喻成“中央厨房”和“前台出餐”Spark是中央厨房提前把菜批量做出来放保温柜里Spring Boot是前台出餐员客人点单时直接从保温柜里拿。食堂不可能等客人下单了才开始炒菜在线服务也不能让用户去等Spark现算。这套思路在答辩时叫“在线离线分离架构”是完全站得住脚的。2. 系统整体架构设计一条完整的数据流转链路2.1 分层结构做毕设时我最推荐把系统拆成五层每层各管各的方便写文档也方便和老师解释层级组件/技术职责数据采集层网页埋点、后端日志、爬虫采集获取用户行为、景点信息、评价数据数据存储层MySQL、HDFS、Redis业务数据、原始日志、缓存推荐结果计算引擎层Spark Standalone MLlib数据清洗、评分矩阵构建、ALS模型训练业务服务层Spring Boot用户管理、景点展示、推荐接口、路线定制展示层Vue ECharts前端页面、推荐结果可视化、后台管理面板这五层不用都做得特别重每个层能讲清楚主要模块和一份核心逻辑就可以。很多同学会把大量时间花在后台管理页面上在我看来不划算。管理页面能做基础的分页查看就够了真正要花心思的是推荐链路的数据流。2.2 数据是怎么从原始行为变成推荐结果的我给你画一条主线你照着这条主线安排开发顺序就不会乱用户在网页上浏览景点、评分、收藏这些操作被记录到日志表。定时任务将日志同步到HDFS同时把结构化数据导入MySQL。每天凌晨Spark on Standalone提交一个离线任务从MySQL/HDFS读取一段时间内的用户行为。Spark对数据进行清洗、去重、时间窗口过滤构建出用户-景点评分矩阵。用ALS算法训练模型对每个用户生成TopN景点推荐候选。把推荐结果写回MySQL表或者写入Redis缓存。用户打开首页Spring Boot根据用户ID读取推荐列表返回给Vue前端展示。如果用户是新用户没有推荐记录走规则策略热门榜、当季景点榜、默认城市榜。注意第5步和第6步中间的推荐结果并不一定直接就是最终内容。后续还可以做补全过滤去掉用户已经去过的景点、去掉不营业的景区、按距离远近加重排序。这一步在项目里叫“后处理策略”虽然不复杂但在演示的时候效果很直观。2.3 本地开发和部署的环境清单很多同学一听到“Spark集群”就慌担心要搞几台服务器。毕设完全不必。你的本机如果内存有16GB直接装Spark Standalone单机模式就够了和集群模式相比代码上没有任何区别只是运行环境少了多节点。以下是我实际使用的环境组合供你复制JDK 1.8Maven 3.6Scala 2.12Spark 3.1.x自带Scala运行时本地提交任务时版本要对Hadoop 3.x可选只在读HDFS时需要建议直接读MySQL或本地文件省很多事Spring Boot 2.5.xMySQL 5.7Redis 6.xVue 2.x Element UI ECharts我特别提示一下Spark与Scala版本必须严格对应。Spark 3.1默认用Scala 2.12如果你引入的第三方包编译版本是2.11提交任务时会直接抛版本冲突。这个坑我用了一个下午才排查出来先放到这里提醒你。3. 推荐算法核心不是拿个别人算法来抄是理解拿来做组合3.1 两类协同过滤到底用哪种推荐系统里最基础的算法就是协同过滤分为基于用户的UserCF和基于物品的ItemCF。UserCF核心是“找和你口味相似的人把你没去过的、他们去过且评价高的地方推荐给你”。它在新闻、社交类场景用得比较多因为用户兴趣变化快邻居用户的实时行为更能捕捉热点。ItemCF核心是“找到和你喜欢的景点相似的景点”。比如你收藏了故宫系统就会推荐颐和园、天坛这类同类景点。它在电商、长视频场景用得更多因为物品相对稳定相似关系可以离线算好。那毕设怎么做选择呢我做了一个折中方案模型主算法用ALS矩阵分解同时在代码里实现了ItemCF作为对照实验。这样有两个好处一是可以在实验章节里比较两种算法的离线评价指标丰富论文内容二是ItemCF算出来的“景点相似表”还可以用于路线定制模块比如你选择了“西安”系统把相似度最高的几个景点按距离聚类生成一条路线。如果你时间不够只做ALS也完全没问题但至少要在原理上讲清楚ALS和协同过滤的关系——ALS是协同过滤中矩阵分解这一分支的代表算法不是凭空冒出来的。3.2 ALS矩阵分解的理解方式和参数调优ALS对初学者最友好的一套解释是这样的假设有3个用户和4个景点用户-景点评分矩阵是3行4列的表格表格里有不少空格因为用户没去过那么多地方。你想填满空格但直接填不可靠于是把矩阵拆成“用户特征矩阵”和“景点特征矩阵”两个小一点的矩阵相乘尽可能还原已知评分同时预测未知评分。在Spark MLlib里调用ALS其实非常清爽我贴一段核心训练代码import org.apache.spark.ml.recommendation.ALS val als new ALS() .setMaxIter(10) .setRank(20) .setRegParam(0.1) .setUserCol(userId) .setItemCol(itemId) .setRatingCol(rating) .setColdStartStrategy(drop) val model als.fit(trainingDF) val userRecs model.recommendForAllUsers(10)这几个参数我建议你逐一想明白答辩喜欢问Rank表示每个用户、每个物品被分解成几个隐向量特征。Rank越大表示特征表达越细腻但计算量会涨还会出现过拟合。我用20到50之间调试最终取了20。MaxIter迭代次数。太小收敛不到足够精度太大会过度拟合且耗时成倍增加。10次已经能在一个可用数据集上跑出不错的结果。RegParam正则化系数防止过拟合。先取0.1如果训练集RMSE和测试集RMSE差距过大就调大一点。训练完成后你想要“相似景点”也简单。ALS模型里存了item的因子矩阵你可以提取出来两两之间算余弦相似度。类似的代码实现只需要几十行但是对于论文里的“多样性与精确性的平衡分析”非常有用。3.3 冷启动问题新用户什么都没有推荐什么这是答辩老师最爱的提问点之一我必须单独拿出来讲。ALS这类算法对新人新物是失效的因为评分为空、行为为空。如果你不处理推荐模块就会返回空列表演示一下就很尴尬。我的策略是三层兜底第一层用热门榜兜底。按最近30天访问量、好评率、搜索次数综合算一个热度分新用户登录后首页推荐就是这份热度榜。第二层用兴趣标签加权。新用户注册时可以选城市、偏好的景点类型自然风光、人文历史、亲子娱乐、美食打卡等。热度分乘以用户兴趣向量里的标签权重生成个性化程度高一些的粗推荐。第三层实时行为补全。用户进入系统后只要浏览或收藏了三五个景点立刻触发一次增量计算通过ItemCF的相似景点表把“浏览过A → 推荐相似度高的B/C/D”这个链路加进去此时用户的推荐列表就已经开始有“个性化”的味道了。只需要两三天数据积累用户有了足够的评分记录就可以切换到ALS正式推荐。这个渐进思路可以说是整篇论文里最高光的“业务亮点”写文档时一定要展开。3.4 路线定制推荐引擎怎么从“单个景点”升级成“一条路线”光推荐几个零散的景点在旅游场景里显得不够落地。真正去旅游的人要的是一天或者两天的路线于是我在推荐候选集上又加了一层路线生成逻辑就是标题里说的“定制化旅行路线智能推荐引擎”。路线生成算法我用了一种简单但有效的方式候选景点池先由ALS或ItemCF捞出来大概20到30个。通过经纬度坐标计算景点两两之间的时间距离预估交通时间膨胀系数。按景点热度分、评分、用户兴趣权重做一个综合收益分。用贪心思路从用户指定起点比如酒店所在城市出发依次加入收益高且与上一景点距离可接受的下一个景点形成一个序列。校验路线总时长比如一天游玩路线控制在10小时内超了就裁剪尾部景点。这里的核心不是追求全局最优路线规划而是体现“推荐结果具备可消费的完整性”。你可以在演示时输入“2天1夜喜欢自然风光从市区出发”前端直接渲染出一条带时间轴的地图路线播放动画给老师看展示效果会非常直观。4. 数据从哪来、怎么清洗、格式怎么定4.1 数据集来源与采集方案做推荐系统最头疼的不是算法而是数据。我在项目里用了三部分数据拼出初始数据集某旅行社区公开的游记和景点评价文本中包含用户ID、景点ID和评价内容。景区基础信息包括名称、城市、经度纬度、门票参考价、主题标签和封面图。自建平台的模拟用户行为日志用脚本随机生成浏览、收藏、评分保证系统演示时每个用户都能看到推荐内容。三部分数据量大约到了2万用户、1.5万景点、30万条评分记录。这个规模在单机上跑Spark已经是比较像样了模型输出的结果区分度明显好过几百条数据的玩具集。这些数据在引入时要做脱敏和版权处理。不要用真实用户的隐私信息用户ID可以重编号。论文附录里要写清楚数据来源和处理过程这既是对学术规范的尊重也是保护自己。4.2 评分矩阵怎么构建ALS需要的是用户ID、景点ID、评分三个字段。但原始的游记评价不是标准评分需要转换。我当时把“用户对景点产生了正面评价”映射为 4分“负面反馈”映射为 1分“收藏行为”记 3分“浏览未收藏”记 2分“下单”记 5分。这样行为日志就能天然变成一个稀疏评分矩阵。清洗逻辑我列几条都是踩过坑之后总结的过滤掉只出现过1到2次行为的用户这些用户没有足够的“口味特征”训练后容易产出噪音。过滤掉被标记为关闭或下架的景点。对同一天内同一用户的重复浏览只保留最高行为强度的那条记录。时间窗口上只用最近6个月的日志太老的数据反映的是过期兴趣。做完这些模型训练的输入就已经是干净程度很高的数据了。4.3 Spark任务和Spring Boot接口的具体配合方式“Spark算出来的结果怎么变成网页上用户看到的内容”是学弟问我最多的问题之一。最常规的做法就是使用“离线写库在线读库”具体一点说第一步单独写一个Scala的Object任务SparkApplication。任务里包含数据读取、ALS训练、结果生成三个步骤最后用JdbcRDD或foreachPartition把结果写入MySQL的推荐结果表。// 简化的Spring Boot 端接口示意 GetMapping(/api/recommend/{userId}) public ResultListScenic recommend(PathVariable Long userId) { ListScenic rec redisCache.get(rec:user: userId); if (rec ! null) { return Result.ok(rec); } ListScenic recFromDb recommendMapper.findByUserIdAndType(userId, ALS); if (recFromDb.isEmpty()) { recFromDb ruleRecommendService.hotList(); // 冷启动兜底 } redisCache.set(rec:user: userId, recFromDb, 30, TimeUnit.MINUTES); return Result.ok(recFromDb); }这段代码展示了Spark和Spring Boot之间最标准的解耦方式Spark只负责算Spring Boot只负责查。接口查不到再走兜底规则。Redis缓存可以把热点用户推荐结果压到毫秒级返回答辩时也算一个性能优化点。4.4 离线任务调度怎么安排很多同学做毕设忽略调度其实这个很好实现。我用的是最简单的Linux crontab固定每天凌晨2点执行一次Spark提交脚本0 2 * * * /opt/spark/bin/spark-submit \ --class com.example.Job.TrainRecommendJob \ --master local[4] \ --driver-memory 2g \ /path/to/spark-travel-recommend-1.0.jar如果你想把工程化程度做得更高一点可以自己写一个简单的管理接口在Spring Boot后台点击按钮手动触发任务但默认还是cron跑。毕业答辩时老师如果问“推荐结果多久更新一次”你直接说“离线任务每天凌晨跑一次全量线上CDN和Redis缓存30分钟更新一次”这个问题就通过了。5. 调试、评估和性能优化这些地方最容易翻车5.1 数据倾斜Spark任务跑到最后一个stage就是卡住我在一次训练中JOB跑到最后一个Stage进度卡在99.9%拖了40分钟没结束最后直接OutOfMemory。原因很简单某个热门城市比如某些知名旅游城市的景点评分数量是其他地区的几十倍生成用户物品矩阵时某些分区负载特别大。缓解办法训练前先用SQL按key做Aggregate对评分数量超过阈值的热门景点单独挑出来走广播变量或者在ALS训练前进行样本加权/降采样。毕设环境下最实用的还是这些做法限制每个用户的最大行为数、每个景点的最大接收评分数量让数据分布尽量均匀。5.2 ALS的结果质量差别只调模型参数第一次跑出来的推荐结果我看了想哭给用户推荐的景点全是同一个类重复度极高而且和用户历史兴趣没什么关联。后来定位到是数据预处理出了问题我把用户浏览记录和评分放在一起但没有区分行为权重导致每个景点得分几乎只由访问量决定用户本身的口味被稀释了。正确做法按我前面说的行为映射规则比如收藏权重是3、下单是5、浏览是2、负面反馈跳过不进入训练。再用Log变换处理一下累计值效果会有非常明显的提升。如果调整完还是差建议先去检查数据而不是纠结Rank是20还是30。5.3 推荐效果到底怎么评价答辩老师必问你说你的推荐效果好怎么证明所以离线评估必须做。将清洗后的评分矩阵切分训练集和测试集比例我用8:2保持每个用户至少有一条测试数据。训练后用测试数据去预测计算RMSE均方根误差和MAE平均绝对误差。这两个指标越小说明预测评分越准。除了评分误差我还会统计Top10推荐命中率在测试集中用户真正访问过/评过分的景点是否出现在推荐列表中如果出现就算命中。这个方法有一个不正式但很有效的通俗版本每次演示前挑一个有历史访问记录的用户账号展示推荐列表里有多少个景点是他确实去过且打过分。肉眼可见的正确性比任何指标都有冲击力。5.4 前端可视化怎么支撑演示不要小看前端页面推荐系统的门槛很容易被可视化拉高或者拉低。我强烈推荐你在首页放三个模块猜你喜欢基于ALS的用户个性化Top10推荐。热门景区榜30天热度排序榜用ECharts画一个柱状图加地图分布。探索路线输入出发点和时间后生成“一日游路线”和“两日一夜路线”在地图上画出连线。地图展示我用的方案是引入第三方地图组件或者自己用坐标点填充到前端图表里。不要炫技也不要停留在纯文字列表。老师看到地图上一条完整的路线被动态画出来一般就开始打心里给高分了。6. 常见问题速查表把时间花在刀刃上以下这些问题是我在做完整个项目后总结出来的最高频故障和答辩提问点你可以做成速查表放在笔记本旁边症状可能原因排查方向 / 解决方案spark-submit后任务秒挂Scala版本和Spark不兼容统一使用Spark官方默认Scala版本检查依赖包ALS训练完推荐为空冷启动策略没写添加热心榜、标签加权和ItemCF增量逻辑任务卡住不结束数据倾斜严重限制用户/景点行为上限处理后重新提交接口返回很慢每次请求都在实时算推荐改成离线写库、在线读库加Redis缓存前端图片不展示图片来源跨域或字段为空统一用本地或对象存储图片链接检查数据完整性论文里没有实验对比只用了一个算法至少补一个ItemCF对照或调参对照实验推荐结果全都是热门城市用户特征弱矩阵没有学到有效向量增加行为权重区分清洗垃圾用户提高Rank观察答辩问“为什么不用Hadoop”没有说清选型逻辑强调Spark内存迭代优势、MLlib算法库、开发效率答辩问“成本和扩展性”没有集群环境做测试诚实回答Standalone模式验证代码可以直接扩展到Yarn集群答辩问“这个系统的难点在哪”难点没被识别把冷启动、在线离线架构、路线拼接算法作为三大难点讲除了排错这里再给你两个心得。心得一先定数据再定算法。我曾经先写好了Spark训练代码再去网上找数据结果数据格式完全不匹配评分字段乱七八糟清洗花了一周。如果你做这个项目第一天就把数据下载好、结构理清楚、脚本写好后面会非常顺。心得二论文的代码量不决定分数实验和思考才决定。我见过有人贴了2000行代码但老师问什么都答不出所以然也见过只写300行核心算法但把每个参数都讲清楚的同学拿高分。推荐系统的重点在于你做了哪些实验、如何评估结果、如何解释现象而不在于拼装了多少个页面。我个人做下来最大的体感是这种题目把自己的能力边界撑大了一圈。你既要懂Java能写Spring Boot接口又要懂Spark能调分布式任务还要懂一点前端和数据分析能讲清楚数据如何变成商业价值。做完之后简历上有东西可写面试里有内容可聊。如果你当前正在这个题目上挣扎我劝你别急着写代码先按我这条数据链路把每一个模块的输入输出理清楚再动手敲。你会发现后面每一步都顺很多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑