ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

农产品推荐系统毕设全流程:Hadoop+PySpark+Scrapy实战解析

农产品推荐系统毕设全流程:Hadoop+PySpark+Scrapy实战解析 每年三四月份毕设群里就开始热闹起来。农产品推荐系统这个题目几乎年年都有看起来就是 Hadoop、PySpark、Scrapy 这几个技术名词的堆叠但真正动手做的时候里面的细节多到让人头皮发麻。我这个项目从爬虫采集、离线清洗、统计分析到推荐模型、可视化大屏整条链路完整跑通配套的代码、论文、PPT 和讲解视频也都备齐了。今天把这套东西从零到一的过程彻底捋一遍重点讲讲每个环节为什么这么设计、哪些地方容易翻车给准备走大数据方向毕设的同学一条可以照抄的落地路径。这不是一个普通的管理系统。它的核心是一条完整的大数据流水线Scrapy 负责从公开农产品信息网站抓取价格、产地、销量等数据Hadoop 提供分布式文件存储底座PySpark 完成离线清洗、统计以及推荐模型训练最后用可视化大屏把分析结果呈现出来并把推荐结果通过 Web 接口输出给用户。无论你是第一次接触大数据项目还是已经搭过一些简单应用这篇文章里提到的思路和踩坑经验都能帮你少走不少弯路。1. 项目到底要做什么需求拆解与技术选型1.1 从毕设题目到功能清单拿到这种题目第一件事不是急着打开终端敲命令而是把题目拆成能落地交付的功能模块。我当时给自己列了一张清单刚开始很粗后来越拆越细最后变成了这样农产品数据采集用 Scrapy 从公开信息源抓取农产品名称、品类、产地、价格、销量、发布时间等数据。数据存储与预处理原始数据落 HDFS用 PySpark 做清洗、去重、格式标准化。统计分析产出价格趋势、产地分布、品类占比、市场销量排行等指标。推荐系统基于用户行为数据训练协同过滤模型输出个性化农产品推荐列表。可视化大屏用图表把统计结果展示出来兼顾答辩演示效果。Web 后端提供推荐接口和图表数据接口打通前后端链路。这个需求拆解的过程非常关键。很多同学一上来就写代码结果做着做着发现功能边界模糊比如推荐系统到底要不要做用户注册登录可视化大屏要展示多少张图这些在开题阶段没想清楚后期改起来特别痛苦。我当时是把每个模块的输入、输出、数据流向都画在纸上虽然不能叫架构图但至少心里有数知道这套系统从数据进来到最终展示要走哪几步。也有同学会问“是不是一定要把 Hadoop 和 PySpark 都放进去”说实话如果只做一个小型推荐网站MySQL 加 Python 就足够了。但这是大数据方向的毕设题目本身已经限定了技术栈所以这些组件必须出现在正确的位置上。关键是不要让它们变成摆设而是让每个组件都能解释清楚它解决了什么问题。1.2 为什么是 Hadoop PySpark Scrapy 这个组合技术选型这块我想说点真心话。这个组合在真实的大数据开发岗位中确实常见但在毕设场景下它更多是为了模拟企业级数据处理链路。Scrapy 是 Python 生态里最成熟的爬虫框架支持异步并发、中间件扩展、管道处理用它抓取农产品数据再合适不过。Hadoop 提供 HDFS 分布式存储虽然我的数据量远远达不到“分布式”的规模但把原始数据按目录结构放到 HDFS 上再配合 Hive 做查询分析整条链路就有了大数据项目的骨架。PySpark 的价值体现在两层。第一层是数据清洗和统计用 DataFrame API 处理几万条或几十万条数据非常顺手而且能直接读写 HDFS不需要像传统方案那样先下载到本地再处理。第二层是机器学习PySpark 自带的 MLlib 里有 ALS 协同过滤算法这是推荐系统的核心可以直接拿来训练模型、生成推荐列表不用自己去实现矩阵分解。Scrapy 放在最前面是因为整个系统的数据源头是它。爬虫爬不到数据后面所有模块都是空转。我在选型时对比过 requests 加 BeautifulSoup 的方案那个方案写起来简单但遇到需要并发抓取、请求重试、数据去重、管道存储时就捉襟见肘。Scrapy 的架构天然把这些能力都包含了定义好 Item 和 Pipeline后期扩展特别方便。还有一个现实问题环境怎么搭。Hadoop 和 Spark 直接装在虚拟机上非常容易翻车系统路径、Java 版本、权限配置任何一个环节出错都能耗掉半天。我后来尝试了 Docker 镜像方案直接拉一个已经配好 Hadoop 的基础镜像再手动调整配置省去了大量重复操作。这里给一个建议如果毕设只要求跑通流程伪分布式模式完全够用不需要搭建多节点集群否则光是节点通信和资源调度的问题就能让人崩溃。2. 数据从哪来Scrapy 爬虫的完整落地细节2.1 爬取目标与字段设计爬虫要做得好首先要明确抓什么、存什么。农产品推荐系统需要的数据核心是商品信息和价格信息。我当时把目标设定为公开的农产品价格信息网站和批发市场数据页面这类站点数据更新及时、字段结构相对固定适合用于学习和研究。字段设计上我最终确定了这样一组name农产品名称比如“西红柿”“黄瓜”“富士苹果”。category品类用于分类统计和推荐时的内容匹配。origin产地用于产地分布分析和用户偏好判断。price单价单位统一为元/公斤遇到元/斤的页面要主动转换。unit计量单位保留原始单位防止转换出错。market所属市场用于市场维度的对比。date发布日期用于时间序列分析。sales_volume销量部分站点没有这个字段没有就直接置 0。字段设计看起来简单其实坑很多。比如价格单位不统一、产地信息为 null、同一个商品在不同市场名称写法不一样这些都会在后续 PySpark 清洗阶段变成麻烦。我的经验是在爬虫阶段就尽量把数据规范化能用规则转换的就在 Pipeline 里转换不要把所有脏数据都丢给离线任务否则清洗逻辑会变得极其复杂。我见过不少同学把日期字段存成“2025-03-18 14:23:00”看起来没问题但到了按天做统计时还得先截断再分组。我当时直接统一成 yyyy-MM-dd 格式省了后面很多事。品类字段也是类似需要建立一套基础分类映射比如把“西红柿”“番茄”归到“茄果类”不然统计出来的品类占比会很零散。2.2 Scrapy 框架搭建与核心代码Scrapy 项目结构并不复杂核心是 items.py、pipelines.py 和 spiders 目录下的爬虫文件。我建的项目叫 agri_scraper目录里最重要的几个文件按职责拆分得很清楚。先定义一个 Item把所有字段声明出来这样后续 Pipeline 处理时能清楚地知道数据长什么样import scrapy class AgriProductItem(scrapy.Item): name scrapy.Field() category scrapy.Field() origin scrapy.Field() price scrapy.Field() unit scrapy.Field() market scrapy.Field() date scrapy.Field() sales_volume scrapy.Field()Spider 的核心逻辑是解析页面提取目标字段。下面是一个简化的示例实际页面结构需要根据目标站点调整 CSS 选择器或 XPath import scrapy from agri_scraper.items import AgriProductItem class AgriPriceSpider(scrapy.Spider): name agri_price allowed_domains [example-agri.com] start_urls [https://example-agri.com/price/list] def parse(self, response): for row in response.css(tr.item-row): item AgriProductItem() item[name] row.css(td.name::text).get() item[category] self.classify(row.css(td.name::text).get()) item[origin] row.css(td.origin::text).get() item[price] self.parse_price(row.css(td.price::text).get()) item[unit] 元/公斤 item[market] row.css(td.market::text).get() item[date] response.url.split(date)[-1] item[sales_volume] 0 yield item next_page response.css(a.next::attr(href)).get() if next_page: yield response.follow(next_page, callbackself.parse)这段代码里有两个细节值得注意。第一是 classify 方法它负责把原始名称归到统一的品类体系里内部就是一组关键词映射表。第二是 parse_price 方法因为页面上的价格可能是“3.5元/斤”“2元/kg”这种非标准格式必须提前写解析函数统一转成浮动数值。做爬虫的核心不是把数据拿回来而是拿回来的数据能直接给下游用。Pipeline 负责把数据写到存储系统。我当时的做法是先写入 MySQL再通过同步脚本每天将新增数据导出为 CSV 并上传到 HDFS。这个流程比直接在 Pipeline 里调用 HDFS API 更稳定因为爬虫的写入频率很高直接走 HDFS 容易产生大量小文件后续处理反而低效。Pipeline 里还会做简单的合规校验比如价格大于 0 且不超过某个上限明显异常的数据直接丢弃。2.3 反爬机制与动态页面抓取的实战处理反爬是爬虫项目里绕不开的话题但想强调一句我们只采集公开数据、只用于学习和研究必须遵守目标站点的访问频率要求和 robots.txt 声明不要给目标服务器造成压力。在这个前提下常见的几个反爬策略可以这样应对User-Agent 轮换维护一个 UA 列表在 Downloader Middleware 中随机选择。很多站点只会检查 UA 是否为常见浏览器。请求间隔通过 DOWNLOAD_DELAY 设置 1 到 3 秒的下载延迟可以显著降低被封风险。比设置并发数更实在。失败重试RETRY_TIMES 设为 3遇到 502、503 时自动重试而不是直接跳过。动态页面处理部分农产品价格页面是 iframe 嵌进去的直接请求当前 URL 只能拿到空壳。我引入了 scrapy-playwright 来渲染动态内容等 iframe 加载完成后再提取数据。scrapy-playwright 的集成并不复杂核心是在 settings.py 中开启 Playwright 支持然后在 Request 中指定 meta 参数import scrapy class DynamicSpider(scrapy.Spider): name agri_dynamic def start_requests(self): yield scrapy.Request( urlhttps://example-agri.com/dynamic-price, meta{playwright: True, playwright_include_page: True}, callbackself.parse ) async def parse(self, response): page response.meta[playwright_page] # 等待 iframe 渲染完成后提取 await page.wait_for_selector(iframe) frame page.frames[1] rows await frame.query_selector_all(tr) # 继续处理 rows这种异步处理方式在动态页面里非常管用特别是当数据被嵌在多层 iframe 中时普通请求根本拿不到。但要注意启动 Playwright 后爬虫运行速度会明显下降所以我的策略是普通静态页面走 Scrapy 原生请求只有遇到动态 iframe 时才用 Playwright二者搭配效率和安全之间取一个平衡。爬虫写完后一定要单独验证数据质量。我的做法是抓取完当天数据后打开 MySQL 表看几个抽样记录确认价格字段没有空值、日期字段没有乱码、品类字段都有归属。这些检查看着琐碎但确实能为后面省下大把时间。3. 数据怎么存怎么算Hadoop PySpark 离线链路3.1 HDFS 目录设计与数据入库流程Hadoop 环境搭好之后第一件事是规划 HDFS 目录。这个看似不起眼实际上体现了一个人对大数据工程的理解。我采用的目录结构是这样的/agri/raw存放从 MySQL 导出的原始数据按日期分区比如 /agri/raw/2025-03-18/。/agri/clean存放 PySpark 清洗后的数据同样按日期分区。/agri/stats存放统计结果比如品类价格汇总、市场排行。/agri/reco存放推荐模型输出的结果供 Web 接口读取。为什么要按日期分区因为农产品数据是典型的时间序列数据每天的爬取结果只有当天完整后续需要按天做增量处理和回溯分析。如果所有数据堆在一个目录里统计时全表扫描不仅慢而且逻辑混乱。按日期分区后PySpark 读取时可以直接指定分区路径处理效率和数据组织都清晰很多。数据入库流程上我选择的是“先 MySQL 后 HDFS”。爬虫 Pipeline 写入 MySQL 之后每天定时执行一个 Shell 脚本把前一天的数据导出为 CSV再用 hdfs dfs -put 命令上传到 HDFS。这里有一个小细节CSV 导出时建议去掉表头或者统一在 PySpark 读取时指定 headerTrue避免后续处理时多出一行脏数据。有的同学可能觉得绕了一圈有点多余为什么不直接让爬虫写 HDFS直接写的问题是爬虫产生的记录是逐条的每条写一个小文件会造成 HDFS 上文件数量爆炸影响 NameNode 性能和 Spark 读取效率。先攒到 MySQL 再批量导出正好规避了这个问题。这也是为什么我说工程方案要选“最合理的”而不是“看起来最直接的”。3.2 PySpark 数据清洗与统计分析数据进入 HDFS 之后就到了 PySpark 的主场。清洗这一步做得好不好直接决定后续统计和推荐模型的质量。我当时写了一个离线任务主要做这几件事删除价格为空或小于等于 0 的记录。按字段组合去重比如 name、market、date 三者相同视为重复。统一日期格式清洗非法日期。品类字段标准归一化把同义名称归到统一品类。对价格做异常值过滤比如超过该品类历史均值 3 倍的数据认为是录入异常。Python 代码示例如下from pyspark.sql import SparkSession from pyspark.sql.functions import col, to_date, avg, count spark SparkSession.builder \ .appName(AgriCleanAndStats) \ .getOrCreate() df spark.read.csv(hdfs://localhost:9000/agri/raw/2025-03-18/, headerTrue, inferSchemaTrue) # 清洗去空值、去重复、过滤异常 clean_df df.filter(col(price).isNotNull()) \ .filter(col(price) 0) \ .dropDuplicates([name, market, date]) # 标准化日期 clean_df clean_df.withColumn(date, to_date(col(date), yyyy-MM-dd)) # 按品类统计日平均价格 daily_stats clean_df.groupBy(category, date) \ .agg(avg(price).alias(avg_price)) \ .orderBy(date) daily_stats.write.csv(hdfs://localhost:9000/agri/stats/daily_price/, headerTrue, modeoverwrite)清洗逻辑里的去重非常重要因为爬虫可能因为重试机制把同一页面抓了两次或者同一个商品在列表页和详情页各出现一次。按 name、market、date 三元组去重是我根据农产品数据的实际情况摸索出来的比简单的全部字段去重更可靠。统计分析是为了支撑可视化大屏和推荐系统。除了日平均价格我还会计算每个市场的品类销量排行、产地分布占比、周度价格环比变化等。PySpark 的 DataFrame API 写这类聚合逻辑非常顺手一个 groupBy 加 agg 就能搞定。有一个经验是不要在 HDFS 上反复读取原始数据应该把清洗后的结果持久化到 /agri/clean 目录后续的统计和推荐都从 clean 目录读取这样整体运行时间能减少一半以上。3.3 推荐算法实现从 ALS 协同过滤到混合策略推荐系统是这个项目的技术亮点也是答辩时老师最容易追问的部分。我采用的是两层设计第一层是 PySpark MLlib 的 ALS 协同过滤第二层是基于农产品属性的内容推荐用来解决冷启动问题。ALS 算法需要用户对物品的评分数据。真实场景下农产品电商平台的用户行为通常是隐式的比如浏览、收藏、加入购物车、下单等并不会直接给出 1 到 5 分的评分。所以我做了一层转化浏览记 1 分收藏记 3 分下单记 5 分按用户和商品聚合生成评分矩阵。代码实现不算复杂from pyspark.ml.recommendation import ALS from pyspark.ml.evaluation import RegressionEvaluator ratings spark.read.csv(hdfs://localhost:9000/agri/clean/user_behavior/, headerTrue, inferSchemaTrue) train, test ratings.randomSplit([0.8, 0.2], seed42) als ALS(userColuser_id, itemColproduct_id, ratingColrating, coldStartStrategydrop, maxIter10, regParam0.1, rank10) model als.fit(train) # 评估模型效果 evaluator RegressionEvaluator(metricNamermse, labelColrating, predictionColprediction) predictions model.transform(test) rmse evaluator.evaluate(predictions) print(fRMSE: {rmse}) # 为每个用户生成Top10推荐 user_recs model.recommendForAllUsers(10) user_recs.write.csv(hdfs://localhost:9000/agri/reco/als_top10/, headerTrue, modeoverwrite)ALS 调参是个实践活。maxIter 决定了训练迭代次数太小模型欠拟合太大耗时增加且可能过拟合regParam 控制正则化强度值越大模型越保守rank 是隐因子个数影响模型的表达能力。我跑了一组对比实验最后把正则参数定为 0.1隐因子数定位 10RMSE 稳定在 0.8 左右对于毕设项目完全够用。冷启动问题怎么解决呢新用户没有任何行为记录ALS 无法给他推荐。我用的是基于属性的内容推荐根据用户注册时选择的偏好品类或当前浏览的商品在农产品数据中找相似商品。相似度的计算基于品类、产地、价格区间、季节性等特征用余弦相似度排序。这一段逻辑用 PySpark 实现起来也不难核心是把商品特征转成向量然后计算向量之间的相似度。值得注意的是推荐结果不能只给用户看几个商品名就完事了还需要附上推荐理由比如“因为您浏览过西红柿所以推荐同品类的新鲜番茄”。这也是提升答辩演示效果的一个重要设计能让评委直观感受到推荐系统是在“思考”的。4. 数据怎么看怎么推可视化大屏与 Web 端实现4.1 可视化大屏的图表选型与设计可视化部分是整个项目中最直观、答辩时最容易出彩的模块。我当时的思路不是堆图表数量而是围绕农产品推荐这个主题把最有价值的几个分析维度展示出来。最终大屏上放了四类图表折线图农产品价格趋势让人一眼看到不同品类的价格变化情况。柱状图销量排行展示当前市场最受欢迎的农产品Top10。饼图品类占比说明农产品类别分布情况。地图产地分布用散点图展示农产品来源地。前端可视化我选用的是 ECharts支持 Vue 和 React图表类型丰富且交互体验好。如果你希望更快搭建一个数据大屏页面也可以参考 AVue-Data 这类大屏模板方案它内置了很多大屏布局和图表组件省去自己写 CSS 和栅格布局的时间。不过我的建议是毕设还是至少自己手写一个图表组件这样才能在答辩时说清楚每个图表的配置逻辑。大屏设计里有一个容易被忽视的点数据从哪来。不能把 PySpark 的统计结果直接扔给前端展示正确的做法是统计结果落库大屏通过后端接口读取。我当时的流程是PySpark 统计完写入 MySQL后端提供 /api/stats/price-trend 等接口前端页面加载时调用接口拿数据再填充到 ECharts 的 option 中。这样明确了数据流向也方便后期更换数据源。4.2 后端 API 与前后端对接后端我用的是 FastAPI轻量、自带接口文档、支持异步很适合毕设项目。接口设计上我分成两类一类是可视化大屏的统计接口一类是推荐系统的推荐接口。统计接口返回的是大屏图表需要的数据比如价格趋势接口返回按日期排序的品类和均价数组销量排行接口返回商品名称和销量排名的列表。推荐接口稍微复杂一点需要接收用户ID返回推荐商品列表和推荐理由。FastAPI 的代码示例from fastapi import FastAPI from pydantic import BaseModel import pymysql app FastAPI() DB_CONFIG { host: localhost, user: agri, password: agri123, database: agri_db, charset: utf8mb4 } app.get(/api/stats/price-trend) def price_trend(category: str 茄果类, days: int 30): conn pymysql.connect(**DB_CONFIG) cursor conn.cursor() sql SELECT date, avg_price FROM daily_price WHERE category %s ORDER BY date DESC LIMIT %s cursor.execute(sql, (category, days)) rows cursor.fetchall() cursor.close() conn.close() return [{date: r[0].strftime(%Y-%m-%d), avg_price: r[1]} for r in rows] app.get(/api/recommend/{user_id}) def recommend(user_id: int, top_n: int 10): # 读取PySpark生成的推荐结果表返回TopN商品和推荐理由 return {user_id: user_id, items: [...]}后端接口写的多了之后有个体会代码本身不难难的是把数据准备到位。推荐结果表是 PySpark 离线生成的但如果用户行为数据每天都在更新推荐结果也要定时重算。我当时写了一个计划任务每天凌晨自动跑一遍清洗、统计和推荐任务把结果同步到 MySQL。这样前端任何时候打开都能看到当天最新的推荐数据。前后端联调的时候最容易出问题的是字段类型不匹配。比如 PySpark 输出的小数在 MySQL 里可能变成 DecimalJSON 序列化时容易出问题。解决办法是统一在后端做类型转换或者建表时就设置好字段类型为 float 或 double减少转换层的意外。4.3 推荐结果的落库与用户行为闭环推荐系统不是一次性任务。用户看了推荐商品之后产生了新的浏览或下单行为这些行为数据应该被采集回来作为下一轮模型训练的输入形成一个完整的闭环。我在 Web 端加了一个简单的行为上报接口用户点击某个推荐商品时前端发送一条事件记录到后端后端写入行为日志表。采集的这些行为数据最终会进入离线清洗流程和爬虫原始数据一起处理生成新的评分矩阵再训练新模型。虽然毕设阶段用户量有限但这个闭环设计在答辩时很加分因为它体现的是工程思维而不只是“做了一个算法演示”。为了保证大屏加载速度和推荐接口的响应速度我还在后端加了一层 Redis 缓存。热点统计接口的查询结果缓存 5 分钟推荐列表缓存 30 分钟。这样一来即使 PySpark 离线任务还没有跑完接口也能及时返回上一次的缓存结果避免大屏打开时白屏或长时间等待。5. 毕设路上最常见的坑问题排查实录5.1 PySpark 环境与 Python 进程的经典报错PySpark 的报错几乎是每个做大数据毕设的同学都会遇到的“开门红”。最经典的就是这条pyspark cannot run program python3 error13, Permission denied。原因是 Spark 在启动 Executor 时需要调用系统 Python 解释器但当前用户对 python3 没有执行权限或者 PYSPARK_PYTHON 环境变量没有正确设置。排查思路是先执行 which python3 确认解释器路径然后看该路径是否有执行权限接着在 Spark 配置中强制指定解释器export PYSPARK_PYTHON/usr/bin/python3 export PYSPARK_DRIVER_PYTHON/usr/bin/python3如果仍然不行检查 Spark 运行用户是否有权限访问该路径必要时用 chmod 修复权限。还有一个易踩的坑是本地明明有 Python3但 Spark 运行在三台虚拟机集群上其他节点没有安装 Python3也会报类似错误。我的建议是调试阶段尽量用伪分布式或者单机模式等流程完全通顺了再考虑集群否则排查跨节点环境问题会非常痛苦。另一个高频报错是 jar does not exist or is not a normal file: /usr/local/hadoop/share/hadoop/m... 这类信息看着像是缺 jar实际多半是 Hadoop 配置文件中的路径有问题或者环境变量没有 source 到当前会话。我遇到过的情况是改了 Hadoop 安装目录后hadoop-env.sh 里的 HADOOP_HOME 还是旧路径导致很多辅助 jar 找不到。修复方式就是逐项检查 core-site.xml、hdfs-site.xml、yarn-site.xml 中的路径配置然后重新 source /etc/profile。5.2 Scrapy 爬不到数据先查这三个地方Scrapy 爬虫跑完后只显示 proceed finished with exit code 0日志里没有任何 item 输出这是新手最容易懵的场景。程序确实正常执行了但什么都没抓到看到“finished”这个词让人误以为成功了实际上 parse 方法里一个 item 都没产生。这类问题我排查的顺序固定是三步第一步查看目标页面内容是否和预期一致。很多站点会有反爬验证页或登录跳转返回的 HTML 里根本没有商品列表。在爬虫里临时加一条 print(response.text[:500])看看返回的响应是什么能快速定位是不是被重定向了。第二步检查 CSS 选择器或 XPath 是否匹配。这一步最容易踩坑因为页面结构可能调整过之前写的选择器已经失效。用 Scrapy Shell 交互式调试是最快的办法直接 scrapy shell 目标URL然后反复测试选择器确认能选中数据再更新代码。第三步确认请求头是否完整。部分站点校验 Referer 或 Cookie请求头不全会返回空白列表。在 start_requests 里补全 headers或者用中间件统一配置能解决大部分问题。如果上述三步都没问题那就要看是不是被限流了。设置 DOWNLOAD_DELAY 和重试机制之后再次运行往往就有数据了。总之exit code 0 不代表成功一定要在日志里看到 item 的数量。5.3 Hadoop 集群稳定性的几个实战注意事项Hadoop 在毕设阶段一般跑的伪分布式模式虽然只有一台机器但依然有一堆小坑。第一个是磁盘空间问题HDFS 默认副本数是 3即使只有一台机器它也会把每个数据块复制三份副本如果集群只有一个 DataNode 且磁盘空间不大存几千条数据可能还无所谓但一旦数据量上来很快就会出现“Insufficient disk space”。我的方案是把副本数改小在 hdfs-site.xml 中设置 dfs.replication 为 1。第二个是文件小文件爆炸。Scrapy 爬虫如果直接把逐条数据写成小文件上传 HDFSNameNode 会不堪重负。前面我也提过先攒到 MySQL 再批量导出成一个或几个大 CSV是更合理的做法。这个经验是我实际运行一周后对比发现再多的代码技巧也救不了小文件带来的元数据压力。第三个是 HDFS 扩容和磁盘清理。毕设后期数据量大了会有 “HDFS 服务器扩容” 的需求但虚拟机上加磁盘相对麻烦更快的办法是清理不用的中间数据。我在 HDFS 上会定期删除 /agri/raw 下超过两周的原始数据保留 /agri/clean 和 /agri/stats 的产物。因为原始数据在 MySQL 中还有留存HDFS 里的中间副本删掉也不影响恢复。还有一个容易被忽略的问题Java 版本。Hadoop 3.x 对 Java 8 的支持最稳有些同学系统里装的是 Java 11 或 17启动时会出现各种奇怪的类加载错误。我的建议是直接用 Hadoop 官方文档推荐的 JDK 版本不要贪新求快毕设环境稳定压倒一切。整个大数据组件家族之间版本匹配是真正的玄学保持所有组件大版本一致能减少很多兼容性烦恼。6. 论文、PPT 与视频讲解让毕设完整交付6.1 论文怎么写才能撑起这个题目论文是毕设的重要交付物很多同学代码跑通了但对写作无从下手。我的经验是论文结构严格按标准模板走但内容上要突出“技术的落地逻辑”。一般框架是绪论、相关技术介绍、需求分析、系统设计、系统实现、系统测试、总结与展望。重点在系统设计和实现上大量放体系结构描述、流程图、截图和核心代码让评委能清晰看到你做了什么。论文里技术方案部分不能只是罗列 Scrapy 是什么、Hadoop 是什么而要说清楚你的系统里这些组件是怎么组织的。比如爬虫部分写“基于 Scrapy 的农产品数据采集模块”需要有爬虫流程图、管道处理说明、反爬策略分析。推荐系统部分写“基于 ALS 和内容推荐的混合推荐策略”要有评分矩阵构建过程、模型评估指标、冷启动解决方案。这些都是能体现工作量和技术深度的内容。写作过程中我建议图表文字。同一个模块一张系统流程图比三段文字描述更直观。但图不要用随机截屏凑数要有设计感统一配色、统一字体这些细节在答辩加分项里非常明显。另外论文中的代码不要全部贴大段只需保留关键方法和逻辑注释要清楚评审老师通常重点关注代码片段是否解决了某个具体问题。6.2 PPT 与视频讲解的准备思路PPT 的逻辑和论文不同论文是面面俱到PPT 是讲一个故事问题是什么、用了什么方案、效果怎么样。我的 PPT 控制在 18 页以内顺序是项目背景与意义、需求分析、技术选型、系统架构、爬虫模块、数据存储与处理、推荐算法、可视化展示、系统测试、总结。每一个模块尽量配一张截图比如爬虫运行日志、HDFS 文件列表、PySpark 执行结果、可视化大屏页面这样讲起来有凭有据。讲解视频也不要一边讲一边录那样会磕磕绊绊。我的做法是先写一个讲解脚本按模块口播然后分段落屏最后用剪映拼接。视频控制在 15 到 20 分钟前面讲背景 2 分钟技术架构 3 分钟重点放在爬虫和推荐系统的实现 8 分钟最后演示大屏和推荐效果 5 分钟。这样既不冗长也把项目的亮点全部覆盖。答辩现场还有一个容易被忽略的点提前准备好“如果环境跑不起来”的备选方案。我在演示时从不现场运行爬虫而是播放录好的演示视频因为现场网络、目标站点状态都不可控。推荐接口也准备了 Mock 数据作为兜底一旦真实环境出问题切到 Mock 数据也能完成演示。这种冗余准备是答辩顺利通过的重要保障。最后再说一点整套系统做下来我最大的体会是毕设项目不是技术越炫越好而是每一步都要能自圆其说。Hadoop 为什么存在、PySpark 解决什么问题、推荐结果怎么来的、数据流怎么闭环这些在答辩时被追问的深度往往取决于开发时对方案的理解程度。我带着这套系统去答辩时老师最感兴趣的不是代码量而是我解释“为什么要先 MySQL 再 HDFS”和“冷启动怎么处理”这两个点。所以说多问自己几个为什么比多敲几千行代码更有价值。
返回列表