资讯动态

Hadoop+Spark构建宠物商品比价推荐系统实战解析

发布时间:2026/10/5 3:40:20 来源:尧图企业网站定制
这个题目其实挺典型的宠物商品比价加推荐技术栈直接踩在大数据开发的经典组合上Hadoop做存储、Spark做计算、SpringBoot做服务最后再用可视化大屏把结果摆出来。很多同学拿到这类题目第一反应是“东西太多不知道从哪下手”其实拆开看就是一条数据管道采集、清洗、计算、展示。我按自己做过的类似项目把整个系统从头到尾捋一遍包括环境搭建、核心代码思路、大屏开发、还有调试时踩过的坑希望能给正在做大数据方向项目或毕设的朋友一点参考。1. 项目概述与核心需求拆解1.1 宠物商品比价的真实痛点养宠物的人都有一个共同体验同一款猫粮、同一个牌子的驱虫药在不同平台上的价格能差出几十块。宠物商品本身SKU不算特别多但规格复杂比如按重量分、按年龄段分、按口味分用户手动比价非常累。这个系统的核心需求就是解决“同款商品在多个渠道的价格对比”问题并且根据用户的历史浏览和购买行为推荐可能感兴趣的商品。比价的关键不是简单列价格而是把不同平台、不同表述的同一商品识别出来。比如“皇家猫粮K36 2kg”和“皇家K36成猫粮2kg装”其实是同一个商品但字符串完全不同这就需要一个商品归一化匹配模块。推荐则要处理用户行为数据比如点击、收藏、加购、购买这些行为日志数据量一大单机处理就吃力了正好用Spark来做离线计算。1.2 技术选型背后的理由很多类似系统会用MySQL加一个推荐算法就完事但既然题目明确要求HadoopSparkSpringBoot说明目标不是做一个普通Web项目而是体现大数据处理能力。Hadoop HDFS负责存储原始日志和采集数据Spark负责批量清洗、特征统计和训练推荐模型SpringBoot负责对上层提供REST接口可视化大屏通过接口拿数据渲染。这个组合的合理性在于数据量级达到千万级甚至亿级时MySQL单表查询和单机Python处理都会成为瓶颈。HDFS可以低成本扩展存储Spark的分布式计算能力能处理TB级数据SpringBoot则负责把计算结果暴露给前端各司其职。而且这三个技术栈目前在企业里也是主流搭配做完这个项目简历上写“熟悉Hadoop生态、Spark计算、SpringBoot开发”是有底气的。1.3 系统功能边界与角色设计系统分前台和后台两个视角。前台面向普通用户注册登录、商品搜索、多平台比价展示、价格趋势、个性化推荐列表。后台面向管理员商品管理、价格数据管理、推荐结果管理、可视化统计大屏。大屏上主要展示平台商品数量占比、价格分布、热门品类排行、推荐算法命中率等指标。实际上比价系统的数据来源有两种方式一种是调用电商平台开放API另一种是自己写爬虫抓取。毕设场景下API申请复杂通常采用爬虫模拟请求抓取公开页面信息再解析入库。需要特别注意的是爬虫频率要控制不能影响目标网站机器成本也有限一般用单机加定时任务即可。数据量不大时可以直接落MySQL但为了体现大数据技术建议原始数据先落到HDFS清洗后再同步到MySQL供SpringBoot查询。2. 系统架构与数据流转设计2.1 分层架构与每层职责整个系统按数据流向可以分成五层数据采集层爬虫程序抓取各大电商平台的宠物商品信息包括商品标题、品牌、规格、价格、销量、店铺、抓取时间等。存储层原始数据先写入HDFS按日期分区存放清洗后的结果数据写入MySQL用于业务查询推荐模型和中间结果可以存Redis或HDFS。计算层Spark定期对HDFS上的原始数据做ETL清洗执行商品归一化匹配训练推荐模型将结果写回MySQL。服务层SpringBoot提供用户管理、商品查询、比价详情、推荐列表等接口整合MyBatis操作MySQL结合Redis缓存热点数据。展示层管理后台和可视化大屏。大屏采用ECharts绘制图表通过SpringBoot接口获取统计数据定时轮询更新。每一层边界要清晰尽量不要跨层调用。比如SpringBoot不直接去读HDFS上的原始数据而是通过Spark清洗好的结果表来查询这样可以避免业务服务和离线任务互相干扰。2.2 从数据采集到可视化大屏的完整链路一个典型的处理流程是这样爬虫每天定时抓取宠物商品数据生成JSON或CSV文件。文件上传到HDFS指定目录比如/pet_goods/raw/20250101/。Spark作业读取当天数据进行去重、格式转换、字段补全、价格清洗。Spark运行商品匹配算法生成goods_id与platform_sku_id的映射表。Spark协同过滤算法基于用户行为日志训练推荐模型产出每个用户的TopN商品列表。清洗结果和推荐结果写入MySQL。SpringBoot接口查询MySQL返回给Web端和大屏。大屏每30秒调用一次统计接口刷新图表数据。这条链路把大数据组件和业务系统串起来了。从表面看Hadoop只是在后台存储但真正的价值在于当数据量大了之后Spark的分布式计算能力能把原本几小时的清洗任务压缩到几分钟而且可以做复杂的统计分析比如商品价格波动趋势、用户偏好画像等。2.3 工程模块划分与关键配置实际工程中建议分模块管理Maven多模块结构会清晰很多pet-crawler爬虫模块独立运行产出数据文件。pet-etlSpark ETL模块包含清洗、匹配、推荐算法。pet-common公共Java类、实体、工具类。pet-serverSpringBoot后端模块包含Controller、Service、Mapper。pet-webVue前端包含管理后台和大屏页面。单独把Spark任务拆出一个模块很重要因为大数据任务和Web服务是不同生命周期。Spark任务用Scala或Java编写打包成JAR通过spark-submit提交SpringBoot则是一个独立应用通过java -jar运行。两者之间用MySQL和Redis交互互不依赖。Hadoop和Spark的配置版本要提前确定避免踩版本兼容的坑。我个人常用的组合是Hadoop 2.10.x、Spark 2.4.x、Java 8、SpringBoot 2.3.x。这个组合经过了大量项目验证互相兼容性好网上资料也多。如果用太新的版本比如Hadoop 3.3加Spark 3.x对CPU指令集和依赖库要求更高伪分布式环境下容易出莫名奇妙的错。3. 大数据基础环境搭建实战Hadoop伪分布式与Spark3.1 Hadoop伪分布式搭建完整步骤很多同学第一次接触Hadoop就是卡在环境搭建上。伪分布式模式是在一台机器上模拟分布式环境每个Hadoop进程都是一个单独的Java进程分别扮演NameNode、DataNode、ResourceManager、NodeManager。搭建步骤可以总结为五步。第一步安装JDK并配置环境变量。Hadoop 2.x和Spark 2.x必须用Java 8不要用高版本JDK不然会有UnsupportedClassVersionError或者JAXB相关报错。配置JAVA_HOME后记得source /etc/profile。第二步配置SSH免密登录。伪分布式虽然只有一台机器但Hadoop启动脚本仍然会通过SSH连接到本机来启动远程进程。执行ssh-keygen -t rsa一路回车然后把公钥追加到authorized_keys里。验证方式就是ssh localhost不需要输入密码。第三步解压Hadoop并修改配置文件。需要改的文件有五个hadoop-env.sh设置JAVA_HOME。core-site.xml配置fs.defaultFS为hdfs://localhost:9000。hdfs-site.xml配置副本数dfs.replication为1NameNode数据目录和DataNode数据目录。mapred-site.xml或直接使用YARN模式配置mapreduce.framework.nameyarn。yarn-site.xml配置ResourceManager地址和NodeManager附属服务否则Spark on YARN会报错。第四步格式化NameNode。执行hdfs namenode -format这一步容易出现NameNode already formatted的提示因为多次格式化导致clusterID不一致需要在dfs/name目录下手动清理旧数据再格式化。第五步启动服务并验证。执行start-dfs.sh和start-yarn.sh然后用jps查看进程正常情况下能看到NameNode、DataNode、ResourceManager、NodeManager。再用浏览器访问http://localhost:9870能看到HDFS文件系统界面就说明成功了。3.2 Spark部署模式选择与提交参数Spark有三种常见部署模式Local、Standalone、YARN。项目里推荐使用Local或YARN。如果是开发调试阶段用Local模式最简单。在IntelliJ IDEA里直接运行Spark代码时默认就是Local模式不需要额外启动Spark集群。但提交到实际环境跑数据时建议用YARN模式因为Hadoop YARN负责资源调度Spark作为客户端提交任务即可。提交命令一般长这样spark-submit \ --class com.pet.etl.ItemCFRunner \ --master yarn \ --deploy-mode cluster \ --executor-memory 2g \ --num-executors 2 \ pet-etl-1.0.jar \ --input /pet_goods/raw/20250101 \ --output /pet_goods/result/20250101这里有几个关键参数executor-memory和num-executors控制资源大小伪分布式环境内存有限不要贪多两个executor各1~2G足够deploy-mode选择cluster模式让Driver在集群内部运行这样日志集中便于排查。很多同学在本地跑通Spark代码一提交到集群就报端口冲突或目录权限问题大部分是因为--master写错了或者HDFS目录没有写权限。3.3 高可用扩展Hadoop与Zookeeper整合要点伪分布式环境下其实不需要高可用但很多课程设计强制要求整合Zookeeper。整合的意义在于解决NameNode单点故障问题引入两个NameNode一个Active一个Standby通过Zookeeper实现自动故障切换。整合步骤不复杂安装Zookeeper配置zoo.cfg启动zkServer.sh然后在Hadoop的core-site.xml里配置ha.zookeeper.quorum在hdfs-site.xml里配置nameservices、namenode列表、JournalNode等。启动顺序是先启动Zookeeper再启动JournalNode然后格式化NameNode并初始化共享编辑日志最后启动start-dfs.sh。这里最容易踩的坑是格式化NameNode后没有初始化shared edits导致Active NameNode无法写入JournalNode日志里会大量报错。解决方法是运行hdfs namenode -initializeSharedEdits。另外一个坑是Zookeeper和Hadoop的Netty包版本冲突如果出现org.apache.hadoop.hdfs.server.namenode.ha相关异常先检查zookeeper的JAR包是否被错误放入了Hadoop的lib目录。4. 核心业务实现数据清洗、比价引擎与推荐算法4.1 多平台数据采集与标准化清洗采集层我建议用Python写爬虫不要用Java写。原因很简单Python爬虫生态成熟BeautifulSoup、Scrapy、Requests都很好用写起来效率高。Java爬虫在后续和HDFS交互上方便但开发效率低。实际项目里经常是Python爬虫采集数据生成文件再通过hdfs dfs -put命令上传也可以用Flume做日志收集但对毕设来说直接用命令上传就够了。采集的数据字段定义如下字段名含义示例platform平台标识jd、taobao、pddsku_id平台商品ID1000321title商品标题皇家猫粮K36 2kgbrand品牌皇家category类目猫主粮spec规格参数2kgprice实付价159.00sales月销量3200shop_name店铺名旗舰店crawl_time抓取时间2025-01-01 10:00:00清洗模块通常包含以下步骤去除标题中的HTML标签和不可见字符。价格字段可能包含“¥”或“券后价”需要提取纯数字。规格字段不统一比如“2kg”和“2000g”需要统一转换成标准格式。销量字段有的平台是“1.2万”需要转换成12000。按platform sku_id去重每天只保留一次记录保留更新时间最新的那条。这一步用Spark DataFrame写非常顺手。样例代码如下val df spark.read.json(hdfs://localhost:9000/pet_goods/raw/20250101) val clean df .withColumn(price, regexp_replace(col(price), [^0-9.], )) .withColumn(sales, expr(cast(sales as double))) .filter(col(price).isNotNull col(price) 0) .dropDuplicates(platform, sku_id) clean.write.mode(overwrite).parquet(hdfs://localhost:9000/pet_goods/clean/20250101)关键一点是清洗规则不要一股脑写死在Spark代码里建议用配置文件把映射关系放进去比如规格标准化规则、品牌别名映射。因为平台的数据格式会经常变规则配置化可以不用重新编译打包就调整逻辑。4.2 商品同款匹配与比价策略比价系统最核心的模块就是同款识别。同款不能简单的按标题精确匹配因为不同平台之间的标题风格差异很大拼多多可能写“皇家猫粮K36成猫幼猫通用粮2kg”京东可能写“【官方旗舰店】royal canine K36 2kg”。因此我采用了“品牌关键规格核心词”的多级匹配策略。具体实现思路分三层第一层品牌匹配。先把品牌标准化比如“皇家”和“royal canine”对应到同一个品牌ID。这一步需要维护一个品牌映射表或者用HanLP分词后提取品牌词。第二层规格匹配。从标题中提取规格信息比如“2kg”、“4kg”、“10L”等转换成标准单位。只有规格相同才能继续比价。第三层系列匹配。在品牌和规格一致的前提下提取商品的系列名比如“K36”、“天然粮”、“处方粮”等。可以使用编辑距离或SimHash计算相似度设定阈值0.8以上视为同款。通过三层匹配后生成商品标准ID即goods_id。每个goods_id下挂多个平台的SKU。比价展示时从这个goods_id关联所有平台的价格按照价格从低到高排序展示最低价、最高价、平均价和历史价格走势。比价策略里需要特别注意价格时间维度的处理。用户要的不只是当前最低价还有“这款商品最近30天价格有什么变化”。因此我设计了价格事实表一天一条记录包含goods_id、platform、price、crawl_date。Spark按天聚合更新大屏上用折线图展示价格趋势。4.3 基于Spark MLlib的ALS推荐实现推荐模块我选用了Spark MLlib里的ALS交替最小二乘算法。ALS是协同过滤的一种实现特别适合处理用户对商品的评分矩阵。但宠物电商场景里没有显式评分只有用户行为所以需要把行为转化为隐式评分。点击给1分、收藏给3分、加购给5分、购买给10分时间衰减后再加权生成一个(userId, goodsId, score)数据集。ALS算法在Spark里用起来很简洁import org.apache.spark.ml.recommendation.ALS val als new ALS() .setMaxIter(10) .setRank(10) .setRegParam(0.1) .setUserCol(userId) .setItemCol(goodsId) .setRatingCol(score) .setColdStartStrategy(drop) val model als.fit(training) model.recommendForAllUsers(10).write.mode(overwrite) .save(hdfs://localhost:9000/pet_goods/recommend/20250101)注意setColdStartStrategy(drop)这个参数一定要设置否则预测时遇到未见过的新用户或新商品就会返回NaN后续写入MySQL时会报错。ALS训练出来的结果是用户对商品的预测评分把每个用户评分最高的10个商品写回MySQL的推荐结果表SpringBoot接口直接查询即可。不过协同过滤有明显的冷启动问题新用户没有历史行为推荐结果会不准确。项目里我做了一个混合策略对于新用户推荐热销榜、好评榜、低价榜对于老用户优先实时推荐没有实时数据则用ALS离线结果。这个逻辑放在SpringBoot服务里判断很简单却能让推荐感觉聪明很多。4.4 SpringBoot服务层设计与接口规范SpringBoot作为服务层负责把计算好的数据暴露给前端。项目里没有让SpringBoot直接调用Spark而是通过MySQL完成数据交互。这是因为Spark作业是周期性的而SpringBoot是常驻服务两者生命周期不同步。推荐结果和统计结果放MySQLSpringBoot只做查询和缓存。核心接口设计如下接口路径方法说明/api/goods/searchGET商品搜索支持关键字分页/api/goods/compare/{goodsId}GET比价详情返回多平台价格/api/goods/price/trend/{goodsId}GET价格趋势/api/recommend/{userId}GET个性化推荐列表/api/dashboard/overviewGET大屏概览统计SpringBoot整合MyBatis没什么好说的关键是热点数据一定要加Redis缓存。比价页面的调用频率很高同一个商品ID的比价数据没必要每次都查询数据库。我用RedisTemplate缓存goodsId对应的比价结果设置过期时间10分钟能显著降低MySQL压力。大屏接口和普通接口要分开设计因为大屏接口返回的数据结构比较固定字段多、聚合层次深。我建议单独建DashboardController专门返回大屏需要的VO对象不要复用普通列表的布尔类型字段不然前端解析很麻烦。5. 可视化大屏开发从数据到图表的落地技巧5.1 大屏技术选型与布局设计可视化大屏我建议直接用Vue加ECharts不必引入过重的BI工具。ECharts的组件丰富能覆盖大屏90%的需求而且社区案例多改起来快。如果追求更炫酷的效果可以用DataV的React组件但学习成本稍高。大屏布局一般分三栏中间放核心指标比如总商品数、总用户数、平均折扣左右两侧放排行和占比图。宠物商品比价系统的大屏我放了六个图表模块各平台商品数量占比饼图热门品类Top10横向柱状图价格区间分布柱状图商品价格趋势Top5折线图推荐算法命中率仪表盘最新采集动态滚动列表大屏设计有个原则数据密度要高但不是堆砌数字。每一块图表都要对应一个业务问题比如平台占比展示采集覆盖度价格区间分布反映宠物商品的市场价格结构推荐命中率反映算法质量。5.2 核心图表模块实现要点饼图和柱状图比较常规重点说折线图和仪表盘。折线图用于展示几款热点商品的价格走势。后端接口需要返回每个商品最近30天的价格序列数据结构类似{ goodsId: 1001, title: 皇家猫粮K36 2kg, trends: [ {date: 2025-01-01, price: 159}, {date: 2025-01-02, price: 156} ] }前端把多个商品的数据映射成多条线ECharts就自动渲染成多折线图。这里要注意时间轴对齐有些商品在某天可能没有价格记录需要后端补全为前一天的最近价格否则折线会出现断档。仪表盘用于展示推荐命中率。命中率怎么定义简单来说用户某次推荐列表里如果有商品被点击或者加购就记为命中。这个数据不能实时算是由Spark离线统计通过推荐结果表和行为日志表关联统计每个用户的推荐列表中商品的点击比例。仪表盘的值定期更新大屏展示的是最近一次计算的结果。5.3 大屏数据实时刷新策略大屏不需要秒级实时30秒轮询一次足够了。实现方式有三种前端setInterval定时调用接口最简单。后端WebSocket推送实时性高但需要维护连接状态。用Server-Sent Events单向推送比WebSocket轻量。毕设项目我推荐第一种因为大屏是展示给评委看的轮询间隔可以调成10秒甚至5秒也能达到“实时”的观感。但注意后端接口要做缓存如果每次刷新都查全量统计数据库压力会非常大。我是在Dashboard接口上加了一层Spring Cache缓存时间设为10秒这样即使前端轮询很频繁后端也扛得住。大屏页面在大屏显示器上开发时要注意缩放适配。最稳妥的方案是用vw/vh单位做自适应布局或者用transform: scale()做整体缩放。我自己习惯用后一种把大屏设计稿固定在1920x1080然后根据屏幕实际尺寸等比缩放开发时不用考虑各种分辨率效果很好。6. 常见问题与调试实录6.1 Hadoop/Spark环境启动类问题这类问题占了整个项目调试时间的一半很多新手都是在环境上栽跟头。第一个高频问题格式化NameNode时提示Java.io.IOException: Incompatible clusterIDs。原因是多次格式化导致dfs/name和dfs/data目录里的clusterID不一致。解决办法是删除NameNode和DataNode两个目录下的所有数据然后重新格式化并且以后不要随便再格式化。第二个高频问题DataNode无法启动日志里报Initialization failed for block pool。大概率也是clusterID问题和上面一样清理DataNode目录即可。第三个高频问题Spark任务提交后一直卡在ACCEPTED状态YARN界面没有Application。这通常是ResourceManager和NodeManager时间不同步导致。检查集群机器的时间执行date -s同步一下任务就能正常跑起来。第四个高频问题405错误访问HDFS Web UI。Hadoop 3.x默认端口是9870Hadoop 2.x是50070很多教程不区分版本用错端口就会打不开页面。先执行hdfs getconf -confKey dfs.namenode.http-address确认实际端口。6.2 Spark作业性能与内存问题Spark作业在伪分布式环境下最容易报的就是OOM内存溢出。我有一次清洗100万条商品数据默认配置直接报ExecutorLostFailure。调优方向有三个。第一个方向是减少Shuffle数据量。清洗任务中dropDuplicates(platform, sku_id)会触发全量Shuffle。可以先按platform分区再在分区内去重这样可以把Shuffle量降下来。还可以用repartition控制分区数比如df.repartition(4)。第二个方向是调整内存配置。在spark-submit时加上--conf spark.memory.offHeap.enabledtrue和--conf spark.memory.offHeap.size1g同时把executor-memory控制在2G以内。注意伪分布式环境下物理机内存有限给Spark太多内存可能会把系统拖垮。第三个方向是使用cache()在哪里要用。如果同一份DataFrame被多次action操作比如既写入Parquet又统计数量每次都会重新计算。在这之前执行df.cache().count()让Spark把数据缓存到内存里能节省大量时间。6.3 SpringBoot连接大数据组件的坑SpringBoot本身不直接和Hadoop交互但很多同学喜欢在SpringBoot里读HDFS文件这时需要添加hadoop-client依赖。注意这个依赖会把很多Hadoop的JAR包引入SpringBoot的lib目录导致和SpringBoot自带的servlet-api、jackson等冲突。解决办法是在Maven里排除冲突包或者在独立的模块里做HDFS读写再封装成REST接口不要让Hadoop依赖污染主服务。另一个高频问题是推荐接口偶发返回空列表。原因是Spark写MySQL的时间刚好和SpringBoot查询的时间重叠MySQL事务隔离级别导致读到了未提交的数据。解决办法是调整Spark写MySQL的时机也调整大屏轮询间隔同时读取时要加判断如果最近一次推荐结果为空就查询历史兜底数据。还有MySQL连接问题Spark作业如果没有加mysql-connector-java驱动写MySQL时会报ClassNotFoundException。在提交任务时需要用--jars参数指定驱动JAR路径或者用--packages引入spark-submit --packages mysql:mysql-connector-java:8.0.33 ...6.4 部署与演示环境注意事项如果要在演示现场跑整个系统一定要先启动好Hadoop和Spark再启动SpringBoot然后打开大屏页面。顺序错了会各种连不上。建议写一个启动脚本一键搞定start-dfs.sh start-yarn.sh nohup java -jar pet-server.jar server.log 21 演示前把HDFS上的原始数据跑一遍清洗和推荐任务确定MySQL里有最新结果。在现场不要临时跑Spark任务一旦资源不够或网络抖动任务卡住会给演示带来很大风险。我习惯提前一天把离线任务都跑完现场只查MySQL结果这样最稳。还有一个细节大屏页面要提前在演示机器上打开并且关闭浏览器的自动休眠。如果大屏是用setInterval轮询浏览器标签页在后台时间久了会被节流轮询间隔会变成几分钟一次看起来就像假死。最好的办法是演示时大屏全屏展示或者把页面加一个visibilitychange监听页面恢复可见时立刻刷新一次数据。这个项目做完之后的一些体会我个人做下来最大的感受是这类系统真正的难点不在算法多高级而在数据链路的完整度。从爬虫采集到HDFS存储从Spark清洗到MySQL结果再到SpringBoot接口和大屏图表任何一个环节断了系统都跑不起来。所以做项目时一定要先把数据流跑通哪怕界面丑一点、逻辑简单一点先把端到端串起来再逐步优化。另外推荐算法的部分不要迷信模型复杂度。ALS调参看起来花哨但实际效果很大程度上取决于评分矩阵的质量。如果你的用户行为数据很少还不如先把“猜你喜欢”做成简单的热度推荐加规则过滤把链路跑顺之后再用算法模型去替代效果反而更好。调试大数据项目要有耐心错误日志一定要逐行看尤其注意Exception的Caused by部分很多问题都藏在最下面那层。还有一个通用技巧遇到诡异的报错先重启Hadoop和Spark进程再检查磁盘空间最后才怀疑代码。大数据组件跑久了临时目录和日志文件非常占磁盘磁盘满了之后各种莫名其妙的异常都会出现清理一下往往立竿见影。这个系统做完之后其实还可以继续扩展很多方向。比如接入实时流处理用Kafka加Spark Streaming统计实时点击流比如把推荐从离线升级成实时用Redis存用户最近行为还比如加入NLP对商品评论做情感分析给商品打上正负面标签。这些方向都会让项目更有亮点不过在基础功能稳定之前先别急着往上加东西稳扎稳打才是关键。

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

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

免费获取报价 →
↑