Hadoop+Spark+Kafka+Hive民宿推荐系统全链路开发实战 1. 这个毕设到底要做什么民宿推荐系统的完整拼图如果你正在为一台 8G 内存的笔记本该选什么毕设题目发愁同时又不想做烂大街的 XX 管理系统我强烈建议你看看这个组合——Hadoop Spark Kafka Hive配上民宿爬虫与可视化。我当时选这个题目的理由很简单技术栈足够新能讲的故事足够多而且数据源自采自用不依赖任何现成数据集论文里数据采集与预处理那一章天生就有料。先把这个题目的内核捋清楚。很多同学拿到类似题目第一反应是我要做一个网站网站里能看到民宿列表和推荐结果。这个理解大方向对但容易跑偏。毕设评委想看到的不是我搭了一个民宿搜索页面而是我用一套大数据处理链路解决了一个真实场景下的推荐问题。所以核心不是前端多好看而是数据从产生、传输、存储、计算到展示的完整闭环。这个系统的数据流大概是这样的爬虫从民宿平台上抓取公开的房源信息包括名称、价格、评分、标签、经纬度、评论数等抓到的数据经过清洗后进入消息队列 Kafka再由消费者写入 Hive 数据仓库Spark 从 Hive 中读取数据完成离线分析和推荐模型计算计算结果落回 MySQL最终由可视化页面调用接口展示。整个链路中Kafka 负责缓冲和解耦Hive 负责大规模数据的管理与查询Spark 负责真正的计算Hadoop 的 HDFS 则作为底层存储底座。那推荐两个字怎么体现通常有两种完全不同的做法。一种是把用户浏览、收藏、评分等行为数据收集起来做成协同过滤模型给每个用户生成个性化 TopN 推荐另一种是走统计分析路线针对热门城市、价格区间、评分偏好做筛选排序。毕设阶段我建议两者结合先把统计分析型推荐做扎实再叠加一个 ALS 协同过滤模型做个性化召回。这样论文里的实验章节既有规则又有算法还能源源不断产出可视化图表。2. 技术栈为什么长这样Hadoop、Spark、Kafka、Hive的分工与合作2.1 四件套各管哪一段很多同学一开始最迷糊的就是这四样东西分别干嘛感觉都是大数据框架好像换个名字也差不多。这是毕设答辩时最容易露怯的点。我用自己的话给你捋一遍Hadoop 的核心是 HDFS 和 YARN。HDFS 管的是文件存储爬虫抓到一百万条民宿数据最终要以文件形式躺在 HDFS 上YARN 管的是资源调度Spark 跑任务时要申请 CPU 和内存就是找 YARN 要。Hive 本身不存数据它只是把 SQL 翻译成 MapReduce 或 Spark 任务让你能用熟悉的 SQL 去查 HDFS 上的文件。你执行一句SELECT count(*) FROM room_infoHive 背后要做的事情远比你想象的多得多。Spark 是真正的计算引擎。它从 Hive 表里读数据跑推荐算法、做统计聚合、生成用户画像。如果你非要用 MapReduce 去实现 ALS 协同过滤你大概率会写到怀疑人生而 Spark 原生提供 MLlib 库。Kafka 是消息管道。爬虫每抓完一批数据推送到 Kafka 的 topic 里下游消费者再从 topic 拉取处理。这样爬虫和落库之间就没有强耦合爬虫想快点抓就快点抓消费者有自己的节奏慢慢写两边互不拖累。它们的关系可以类比成一条餐饮流水线爬虫是采购员买回来的菜数据先放在传送带Kafka上HDFS 是冷藏仓库Hive 是仓库管理员负责登记和管理货架Spark 是厨房里的大厨把原料做成成品菜最后可视化页面就是把菜端上桌给客人看。这个类比我每次答辩前都会在脑子里过一遍效果很好。2.2 为什么不用 Flume、Flink 或 MongoDB这个问题基本是答辩必问提前想清楚能省很多麻烦。Flume 是一种日志采集工具擅长监听日志文件变化把日志持续搬进 HDFS。但我们的数据源是网页爬虫不是服务器日志采集节奏由自己控制用 Flume 反而要额外适配不如直接在爬虫里调用 Kafka Producer 来得干净。Flink 的实时计算能力确实比 Spark Streaming 亮眼但毕设项目的数据量通常并不需要毫秒级响应而且 Flink 的部署和调优成本比 Spark 高不少。我们的推荐场景本质上是 T1 离线计算Spark 的批处理能力绰绰有余。选了 Flink你不仅要多处理一套状态管理、窗口机制论文里还得解释为什么民宿推荐需要实时性这个因果关系很难编圆。MongoDB 做存储非常灵活文档结构很适合房源这种字段不固定、经常变动的数据。但问题是你的数据分析主战场在 Hive数据最终要进入数仓体系做多维聚合。与其在 MongoDB 里绕一圈再导进 Hive不如爬虫落地后直接走 MySQL Hive 的双写路径减少中间环节。2.3 版本搭配与资源评估版本问题看起来很土但真能坑掉一周时间。我的建议是一组经过大量同学实测、互相兼容的组合组件推荐版本备注Hadoop3.1.3不要用 2.x配置写法差异较大资料也老Spark3.1.2预编译版选 hadoop3.2 那个包Hive3.1.2与 Hadoop 3.1.x 兼容性良好Kafka2.8.0内置 zookeeper 协调足够用ZooKeeper3.6.3Kafka 启动前必须先起它MySQL5.7 或 8.0放爬虫原始数据与最终结果如果你的笔记本只有 8G 内存伪分布式模式下 HDFS、YARN、Hive、Kafka、Spark 全开会比较吃力我后面专门开了一节讲降级方案。如果学校有服务器或云资源建议至少 16G 内存起步4 核 CPU。3. 爬虫落地民宿数据怎么采、怎么存、怎么防封3.1 技术路线选择requests 还是 selenium民宿数据的采集是整个系统的基础你看相关搜索词里大家最关心requests爬虫python selenium反爬虫说明这块确实容易踩坑。我做的时候总结出一条判断标准如果目标页面是服务端渲染直接requests BeautifulSoup 就够了如果页面内容靠 JavaScript 动态加载必须上selenium或 Playwright。我当时先分析目标网站发现列表页的数据在 HTML 源码里就能拿到于是直接用 requests。但详情页的点评数据和部分标签是异步加载的后来切到了 selenium 驱动浏览器渲染虽然慢一点但稳定。这里的经验是不要一上来就无脑上 selenium它的资源消耗大并发效率低爬一万条数据的速度能慢到你怀疑人生。优先用 requests只有遇到动态加载再加 selenium。3.2 字段设计为后续分析和可视化留好余地爬虫能采集的字段远比想象的多但并不是所有字段都要。我当时设计的主表结构大致是这样的class RoomInfo(Base): __tablename__ room_info id Column(Integer, primary_keyTrue, autoincrementTrue) room_id Column(String(64), uniqueTrue) # 平台房源唯一ID title Column(String(255)) # 房源标题 city Column(String(64)) # 城市 district Column(String(64)) # 行政区 address Column(String(255)) # 详细地址 longitude Column(Float) # 经度 latitude Column(Float) # 纬度 price Column(Float) # 每晚价格 score Column(Float) # 综合评分 comment_count Column(Integer) # 评论数 tags Column(String(500)) # 标签逗号分割 landlord_name Column(String(64)) # 房东昵称 landlord_level Column(String(32)) # 房东等级 created_at Column(DateTime) # 采集时间有几个字段要特别留意。经纬度必须单独存因为可视化部分要做地图散点如果只有文本地址图表很难画评论数、评分、价格是推荐排序的核心特征后面 Spark 全都要用到标签字段做成逗号分隔的字符串方便后续用 Hive 的split()函数拆开分析。3.3 反爬对抗的几条经验爬虫写得再好被对方封了就全白搭。我梳理了几个真正有效的反爬应对手段。第一是伪造请求头。UA 池至少准备 20 个以上真实浏览器的 UA随机取用。光这一点就能过滤掉大部分基础封禁。第二是控制访问频率。不要用固定延时随机延时 2-5 秒更接近人类行为。第三是 IP 代理池。如果爬的数据量大目标网站很可能按 IP 限流可以买一些短效代理自动切换。第四是 Cookie 处理。部分页面登录前后内容差异很大可以先手动登录一次拿到 Cookie在请求时带上。我实际踩过的一个坑是某平台把民宿价格做了动态混淆直接在 HTML 里看到的价格和真实价格差了 30%后来发现价格藏在某个 JavaScript 变量里需要正则提取加解密。这种问题只能逐个分析没有捷径。3.4 为什么用 SQLAlchemy 存 MySQL 而不是直接进 HDFS参考热搜词里sqlalchemy储存爬虫数据热度很高说明很多同学都遇到过这个疑问。我的选择是爬虫数据先入 MySQL再由 Spark 或通过 Sqoop 批量导入 Hive。原因有三。第一爬虫需要增量刷新和去重比对比如今天爬到了一条房源明天再爬到同一条要判断价格有没有变化MySQL 里按room_id做唯一索引很容易实现。第二直接写 HDFS 虽然也能存但文件格式和目录结构需要自己管理而 MySQL 作为中转区对后续清洗步骤更友好。第三可视化阶段推荐的快速查询表放在 MySQL 里爬虫数据经过处理后本来也会回到 MySQL这样整个存储层的逻辑很统一。SQLAlchemy 的 ORM 用起来也不复杂定义好 Model 后爬虫每抓一条数据就session.add(record)批量提交就能满足需求。4. Kafka实时管道数据如何从爬虫端流到分析引擎4.1 Kafka在系统里到底承担什么角色不夸张地说Kafka 是这套系统里画龙点睛的一笔。很多同学的交作业版本是爬虫 → 直接写MySQL → Spark 读 MySQL这个链路能通但没法在答辩时体现你对分布式架构的理解。把 Kafka 插进去整个系统的解耦性和抗压能力就讲得通也符合大数据系统的标准设计范式。爬虫采集速度是不稳定的白天快、晚上慢遇到反爬屏蔽可能短时间没数据。如果消费者和生产者直接对接一端卡顿会影响另一端。有了 Kafka 缓冲层爬虫只管往 topic 里推数据没人管消费者什么时候取消费者即使临时抽风消息也还在 Kafka 里存着恢复后继续消费数据不丢。另外集群安装kafka可视化工具这些热词说明大家在部署和使用上都遇到过问题。Kafka 本身不像 MySQL 那样有直观的管理界面排查积压或查看 offset 主要靠命令行工具kafka-consumer-groups.sh。我建议把 Kafka 的 topic 下所有 partition 的 LAG积压量打印出来做监控这是判断链路健康度最直接的手段。4.2 Producer 与 Consumer 的最简可运行代码爬虫端发送消息的代码非常直白from kafka import KafkaProducer import json producer KafkaProducer( bootstrap_serverslocalhost:9092, value_serializerlambda v: json.dumps(v, ensure_asciiFalse).encode(utf-8) ) for data in batch_data: producer.send(room_info_topic, valuedata) producer.flush()消费者端你可以用 Spark Structured Streaming 直接从 Kafka 里读from pyspark.sql import SparkSession spark SparkSession.builder \ .appName(KafkaConsumerToHive) \ .config(spark.sql.warehouse.dir, /user/hive/warehouse) \ .enableHiveSupport() \ .getOrCreate() df spark.readStream.format(kafka) \ .option(kafka.bootstrap.servers, localhost:9092) \ .option(subscribe, room_info_topic) \ .option(startingOffsets, earliest) \ .load()从 Kafka 读出的二进制 value 需要cast(string)转成字符串再用from_json配合 schema 解析成结构化数据。这一步如果你发现flink sink hive表 数据不入表这种类似问题大概率不是写入端的问题而是 schema 没对好字段类型不一致导致from_json解析出 null。排查思路是先看原始 message 输出再做列裁剪最后才检查 Hive 表元数据。4.3 关于消息延迟高和集群部署的几条经验热词里专门有kafka消息延迟高和kafka集群安装我简单说两个判断方向。延迟高先看是不是消费者线程数小于分区数。消费者组里如果有 3 个消费者但 topic 有 6 个分区那每个消费者至少要处理 2 个区配置前先算清楚比例。再看batch.size和linger.ms如果追求低延迟可以把linger.ms调小比如 5ms让消息更早被发出去。部署上最容易被忽略的是 Kafka 的advertised.listeners配置。伪分布式你填localhost:9092没问题但到了真正的集群上这个地址必须是对外可达的内网 IP否则消费者会一直连不上 broker。这个问题我在集群部署的时候排查了很久最后才发现是 listeners 配错了。本机测试都正常换服务器就四处连接超时十有八九跟这个有关。5. Hive仓库与Spark计算推荐结果是怎么算出来的5.1 数仓分层设计的基本骨架Hive 里的表不能一堆堆随手建要有清晰的分层。我实际跑通后保留的是四层结构这也是论文里最好画图说明的部分。第一层叫 ODS 层也就是原始数据层表结构对应爬虫爬到的原始字段尽量避免改动保留全部脏数据。清洗工作放到下一层再做。第二层叫 DWD 层是清洗明细层经过去重、补全、格式标准化之后的数据。第三层是 DWS 层服务层面向具体的统计需求设计比如按城市、按价格带、按评分段聚合出的汇总表。第四层是 ADS 层面向应用的结果表直接供可视化接口和推荐结果查询使用。用这种分层结构去回答Hive怎么设计这类问题比零散说我建了几张表要有条理得多。评委一听就明白你对数仓是有整体认知的。5.2 清洗要点去重、价格口径、缺失经纬度清洗细节决定了数据质量而数据质量直接决定后面的分析结论能否立得住。我做清洗时处理了三个典型问题。去重是最基本的不能只按room_id去重还要考虑同一条房源被不同爬虫进程抓到时标题或价格有细微差异的情况。我的方案是组合键去重room_idpriceupdated_date保留最新一条即可。价格口径问题比较隐蔽。爬虫抓到的页面价格可能包含优惠券、会员价等多套数值字段含义完全不同。清洗时必须统一成每晚实际售价并排除单位异常的数据。我当时写了个简单的规则价格小于 20 元或大于 5000 元的记录直接标记为异常不参与推荐计算。缺失经纬度的处理也很关键。有些房源没有定位信息但如果城市和行政区字段都在可以用区域中心点坐标作为近似值。这样宁可粗糙一点也不能让可视化地图上缺失一大块区域。5.3 推荐逻辑落地先跑规则统计再上ALS协同过滤这部分是整个项目的灵魂也是热词里spark数据分析案例最集中的地方。我做的第一版推荐不是算法而是规则用户选好城市和价格区间后按评分、评论数加权排序取 Top10。规则推荐虽然简单却能让整个系统先转起来为后续的算法推荐做好对比基线。第二版加上了基于 ALS 的协同过滤。构建矩阵时用户行为数据来自爬虫后台的模拟数据用户浏览民宿、收藏民宿、下单评分合成一个 1-5 分的隐式评分。训练模型时用 Spark MLlib 的 ALS 算法关键参数有rank、iterations、lambda。我当时用了一个小网格搜索from pyspark.ml.recommendation import ALS from pyspark.ml.evaluation import RegressionEvaluator als ALS( userColuser_id, itemColroom_id, ratingColrating, coldStartStrategydrop ) model als.fit(train_df) predictions model.transform(test_df) evaluator RegressionEvaluator(metricNamermse, labelColrating, predictionColprediction) rmse evaluator.evaluate(predictions)跑完 ALS 后每个用户会得到一个room_id列表和预测评分把这些结果写回 Hive 的 ADS 层表里。需要注意的是coldStartStrategy必须设为drop否则预测阶段遇到训练集里没出现过的用户会直接产生 NaN后续写库会报错。这个错我在实际开发里踩过印象非常深。规则推荐和算法推荐在论文里还可以做一个对比实验。你会发现冷启动场景下规则推荐表现更稳定而具有历史行为数据的用户ALS 的命中率更高。这样的对比表格一放实验章节就有说服力了。5.4 Hive日常三坑小文件优化、UDF、乱码分区删除这三个问题上热搜是有道理的几乎每个人都会遇到。小文件问题是 Hive 最常见的性能杀手。爬虫如果按批次频繁写入每批可能只产生几 MB 甚至几百 KB 的小文件日积月累上万个文件会让任务 Slow。解决方案是在建表或插入时设置merge参数或者在跑完一天的数据后执行一次合并操作INSERT OVERWRITE TABLE ... SELECT ...让 Spark 重新写一份数据自动把大量小文件聚合成少量大文件。过小的文件合并通常控制在 128MB 左右一个比较理想。自定义 UDF 在毕设里属加分项比如为了清洗民宿标签我写了个 UDAF 用于统计高频标签词。写完后记得处理依赖 jar 包的加载否则提交到集群时容易报ClassNotFoundException。我当时用spark.sql(ADD JAR hdfs://.../myudaf.jar)导入省了很多麻烦。删除乱码分区也是个高频问题。用 Hive 分区表存储按日期或城市分区的数据时如果有人误用了错误编码写入分区字段会产生city%E5%8C%97%E4%BA%AC这种乱码分区。删除方式不是直接DROP TABLE而是用ALTER TABLE room_dwd DROP PARTITION (city乱码值)。不写正确分区等于没删而且分不清哪个分区对的话可以先用SHOW PARTITIONS列出所有分区再一个个核对。6. 可视化与联动把Hive里的数字变成能讲故事的图表6.1 可视化选型最快能出效果的组合民宿可视化部分不需要炫技重点是把分析结果清晰展示出来。最经典的组合是 SpringBoot 后端 Vue 前端 ECharts 图表库。如果你的 Java 基础一般也可以直接用 Flask ECharts 在单页面上搞定开发速度更快。我个人偏向后一种因为系统的核心亮点在大数据处理链路上前端只要干净、能看就行。如果你连代码都不想写Superset 或 DataEase 这类开源 BI 工具也能连 Hive 或 MySQL拖拽出图表。不过答辩时评委可能会问这个图表的后端接口怎么实现的如果你只拖拽不出代码会比较被动。所以我建议无论用什么工具至少自己写一个查询 MySQL 结果表的接口把主动权握在手里。6.2 图表清单与背后的查询逻辑我做可视化时列了一份图表清单每一个都能对应到一条 Hive SQL 或者 Spark 计算逻辑图表类型展示内容计算来源全国地图散点图民宿地理分布按经纬度从 DWD 表取全量数据柱状图各城市民宿数量 Top10按城市做 count 聚合价格带分布图价格区间与房源数量关系按价格分桶做 group by散点图/热力图评分与评论数的关系按评分区间、评论数区间聚合排行榜好评民宿 Top20按评分和评论数加权计算推荐结果卡片当前用户的 TopN 推荐读 ADS 层推荐结果表这些图表的背后其实都是很简单的统计逻辑但放在一个完整系统里就显得很丰满。你不一定每张图都画挑 4-5 张最有代表性的就好。6.3 为什么最终查询走MySQL而不是直连Hive在可视化选型时很多人会问数据不是都在 Hive 里吗为什么图表接口不直接查 Hive 理论上当然可以通过 HiveServer2 用 JDBC 就能查询但实际体验很差。Hive 查询有秒级甚至分钟级的延迟每次前端刷新页面都要临时触发一次分布式任务这根本不现实。标准做法是把 DWS 和 ADS 层的结果表每天定时通过 Spark 任务导出到 MySQL前端接口只查 MySQL。这个离线计算结果同步的模式完全贴合民宿推荐这种 T1 场景。我在项目中写了个定时任务每天凌晨自动跑完推荐和统计早上起来打开页面看到的就是昨天的数据快照。这个设计在答辩时也是一个亮点表明你理解离线和实时之间的取舍。7. 部署避坑清单从伪分布式到集群的实战记录7.1 伪分布式搭建与ZooKeeper整合相关热词hadoop伪分布式搭建hadoop和zookeeper整合实战说明这两步是大多数人跨不过去的坎。伪分布式安装其实不难核心是改 4 个文件core-site.xml、hdfs-site.xml、yarn-site.xml、mapred-site.xml。我提醒一个常见的低级错误hdfs-site.xml里dfs.replication在伪分布式下一定要设成 1否则副本数不足会导致 HDFS 始终处于安全模式接下来所有读写都会报错。Hadoop 与 ZooKeeper 的整合要分情况。如果你的集群没有做 NameNode 高可用HA严格来说 ZooKeeper 不是 Hadoop 必需的。但在 HDFS HA 架构里ZooKeeper 负责选主和状态同步Kafka 的 broker 元数据也依赖 ZooKeeper 管理。我在毕设里采用了ZooKeeper Hadoop HA Kafka的完整架构虽然配置多了不少但在论文里可以顺手写一节高可用设计答辩时自然有话题。7.2 一次完整的排障过程Datanode进程反复消失这里分享一个我在集群搭建中花了两天才解决的典型问题排查链路对你会有直接帮助。现象是Hadoop 集群启动后jps命令里能看到 NameNode 和 SecondaryNameNode 进程但 Datanode 进程启动后几秒就自动退出日志里反复出现java.io.IOException: Incompatible namespaceIDs。我最初的猜测是配置文件路径写错了重新检查了所有 XML 配置没有任何收获。随后我想到Datanode 的 namespaceID 与 NameNode 的不一致通常是版本错乱导致的于是把dfs.datanode.data.dir目录下的文件全删了重新格式化 NameNode重启后 Datanode 正常上线。但问题并没有彻底消失。第二天重启机器后Datanode 又挂了这次日志显示Block pool ID needed我把 NameNode 再次格式化仍然无效。最后才想到检查/etc/hosts发现服务器的主机名解析到了 127.0.0.1而 NameNode 广播的是内网 IPDatanode 无法根据 hostname 找到 NameNode导致握手失败。修复方法是把/etc/hosts里的主机名映射改成内网 IP然后在防火墙开放 8020、9000、50070 等端口。这条排查链路的教训我写在这里启动 Hadoop 集群前先检查 hostname 解析再检查磁盘目录权限最后才考虑格式化。格式化是个很好用但风险很大的办法不到万不得已不要用。7.3 内存不足时的降级方案如果你的设备和我最开始一样只有 8G 内存又想跑完全链路建议按以下顺序降级第一步Hadoop 维持伪分布式不要开 HA第二步Spark 用 Local 模式跑而不是提交到 YARN配置spark.masterlocal[*]第三步Kafka 调小堆内存修改kafka-server-start.sh里的KAFKA_HEAP_OPTS-Xmx512m第四步Hive 的hive.exec.parallel设为 false避免多个任务同时抢资源。这套降级方案跑几千条民宿数据完全没问题。数据量不大的时候瓶颈根本不在框架而在你自己机器的资源分配。真正到答辩演示时稳定的演示体验远比大集群重要。7.4 集群配置速查表问题快捷解决方式NameNode 一直安全模式hdfs dfsadmin -safemode leave或检查副本数配置Datanode 起不来检查 namespaceID 和 hosts 映射必要时删 data 目录重格式化Spark 任务 OOM调大spark.executor.memory或减少并行度Kafka 消费者连不上检查advertised.listeners是否配置成可访问的 IPHive 查询慢检查小文件数量跑一次合并8. 最后论文与答辩环节的几张保命牌8.1 LW文档的章节编排建议毕设文档如果只是把系统模块介绍一遍评委大概率会打低分。我的建议是框架固定、内容填充时时刻围绕为什么这样设计展开。摘要部分强调从数据采集到可视化展示的完整闭环关键词写清楚各个技术组件。需求分析部分除了常规的功能需求必须写清楚非功能需求比如数据量预估、性能要求哪怕你的数据只有几千条也要说清楚设计容量。技术选型部分要对比不同方案也就是本文前面分析 Flume、Flink、MongoDB 的那一段写进文档能明显拉高深度感。系统设计部分用图把整体架构和数据流向画出来不需要用复杂的建模图清晰最重要。实验与验证部分是精华。我当时放了三类数据不同 ALS 参数的 RMSE 对比表、规则推荐与算法推荐的命中率对比、系统各环节耗时统计。这些数据都不是编的是跑了几十次任务之后整理的。有了这些数字论文就不虚。8.2 PPT串讲的核心话术答辩 PPT 不需要 30 页15 到 20 页足够。串讲时最有效的线索是一条数据从生到死的旅程爬虫从网页抓到原始数据经过 Kafka 缓冲进入 Hive 仓库Spark 在仓库上完成清洗、分析和推荐计算最后图表把结果呈现给用户中间每个环节遇到什么坑、用了什么手段解决这是我反复练习后效果最好的讲述结构。每翻到一页 PPT心里必须清楚这一页要留下的一个记忆点。比如 Hive 分层那页的记忆点是 ODS/DWD/DWS/ADS 四层ALS 那页的记忆点是coldStartStrategydrop这个坑Kafka 那页的记忆点是advertised.listeners导致消费者连不上。评委听完能记住三四个点答辩就已经成功了。8.3 高频问题怎么接招为什么用 Spark 不用 MapReduce这是出场率最高的问题。标准回答思路是MapReduce 每个中间步骤都可能把结果落盘迭代式计算性能差ALS 这种需要多轮迭代的算法Spark 基于内存计算的优势非常明显。数据量虽然不大但技术选型要考虑扩展性。你的数据量有多少够支撑分布式吗这个问题要诚实回答然后再说分析逻辑。我会说单个核验节点处理几千条数据确实大材小用但如果扩展到全平台爬到十万级数据也只是增加机器的问题架构上不需要改动。这么说既诚实又有底气。爬虫合法吗这是不可回避的议题。回答要点是只采集公开可访问的数据遵循 robots 协议控制访问频率不触碰用户隐私数据采集范围限定在房源展示信息。这样回答既专业又稳妥。做这种系统性毕设最大的体会是别指望一上来就万事俱备。先把最小闭环跑通再把每一个组件往里面加。Kafka 跑不通就用临时文件缓冲Spark 起不来就先本地跑 pandas 验证逻辑。闭环通了剩下的都是打磨而打磨的过程才是你收获最大的部分。