尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Hadoop+Spark+Hive酒店推荐系统实战:从爬虫到大屏毕设全流程解析

Hadoop+Spark+Hive酒店推荐系统实战:从爬虫到大屏毕设全流程解析 做毕业设计选大数据方向的题目很多人一上来就盯着Hadoop、Spark这些框架名觉得名字越响亮越好。但真到动手阶段才发现集群搭起来容易数据进了HDFS之后该干嘛、推荐结果怎么算出来、可视化大屏怎么填每一步都是坑。今天拿一套完整做下来的“HadoopSparkHive酒店推荐系统”当例子把从零到一的全过程拆开讲清楚包括数据怎么爬、仓库怎么建、推荐算法怎么落地、大屏怎么做以及最后文档和答辩怎么整理。这套项目无论是自己当毕设还是给学弟学妹做参考都有直接能抄作业的价值。1. 项目整体设计与技术选型思路1.1 为什么是HadoopSparkHive这套组合很多同学问我做个酒店推荐系统单机用Python跑个协同过滤不就行了吗为什么非要上Hadoop全家桶这个问题问到点子上了。先说清楚毕设选题的本质不是为了用技术而用技术而是要在有限时间内向评委证明你理解大数据生态里每个组件的定位并且能把它们串联起来解决一个实际问题。这个项目选型我建议所有准备做大数据方向的人都认真看看它是目前最适合本科毕设的黄金组合组件在项目中的角色解决的核心问题Python爬虫数据入口从公开旅游网站抓取酒店信息、价格、评分、评论Hadoop HDFS分布式存储把爬到的原始数据按日期/来源分目录存放撑起“大数据”的门面Hive数据仓库做离线清洗、转换、统计分析生成用户行为表和酒店特征表Spark计算引擎加载Hive分析结果跑ALS协同过滤推荐算法产出最终推荐列表ECharts可视化把统计指标和推荐结果放到大屏上形成直观汇报这一套下来三个框架全用上了而且还不是那种“装完环境跑个wordcount就完事”的假把式每个组件都承担了具体的业务责任答辩的时候逻辑非常顺。1.2 推荐系统的主体流程拆解推荐系统本身不是新鲜东西但落到酒店这个垂直场景里有自己的特殊性。你要推荐的不是电影、商品这种标准品酒店有明确的地域属性和价格区间用户决策时“地理位置”往往比“物品相似度”权重更高。我设计的整体数据流向是这样的爬虫采集Python→ HDFS原始数据 → Hive清洗/ETL → Spark MLlib ALS训练 → 生成TopN推荐列表 → MySQL存储 → Spring Boot接口 → ECharts大屏展示这里有一个容易被忽略的细节Hive负责做繁重的SQL统计和预处理而Spark只负责跑算法模型。为什么这么切因为Hive对数据分析师和学生最友好写SQL就能完成大部分ETL工作不必每个步骤都用Java或Scala写Spark代码。而ALS协同过滤是Spark MLlib库里的成熟算法Java和Scala接口都有写起来不复杂还能在答辩时把“机器学习”这个亮点打出来。1.3 为什么推荐系统要选协同过滤酒店推荐有三种主流做法基于内容的推荐Content-based、协同过滤Collaborative Filtering、混合推荐。我选协同过滤有两个核心原因。第一数据很好造。基于内容推荐需要酒店详尽的属性标签设施、风格、周边环境等爬虫能爬到但很零散。协同过滤只需要“用户对酒店的评分或行为”这一张表就行爬评论和评分就能搞定。第二算法成熟有现成库。Spark MLlib里直接封装了ALS交替最小二乘法几十行代码就能训练出一个模型不需要自己造轮子。ALS的原理三句话能讲清楚把用户和酒店映射到同一个隐因子空间里用矩阵分解近似填充缺失的评分再用余弦相似度或直接预测评分为用户生成推荐列表。这套逻辑只要是学过机器学习的老师都能听懂深度刚刚好。2. 数据层搭建爬虫采集与Hive数据仓库设计2.1 酒店爬虫的采集策略与反爬应对爬虫是整个项目的地基数据质量直接决定推荐效果。我用的技术栈是Python 3.8 requests BeautifulSoup目标站点是国内几家大型OTA平台公开的酒店列表页和详情页。采集字段包括酒店名称、所在城市、商圈、星级、最低价格、评分、评论数、经度纬度、用户评论内容和评分。经纬度这个字段很重要做可视化的时候可以用来做地图散点图推荐的时候也可以抽出来算地理距离。爬虫部分有一个非常关键的取舍不要爬全站要爬“热门城市的头部酒店”。一个城市的酒店数量可能上千家但你只需要每个城市前200~500家热门酒店。原因很简单长尾酒店的用户评分数据太稀疏ALS在这种数据上学不出好的隐因子向量还白白增加HDFS存储压力。另外推荐系统的演示重点是“用户→酒店”的映射关系数据规模控制在1万到3万条评论、2000家酒店左右单机跑完全没压力。反爬方面实测最有效的是三招随机User-Agent池每次请求换一个浏览器标识。请求间隔随机化2到5秒之间随机sleep绝不能固定频率。解析失败重试机制连续失败5次就放弃当前页面记录日志跳过。爬下来的数据我按城市分目录存到HDFS上路径格式是/user/hadoop/hotel_data/raw/city北京/20240601.json。这里做了Hive分区表的前置目录设计后面加载数据直接就能映射分区非常干净。2.2 Hive建表与ETL清洗Hive建表是这个项目中性价比最高的技术点建好了后面所有分析都顺建不好后面Spark加载数据全是坑。我建了三张核心表分别是原始数据表、用户行为表、酒店特征表-- 原始数据表外部表location指向HDFS爬虫数据目录 CREATE EXTERNAL TABLE ods_hotel_review ( hotel_id STRING, hotel_name STRING, city STRING, price DOUBLE, rating DOUBLE, comment_count INT, user_id STRING, user_rating DOUBLE, comment_time STRING, content STRING ) PARTITIONED BY (dt STRING) ROW FORMAT SERDE org.openx.data.jsonserde.JsonSerDe STORED AS TEXTFILE LOCATION /user/hadoop/hotel_data/raw;这里用了一个外部插件JsonSerDe是处理JSON日志的利器。默认的Hive表只能读分隔符文本JSON数据必须靠这个SerDe才能直接映射字段。把这个jar包放到Hive的auxlib目录下建表语句加一行ROW FORMAT SERDE就搞定。清洗环节我发现最花时间的是“用户评分单位不统一”的问题。有的站点评分是10分制有的是5分制还有用“4.5/5”这样的文本格式。处理方式是在Hive里建视图统一转成10分制-- 清洗统一评分到10分制 CREATE VIEW dwd_user_rating AS SELECT user_id, hotel_id, CASE WHEN user_rating 5 THEN user_rating ELSE user_rating * 2 END AS rating_10, dt FROM ods_hotel_review WHERE user_id IS NOT NULL AND hotel_id IS NOT NULL;这个视图非常关键后面的ALS模型训练直接读它。数据质量规则也很简单user_id和hotel_id为空的数据丢掉、评分为0或超过10的丢掉、价格小于50的明显异常值过滤掉。这一套清洗下来数据量通常能保留85%以上足够撑起后面的模型训练。2.3 基于Hive的统计分析与指标产出Hive除了做清洗还有一个任务是为可视化大屏提供统计指标。大屏上展示的内容不能是爬虫原始数据的搬运必须是有业务含义的“洞察”。我写了几条典型的分析SQL在这儿挑两条最有代表性的-- 每个城市的酒店均价与最高评分 SELECT city, ROUND(AVG(price), 2) AS avg_price, MAX(rating) AS highest_rating, COUNT(DISTINCT hotel_id) AS hotel_cnt FROM dwd_hotel_feature GROUP BY city ORDER BY hotel_cnt DESC;-- 各评分段的酒店数量分布 SELECT CASE WHEN rating 9 THEN 9-10分 WHEN rating 8 THEN 8-9分 WHEN rating 7 THEN 7-8分 ELSE 7分以下 END AS rating_segment, COUNT(*) AS cnt FROM dwd_hotel_feature GROUP BY CASE WHEN rating 9 THEN 9-10分 WHEN rating 8 THEN 8-9分 WHEN rating 7 THEN 7-8分 ELSE 7分以下 END;统计结果用INSERT OVERWRITE落到MySQL里给后端接口查询或者直接以CSV格式导出给可视化组件读。Hive在这个项目里的地位一句话总结就是“数据中台”。所有脏活累活都在这儿干完Spark拿到的是一份干净、规范、可直接训练模型的中间结果。3. 推荐系统核心ALS算法与Spark落地3.1 用Hive分析结果构建用户-酒店评分矩阵推荐系统训练前要把数据整理成ALS算法需要的格式。MLlib里的ALS接受三种RDD输入格式最常用的是Rating(userId, productId, rating)。问题来了爬虫拿到的user_id是字符串很多是脱敏ID或者用户名ALS要求的是数值型ID。所以第一步必须做ID映射用StringIndexer把字符串ID转成连续的Long型ID同时保存一份映射表备用。这一步非常容易踩坑有个同学就是忘了做映射直接拿字符串去训练跑的时候报一堆类型错误。数据准备好了按8:2划分训练集和测试集。划分的时候要注意不能随机切分要按用户维度切分——同一个用户的数据要么全在训练集要么全在测试集否则会出现数据泄露评估指标虚高。Spark读取Hive分析结果的方式有两种如果是Spark 2.x以上的版本直接用spark.read.table(dwd_user_rating)就能读到Hive表数据如果存在兼容性问题可以走JDBC从MySQL读。我实测下来前者最顺畅前提是Spark编译时带上了Hive支持。3.2 ALS模型训练与参数调优ALS模型训练的核心代码用Scala写因为Spark原生API是Scala的跑起来最省心。核心代码只有二十多行import org.apache.spark.ml.recommendation.ALS val als new ALS() .setMaxIter(10) // 迭代次数 .setRank(10) // 隐因子个数 .setRegParam(0.1) // 正则化参数 .setUserCol(userIdInt) .setItemCol(hotelIdInt) .setRatingCol(rating) .setColdStartStrategy(drop) // 冷启动策略 val model als.fit(trainingData) val predictions model.transform(testData) // 评估RMSE val evaluator new RegressionEvaluator() .setMetricName(rmse) .setLabelCol(rating) .setPredictionCol(prediction) val rmse evaluator.evaluate(predictions) println(sRoot-mean-square error $rmse)这三个参数是调优重点我挨个说maxIter最大迭代次数ALS是迭代优化算法次数太少欠拟合太多过拟合且白白增加耗时。酒店数据量不大10次足够收敛我试过20次RMSE几乎没变化训练时间翻了倍。rank隐因子个数这个参数最玄学控制模型的表达能力和泛化能力。太小模型欠拟合太大模型过拟合还吃内存。经验值是5到30之间我用网格搜索试过5、10、15、2010的效果最好。regParam正则化系数防止过拟合的关键。数据稀疏就调大一点密集就调小。默认0.1在大多数数据集上都算安全起点。冷启动策略建议直接设成drop这个参数的意思是测试集中如果出现训练时没见过的新用户或新酒店不参与评分预测直接忽略。如果不设置默认返回NaN评估器直接报错。这个坑至少有一半第一次跑ALS的同学都会踩到。3.3 生成TopN推荐列表的完整流程模型训练完最后一步是给每个用户生成TopN推荐列表。ALS模型提供recommendForAllUsers方法一行代码就能搞定。但要注意这个方法的输出格式是(userId, Array[(hotelId, rating)])不能直接拿给Web后端用需要做一步展平和映射import org.apache.spark.sql.functions._ val recommendations model.recommendForAllUsers(10) // 转成可导出的扁平结构 val recDF recommendations .select( col(userIdInt), explode(col(recommendations)).as(rec) ) .select( col(userIdInt), col(rec).getField(hotelIdInt).as(hotelIdInt), col(rec).getField(rating).as(predictedRating) )导出的推荐结果我统一写回MySQL建一张recommend_result表字段是user_id、hotel_id、predicted_rating、rank。后端接口按user_id查询返回前10条再join酒店信息表把酒店名称、价格、图片补齐前端就能渲染了。写回MySQL时有个常用技巧用org.apache.spark.sql.execution.datasources.jdbc.JdbcRelationProvider配合df.write.mode(overwrite).jdbc(url, recommend_result, props)。注意要先DROP TABLE IF EXISTS再写入否则表结构更新不了。3.4 Spark集群资源与内存调优这节是经验之谈。很多人在本地IDE里跑Spark没任何问题一上集群就各种Executer lost、OOM。酒店推荐这个项目的数据量并不大但该调的参数一个都不能少。提交任务时我用的参数组合是spark-submit \ --class com.bigdata.hotel.RecommendationRunner \ --master yarn \ --deploy-mode client \ --executor-memory 2g \ --driver-memory 2g \ --executor-cores 2 \ --num-executors 3 \ hotel-recommendation.jar几个关键参数的经验executor-memory不要贪多2g到4g足够。曾见过有人给每个executor分配8g结果YARN资源不够任务排队等到天荒地老。driver-memory在client模式下管的是本地提交进程也有内存上限推荐结果collect回driver时如果太大也会OOM。executor-cores设1到2就好ALS这类计算密集型任务核心数多了反而不一定线性加速。如果报Container killed by YARN for exceeding memory limits说明executor内存超限把spark.memory.offHeap.enabled设为true或者调小spark.memory.fraction给用户代码留更多空间。4. 可视化大屏与后端接口设计4.1 大屏布局与指标体系设计可视化大屏是整个项目里最能“出片”的部分也是答辩时评委停留时间最长的地方。我也不建议搞那种超炫酷的3D大屏重点是有数据洞察、逻辑合理。我设计的12屏布局分四层区域图表类型展示指标顶部标题实时时间项目名称、数据更新时间左列柱状图、折线图各城市酒店均价、评分趋势中部中国地图散点各城市酒店数量及价格分布右列饼图、环形图评分分布、价格档次占比底部推荐列表当前选中用户的Top10推荐酒店这里有一个很容易被忽视的点所有图表展示的数据必须都能在Hive的分析SQL里找到对应的计算逻辑。评委看到大屏上的任何数字问一句“这个数怎么来的”你要能现场打开Hive命令窗口跑一条SQL把数字复现出来。能做到这一点答辩基本稳了。4.2 后端接口与ECharts渲染的数据流后端我用Spring Boot搭一个轻量服务主要负责从MySQL取数、封装成JSON接口给前端。接口数量和命名尽量简单清晰一共五个接口就够了/api/overview核心指标总览酒店总数、用户数、评论数、平均评分/api/cityAnalysis城市维度的均价和酒店数量/api/ratingDist评分区间分布/api/priceDist价格区间分布/api/recommend?userIdxxx指定用户的TopN推荐列表ECharts是目前用的最多的可视化组件配置起来非常简单。前端页面用HTMLCSS原生JavaScript通过fetch调接口拿数据再设置ECharts的option。以城市均价柱状图为例fetch(/api/cityAnalysis) .then(res res.json()) .then(data { const chart echarts.init(document.getElementById(cityChart)); chart.setOption({ title: { text: 各城市酒店均价 }, tooltip: {}, xAxis: { data: data.map(d d.city), axisLabel: { rotate: 30 } }, yAxis: { name: 均价(元) }, series: [{ type: bar, data: data.map(d d.avgPrice), itemStyle: { color: #2b7ecb } }] }); });之所以用原生JS而不上Vue、React是因为毕设场景下简单可靠压倒一切。原生页面打包出来就是一个静态文件夹Spring Boot里配置一下静态资源映射就好不引入额外复杂度。4.3 MapReduce思想在统计中的应用细节这是我在做城市分析时的一个扩展思考。虽然最终用Hive跑出了统计结果但在写Hive SQL的时候脑子里要清楚每条语句底层对应的MapReduce过程是什么。以“各城市酒店均价”为例Map阶段按城市分组输出(city, price)键值对Shuffle阶段把相同city的数据路由到同一个ReducerReduce阶段累加该城市所有价格除以酒店数得到均价为什么强调这个因为答辩有高频问题“你用了Hive那你知道一条SQL是怎么变成MapReduce任务跑起来的吗”提前把这个思路理清楚比临场支支吾吾要好得多。5. 项目工程化整理与答辩准备5.1 源码、文档、PPT的完整目录结构毕设不只是代码交付物更重要。我建议整个项目打包成一个结构清晰的工程目录万一老师要查源码扫一眼就能看出你的工程能力hotel-recommendation/ ├── docs/ │ ├── 开题报告.docx │ ├── 毕业论文.docx │ ├── 答辩PPT.pptx │ └── 需求规格说明书.docx ├── crawler/ │ ├── spider.py │ ├── proxy.py │ └── requirements.txt ├── hive/ │ ├── create_table.sql │ ├── etl_clean.sql │ └── analysis_statistics.sql ├── spark/ │ ├── src/main/scala/com/bigdata/hotel/ │ │ ├── RecommendationRunner.scala │ │ └── DataFrameUtils.scala │ └── pom.xml ├── backend/ │ ├── src/main/java/com/bigdata/hotel/ │ │ ├── controller/ │ │ ├── service/ │ │ └── mapper/ │ └── application.yml ├── frontend/ │ ├── index.html │ ├── css/ │ └── js/ └── README.md每份文档里都要包含环境要求JDK版本、Hadoop版本、Spark版本、启动步骤从爬虫→Hive→Spark→后端→前端、核心代码说明。README这么写老师导入项目后照着走一遍能跑通这就是最好的第一印象。5.2 答辩前必须准备好的八个问题这八个问题是我结合多场答辩总结出来的高频提问每个都整理过标准回答为什么用Hive不用纯Spark SQL——Hive适合离线的复杂SQL分析Spark负责算法训练各司其职。ALS算法原理是什么——矩阵分解用隐因子向量拟合评分矩阵最小化平方误差。数据量不大用大数据框架是不是性能更差——强调这是学习型和工程实践型项目重点是掌握分布式处理思想。推荐结果的评价指标是什么——RMSE、PrecisionK展示测试集上的具体数值。数据倾斜怎么解决——Hive层面加盐、Spark层面重分区给具体案例。爬虫的合法性——只爬公开数据控制频率不涉及个人隐私仅用于学习研究。项目运行需要多少资源——单机伪分布式即可3个虚拟机更好内存16G起步。这套架构换成其他领域怎么迁移——把酒店表换成商品表、用户行为表结构不变协同过滤不依赖具体业务内容。5.3 开发周期规划与里程碑毕设最怕的是时间管理失控我按8周给一个可落地的排期阶段时间核心交付物环境准备第1周Hadoop/Spark/Hive伪分布式环境跑通数据采集第2周爬虫稳定运行数据落HDFS数据仓库第3周Hive表建好ETL完成统计SQL跑通推荐算法第4-5周ALS模型训练完成推荐接口调通可视化第6周大屏页面完成数据联动文档撰写第7周论文初稿完成联调与答辩第8周全流程演示PPT准备前两周最容易拖延建议用Docker快速搭环境搜一个装好Hadoop和Spark的镜像省去大量配置时间。等跑通了核心流程再回头补一遍手动安装既快又稳。6. 踩坑实录与排查技巧6.1 环境与集群常见故障速查实操中遇到的问题五花八门我选五个最典型的记在这现象根本原因快速解决Hive查询报ClassNotFoundException: JsonSerDe缺少JsonSerDe依赖jar包下载jar放入$HIVE_HOME/auxlibSpark执行报ExecutorLostFailureExecutor内存不足被YARN杀调大executor-memory减少并行度ALS训练报requirement failed: Nothing to recommend测试集有训练集没见过的ID设置coldStartStrategy为dropHive查询结果出现大量NULLJSON字段名映射错误检查JsonSerDe字段名大小写和下划线匹配前端跨域访问接口失败Spring Boot未配置CORS加CrossOrigin注解或全局跨域配置这些错误几乎每位同学都会碰到至少一个。排查思路有三板斧第一看日志在哪个组件报的错YARN日志查yarn logs -applicationId第二把SQL拆小一条条跑定位问题SQL第三用hdfs dfs -cat直接看原始数据确认HDFS上数据没问题再怀疑上层。6.2 数据倾斜与性能优化经验虽然酒店这个项目数据量不大但数据倾斜问题在答辩时很容易被问到。所谓数据倾斜就是某个key的数据量特别大导致一个Reduce任务处理海量数据其他Reduce任务早早空闲拖慢整个作业。最经典的一个倾斜场景是“城市维度统计”。北京、上海这些热门城市数据量远大于其他城市如果直接按城市GROUP BY北京所在的Reduce就会成为瓶颈。解决方案是在Hive里加盐Salting把热门城市拆散成多个随机后缀先做局部汇总再合并SELECT city, SUM(price) AS total_price FROM ( SELECT city, CONCAT(city, _, FLOOR(RAND() * 10)) AS salted_city, price FROM dwd_hotel_feature ) t GROUP BY salted_city;两层GROUP BY的方式实测可以消除单个Reduce的瓶颈大促分析、日志统计场景里尤其好用。这个技巧写进论文的“性能优化”章节是很加分的深度体现。6.3 推荐效果不佳的排查方向很多同学把模型训练出来就觉得万事大吉直到演示的时候发现推荐出来的酒店跟用户历史行为完全对不上。我从实操中总结了三个排查方向第一检查评分矩阵稀疏度。如果每个用户平均只有1到2条评分记录ALS很难学出有效的用户向量。常见解法是降低过滤阈值把评论数少于5条的用户直接剔除后再训练。第二检查ID映射是否错乱。StringIndexer的映射结果和推荐结果的ID必须用同一套映射表还原否则展示出来的酒店名全是乱码。第三检查冷启动策略是否误伤。coldStartStrategy设成drop会把新用户的推荐结果全部丢掉如果测试集里新用户占比高评估指标看起来就会很惨。解决办法是给新用户回退成热度推荐——按评分排序取TopN作为冷启动兜底。6.4 文档撰写中的图表技巧与Word排版毕设论文里图表的重要性不亚于文字。一个论文截图甚至比一千字的描述更能让评委理解你的系统。我常用三张图系统架构图画清楚数据从爬虫到HDFS到Hive到Spark到MySQL到前端大屏的完整链路。推荐算法流程图用方框箭头画出训练集、测试集、ALS模型、TopN推荐的过程。大屏效果截图裁剪干净的大屏成品图要保证分辨率清晰、不要带浏览器边框。论文排版有几条实用经验正文字号小四、行距1.5倍是通用配置所有图表要有编号和图题图题在图片下方表题在表格上方代码用等宽字体字号调小一号加浅色背景框页码、目录自动生成答辩前再全局检查一遍断行和错别字。7. 后续扩展思路这套酒店推荐系统做完成型后方向稍微改一改就能演变成其他题目。比如把酒店换成电影、书籍、课程协同过滤算法完全不用动换数据就行。想加深难度可以往两个方向扩展。一个是引入实时推荐。用Spark Streaming消费Kafka里的用户实时行为数据结合离线训练的ALS模型做“实时热门个性化”的混合推荐。这会把项目的技术层次拉高一个档位但工作量也翻倍适合时间充裕的同学。另一个是增加基于内容的推荐。协同过滤有冷启动问题新酒店没有任何用户评分时无法被推荐。可以爬取酒店的描述文本用TF-IDF或者Word2Vec把酒店转成向量计算内容相似度做补充召回。两个召回源的分数做加权融合推荐效果和论文深度都会明显提升。我在实际把玩这套系统的过程中最大的体会是大数据项目的核心不在于你会调多少个框架参数而在于把“数据→信息→价值”这条路走通。Hadoop、Spark、Hive都是手段最终能从一个原始数据集里挖掘出可解释、可落地的规律让评委看到这个系统不是玩具才是毕设真正的得分点。最后分享一个小细节——把全流程跑通的那一条命令、那一条SQL存成一个专门的备忘文件答辩演示时照着敲就行别在台上临时翻笔记找命令从容的状态比背下所有参数更值钱。
返回列表