每年到了毕设季,就会看到一大票同学在“大数据方向”和“能顺利毕业”之间反复横跳。有人一上来就整分布式集群,三台虚拟机还没配完就崩溃了;有人用爬虫抓了两天数据,结果反爬都过不去;还有人推荐算法调了一星期,最后发现相似度算出来全是零。而“Hadoop+PySpark+Scrapy爬虫农产品推荐系统”这个题目,属于大数据毕设里少见的“看着高大上、实际也能跑通”的组合:既有爬虫,又有大数据存储和计算,还有推荐算法和可视化,整套链路非常完整,工作量足够撑起一篇毕业论文,而且每一块技术都是面试能聊的东西。
这个题目说白了就是要做四件事:用Scrapy把某个农产品信息网站的数据抓下来,存到Hadoop的HDFS上,再用PySpark做数据清洗和统计分析,把结果灌进推荐系统和可视化大屏。推荐系统解决“给用户推荐什么农产品”的问题,可视化解决“数据怎么看”的问题,最后配合LW文档、PPT和讲解视频,把整个项目从设计到实现完整呈现出来。
这篇文章我就按我自己做这个项目的顺序来拆,从爬虫到HDFS、从PySpark到推荐算法、从可视化到答辩准备,把每一步的选型逻辑、实操细节和踩过的坑都写清楚。不管你是准备拿这个题目做毕设,还是想练手大数据全流程,应该都能从中找到可以直接参考的东西。
1. 整体架构设计与技术选型逻辑
1.1 整套系统到底由哪几块组成
先画一下这个项目的基本盘。整个系统是典型的大数据离线处理架构,业务链路是“数据采集 -> 数据存储 -> 数据计算 -> 数据应用”:
- 数据采集层:Scrapy爬虫,从目标网站抓取农产品的名称、产地、价格、规格、评分、销量、描述等信息,导出为结构化数据。
- 数据存储层:Hadoop HDFS,把爬虫产出的原始数据落盘,作为整个系统的数据底座。
- 数据计算层:PySpark,对HDFS上的原始数据做ETL清洗、统计分析、特征工程,产出推荐系统需要的数据和可视化需要的指标数据。
- 数据应用层:推荐系统负责计算用户与农产品之间的推荐关系;可视化模块把统计结果用图表展示在Web页面上。
- 辅助交付层:LW文档、PPT、讲解视频,对应毕业设计的论文、答辩和演示材料。
我当时搭这套架构的时候,最深刻的感受是:“层”与“层”之间的数据流转是最容易出问题的地方。比如爬虫导出的CSV里有重复数据,到了PySpark那边做分组统计,数字直接翻倍;再比如推荐系统读的是处理完的CSV,但可视化那边读的是另一份统计结果,两边数字对不上,答辩的时候一问就露馅。所以开头就要把数据流向理清,尽量做到一份数据多处复用。
1.2 为什么选Hadoop+PySpark这套组合,而不是别的
很多同学纠结要不要上Spark Standalone集群,或者直接用MySQL就完事了。我的建议是:毕设场景下,Hadoop伪分布式 + PySpark本地模式完全够用,既体现了大数据技术栈,又不会把时间全耗在环境搭建上。
选HDFS而不是MySQL当主存储,是因为这个题目的核心是“大数据”,不是“业务系统”。HDFS适合存大规模文件,适合批处理;MySQL更适合OLTP事务场景。你想想,爬虫爬下来的数据是几万到几十万条农产品信息,这些数据大多是追加写入、偶尔更新,几乎没有细粒度的事务操作,这种场景HDFS天然合适。
PySpark则是处理HDFS数据最顺手的工具。它的DataFrame API跟Pandas很像,但底层是分布式的。比如做数据清洗、分组统计、Join操作,用PySpark写起来和Pandas差不多,但数据量一旦过百万行,Pandas直接卡死,PySpark依然流畅。
至于为什么不用Java写MapReduce——因为开发效率太低。毕设时间有限,用Python写Spark作业能省一半时间,而且代码量少,论文里还好解释。真到了大厂里面,PySpark也是数据分析岗的重度使用工具,学了不亏。
提示:如果你实在对集群环境不熟,连伪分布式都配不好,可以退一步用HDFS的本地模式(即直接读写本地文件系统),但论文里一定注明“生产环境建议部署为分布式集群”。我见过好几个同学因为环境搭不起来直接弃题,其实这就是没搞清楚毕设和工业界的区别。
2. 农产品数据采集:Scrapy爬虫落地实录
2.1 数据源分析与字段设计
做爬虫第一步不是写代码,而是定数据源和字段。农产品推荐系统的核心数据是“农产品信息”,那围绕“推荐”这个目标,最少需要以下字段:
- 农产品ID(唯一标识)
- 名称(如“烟台红富士苹果”)
- 品类(水果/蔬菜/粮油/肉类)
- 产地(山东烟台/陕西洛川)
- 价格(单价元/500g)
- 销量(月销量/累计销量)
- 评分(用户打分,用于推荐加权和可视化)
- 上架时间(用于分析时效性)
- 商品描述(做文本特征)
字段不是越多越好,而是每一列都要能在后面用上。我见过有的同学把网页里所有字段全爬下来,光CSV就有50多列,最后PySpark处理的时候一半字段没用上,论文里的“数据清洗”章节反而不好写。反过来,字段太少也不行——推荐系统没有价格、评分、品类做特征,算法就无从谈起。
2.2 Scrapy项目的核心结构搭建
Scrapy建项目很简单,一条命令就完事:
scrapy startproject agri_spider cd agri_spider scrapy genspider agri_product www.example-agri.com但真正关键的是Item定义。Item就好比是爬虫和数据管道之间的“合同”,规定了抓下来的数据长什么样。我在项目里是这样定义的:
# items.py import scrapy class AgriProductItem(scrapy.Item): product_id = scrapy.Field() # 农产品ID name = scrapy.Field() # 名称 category = scrapy.Field() # 品类 origin = scrapy.Field() # 产地 price = scrapy.Field() # 价格 sales = scrapy.Field() # 销量 rating = scrapy.Field() # 评分 publish_time = scrapy.Field() # 上架时间 description = scrapy.Field() # 描述Item定义好之后,爬虫文件里最重要的一块就是解析函数。用CSS选择器还是XPath都可以,我的习惯是用XPath,因为定位复杂元素结构时更精确。核心代码如下:
# spiders/agri_product.py import scrapy from agri_spider.items import AgriProductItem class AgriProductSpider(scrapy.Spider): name = "agri_product" def parse(self, response): # 通过列表页定位每个商品块 products = response.xpath('//div[contains(@class, "product-item")]') for product in products: item = AgriProductItem() item['name'] = product.xpath('.//a[@class="name"]/text()').get() item['price'] = product.xpath( './/span[@class="price"]/text()' ).re_first(r'[\d.]+') item['origin'] = product.xpath( './/span[@class="origin"]/text()' ).get() # ... 其他字段解析 yield item # 翻页 next_page = response.xpath( '//a[@class="next-page"]/@href' ).get() if next_page: yield scrapy.Request( response.urljoin(next_page), callback=self.parse )这里特别提醒一个点:re_first(r'[\d.]+')是我处理价格字段的惯用写法,因为页面上价格经常是“¥12.50/500g”这种带符号带单位的形式,直接用正则抽出数字再转float最稳。这个习惯帮我避免了很多次数据解析报错。
2.3 数据管道:清洗和落盘的最后一公里
爬虫抓到数据之后,会统一进入Pipelines处理。这里做的事情有:去重、字段校验、类型转换、规范化产地名称、最终写CSV或JSON文件。
# pipelines.py import csv class AgriPipeline: def open_spider(self, spider): self.file = open('agri_products.csv', 'w', encoding='utf-8-sig', newline='') self.writer = csv.DictWriter(self.file, fieldnames=[ 'product_id', 'name', 'category', 'origin', 'price', 'sales', 'rating', 'publish_time', 'description' ]) self.writer.writeheader() def process_item(self, item, spider): # 简单去重:价格为空或名称为空则丢弃 if not item.get('name') or not item.get('price'): return item # 价格转float,销量转int item['price'] = float(item['price']) item['sales'] = int(item.get('sales', 0)) self.writer.writerow(item) return item def close_spider(self, spider): self.file.close()在Pipelines里做数据清洗有个好处:数据在源头就被修正了,后面HDFS和PySpark拿到的都是干净数据。你要是把清洗全放在Spark那边做,也不是不行,但爬虫这边能做的就没必要让Spark重复劳动。
2.4 反爬策略与调试技巧
只要是爬虫,就绕不开反爬。农产品网站反爬相对温和,但也遇过以下情况:
- User-Agent检测:伪装成正常浏览器的UA,Scrapy设置
DEFAULT_REQUEST_HEADERS即可。 - Request频率限制:在
settings.py里加DOWNLOAD_DELAY = 1.0,每1秒请求一次,礼貌爬取。 - 页面动态渲染:如果目标站是JS渲染的,Scrapy默认抓不到数据,需要搭配Selenium或Playwright。但这个项目我建议尽量选静态页面或SSR渲染的网站,否则爬虫复杂度会直线上升。
- IP封禁:如果爬得太快被临时封IP,直接加代理中间件。但毕设场景一般用不到。
调试方面,我最常用的两个命令是:
# 在shell环境里快速测试解析逻辑,不用启动整个爬虫 scrapy shell https://www.example-agri.com/product/123 # 查看响应内容是否正常 response.xpath('//title/text()').get()scrapy shell是爬虫开发的神器,写XPath之前先在这里试一遍,比反复跑爬虫看日志高效得多。
3. 大数据处理:数据上HDFS与PySpark分析
3.1 HDFS目录规划与文件导入
爬虫产出的agri_products.csv只是一个本地文件,要让Hadoop“管理”它,就得把它上传到HDFS。但直接一股脑丢进去不是好习惯,最好按层级规划目录:
# 在HDFS根目录下创建项目目录结构 hdfs dfs -mkdir -p /user/agri_project/raw_data hdfs dfs -mkdir -p /user/agri_project/clean_data hdfs dfs -mkdir -p /user/agri_project/analysis_output # 上传爬虫原始数据 hdfs dfs -put ./agri_products.csv /user/agri_project/raw_data/这套目录规划的好处是:原始数据、中间数据、结果数据互不干扰。你不小心跑错一个任务,最多污染一层目录,不会把整个数据链路搞乱。答辩的时候老师问“你的数据是怎么管理的”,你直接把这个目录设计讲出来,就是一个加分项。
3.2 PySpark完成数据清洗与ETL
原始数据虽然爬虫那边做了基础清洗,但到了HDFS上还得再做一轮,原因有二:一是爬虫管道里的清洗是“尽力而为”,不一定覆盖所有脏数据;二是PySpark的清洗逻辑能直接写进论文的“数据处理”章节,体现完整的大数据ETL能力。
PySpark连接HDFS最简单的方式是直接读CSV:
# etl.py from pyspark.sql import SparkSession from pyspark.sql.functions import col, when, lower, regexp_replace, avg spark = SparkSession.builder \ .appName("AgriculturalProductETL") \ .getOrCreate() # 读取HDFS上的原始数据 df = spark.read.csv( "hdfs://localhost:9000/user/agri_project/raw_data/agri_products.csv", header=True, inferSchema=True ) print("原始数据量:", df.count()) # 去除空值和重复数据 df_clean = df.dropna(subset=['product_id', 'name', 'price']) df_clean = df_clean.dropDuplicates(['product_id']) # 统一品类和产地的大小写,去掉多余空格 df_clean = df_clean.withColumn( 'category', lower(regexp_replace(col('category'), r'\s+', '')) ) df_clean = df_clean.withColumn( 'origin', lower(regexp_replace(col('origin'), r'\s+', '')) ) # 过滤掉价格异常数据 df_clean = df_clean.filter( (col('price') > 0) & (col('price') < 10000) ) # 写出清洗后的数据 df_clean.write.csv( "hdfs://localhost:9000/user/agri_project/clean_data/agri_clean", header=True, mode="overwrite" )inferSchema=True会自动推断字段类型,但注意如果某一列混入了非数字内容,推断可能会出错。稳妥的做法是在读进来之后再显式指定Schema。这个我后面会再提,因为踩过坑。
3.3 统计分析:为可视化和推荐准备数据
清洗之后就要做统计了。这里的核心目标有两个:一是给可视化大屏提供聚合指标,二是给推荐系统提供用户或商品的辅助特征。
常见统计任务包括:
- 各品类农产品数量分布
- 产地分布Top10
- 价格区间分布
- 月销量Top10农产品
- 各品类平均价格对比
- 高评分农产品榜单
- 价格随时间变化的趋势
用PySpark实现这些统计非常直接。比如统计各品类产品数:
# analysis.py from pyspark.sql.functions import count, avg, round df_clean = spark.read.csv( "hdfs://localhost:9000/user/agri_project/clean_data/agri_clean/*.csv", header=True ) # 各品类产品数 category_count = df_clean.groupBy("category") \ .agg(count("product_id").alias("product_count")) \ .orderBy(col("product_count").desc()) # 产地Top10 origin_top10 = df_clean.groupBy("origin") \ .agg(count("product_id").alias("product_count")) \ .orderBy(col("product_count").desc()) \ .limit(10) # 各品类平均价格 category_price = df_clean.groupBy("category") \ .agg(round(avg("price"), 2).alias("avg_price")) \ .orderBy(col("avg_price").desc()) # 销量Top10 sales_top10 = df_clean.orderBy(col("sales").desc()).limit(10)最后把结果统一导出成CSV,供可视化模块读取:
category_count.write.csv( "hdfs://localhost:9000/user/agri_project/analysis_output/category_count", header=True, mode="overwrite" )很多同学到这里会犯一个错误:每个统计结果都单独导出一份CSV,最后可视化那边文件一大堆,管理混乱。我建议把所有统计结果合并成一个DataFrame,或者至少放在同一个输出目录下,让前端一次性读取。
4. 推荐算法实现:基于协同过滤的农产品推荐
4.1 推荐场景分析与算法选型
推荐系统在这套项目里是最能体现“技术含量”的部分。但农产品推荐跟电影推荐不一样,它没有大量显式的用户评分数据,更常见的是“用户浏览过什么”“用户购买过什么”这类隐式反馈。所以我在设计时做了取舍:用“已有用户对农产品的评分/购买记录”来模拟用户行为数据。
推荐算法我选的是基于物品的协同过滤(Item-based Collaborative Filtering),原因有两条:一是对冷启动相对友好,当用户行为少时,基于物品的相似度计算比基于用户的更稳定;二是农产品属于品类相对固定的商品,用户更倾向于“买了苹果之后还想买梨”这种同类目推荐,物品相似度天然比用户相似度更贴合场景。
它的核心思想很简单:如果用户A购买了商品X,而商品X与商品Y相似度很高,那么就把Y推荐给A。相似度不是靠肉眼判断,而是靠“有多少人同时购买X和Y”来量化。
4.2 相似度计算的数学原理与代码实现
物品相似度的核心在于“共同评分向量”。假设有m个商品,n个用户,我们可以构造一个用户-商品评分矩阵,矩阵中每个元素表示某个用户对某个商品的评分(或购买次数)。两个商品的相似度,就是它们列向量之间的余弦相似度。
余弦相似度的公式是:
similarity(A, B) = (A·B) / (|A| * |B|)其中A和B分别是商品A和商品B的评分列向量。分子是两个向量的点积,分母是两个向量模长的乘积。结果越接近1,表示两个商品被同一批用户喜欢/购买的程度越高。
PySpark实现这个计算,最直观的方式是先把数据转成“用户-商品”的宽表,再用pyspark.mllib.recommendation或pyspark.ml.recommendation里的协同过滤API。但为了在论文里能讲清原理,我用更原始的DataFrame操作来实现一遍:
# recommend.py from pyspark.sql import SparkSession from pyspark.sql.functions import col, collect_list, struct from pyspark.sql.types import DoubleType spark = SparkSession.builder \ .appName("ItemCFRecommendation") \ .getOrCreate() # 读取行为数据:user_id, product_id, rating behavior_df = spark.read.csv( "hdfs://localhost:9000/user/agri_project/clean_data/user_behavior.csv", header=True, inferSchema=True ) # 计算每个商品的评分向量(这里简化为按商品聚合用户评分) product_ratings = behavior_df.groupBy("product_id") \ .pivot("user_id") \ .avg("rating") \ .fillna(0)这一步pivot会把原始的长表转成宽表,每一行是一个商品,每一列是一个用户。然后我们计算商品两两之间的余弦相似度。当然商品数量多的时候,两两计算会非常慢,所以实际实现中我会抽样一部分商品或者用ALS替代。毕设场景下,商品数量在几千个以内,这种实现是够用的。
4.3 推荐结果的生成与业务逻辑封装
相似度矩阵算出来之后,生成推荐就简单了:对每个用户,找出他评分最高的N个商品,跟这些商品最相似的商品就是候选推荐集合,再过滤掉用户已经买过的商品,最终返回M个推荐结果。
def generate_recommendations(user_id, liked_product_ids, similarity_df, top_n=5): rec_scores = {} for pid in liked_product_ids: # 找到与pid最相似的K个商品 similar_items = similarity_df.filter(col("product_id") == pid) \ .orderBy(col("similarity").desc()) \ .limit(10) for row in similar_items.collect(): rec_id = row['similar_product_id'] if rec_id in liked_product_ids: continue # 过滤已购买 rec_scores[rec_id] = rec_scores.get(rec_id, 0) + row['similarity'] # 按相似度加权求和排序,取TopN ranked = sorted(rec_scores.items(), key=lambda x: x[1], reverse=True) return [pid for pid, score in ranked[:top_n]]这段逻辑看着简单,但有几个细节值得展开:
- 加权方式:我用的简单累加,也可以根据用户对原商品的评分做加权,评分高的商品贡献的相似度权重更大。后者更合理,但代码复杂度略高。
- 模型评估:如果论文里需要写推荐效果的评估,可以用离线方式把行为数据切成训练集和测试集,算准确率、召回率或F1值。比如“用户实际购买了商品Y,而推荐列表里包含Y”就算一次命中。不用做在线评估,因为毕设没有真实用户。
4.4 推荐算法在毕设中的合理定位
这里说句实在话:很多毕设的推荐系统都是“演示级别”的,能够跑通流程、产出一个推荐列表,就已经达到答辩要求了。但你要能在论文里和答辩时讲清楚“为什么选这个算法”“数据怎么构造”“相似度怎么计算”“结果怎么评估”,这才是区分度高低的所在。
我认识的不少同学,拿到题目就抄一段协同过滤代码,但问一句“为什么用余弦相似度而不是欧氏距离”就答不上来。这个问题的标准回答思路是:余弦相似度衡量的是方向上的差异,对评分的绝对数值不敏感;欧氏距离衡量的是数值差异,更适合连续数值且量纲一致的场景。在用户-商品评分矩阵里,我们更关心用户“喜欢的方向”是否一致,而不是评分绝对大小是否相同,所以余弦相似度更合理。
5. 农产品数据可视化:从指标设计到图表落地
5.1 可视化指标体系怎么搭
很多同学做可视化特别容易犯一个毛病:拿到数据就开始画图,画完一堆图却不知道想表达什么。可视化不是“画得好看”,而是“把数据里的规律讲出来”。
农产品推荐系统这个题目,核心关切是“农产品的市场结构与用户偏好”,所以我设计的可视化面板围绕四个问题展开:
- 市场上有多少种农产品、怎么分布?(品类分布饼图/柱状图)
- 农产品都来自哪里?(产地分布地图/柱状图)
- 价格和销量有什么关系?(价格区间分布、销量排行)
- 哪些农产品口碑好?(高评分榜单、评分分布)
来看这几个指标是不是跟前面的统计分析对上了。也就是说:先定指标,再定图表,最后再去写PySpark的统计代码。如果顺序反了,就会出现“统计结果很多,但可视化用不上”的尴尬。
5.2 ECharts图表在Web前端中的接入
可视化前端我选的是ECharts,原因是配置灵活、图表类型多、文档齐全,特别适合快速搭建数据面板。如果团队里有同学熟悉Vue/React,也可以配合框架使用;如果不熟,就用最朴素的HTML+JavaScript把数据读进来渲染。
一个典型的ECharts接入流程是这样的:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>农产品数据分析面板</title> <script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script> </head> <body> <!-- 图表容器 --> <div id="category_chart" style="width: 600px; height: 400px;"></div> <script> // 模拟从后端接口拿到的数据 const categoryData = [ { name: '水果', value: 320 }, { name: '蔬菜', value: 240 }, { name: '粮油', value: 180 }, { name: '肉类', value: 120 } ]; const chart = echarts.init(document.getElementById('category_chart')); chart.setOption({ title: { text: '农产品品类分布' }, tooltip: { trigger: 'item' }, series: [{ type: 'pie', radius: '60%', data: categoryData }] }); </script> </body> </html>实际项目中,数据不能像上面那样硬编码,需要通过后端接口读。最简单的方式是用Flask写几个只读接口,把PySpark导出的统计CSV读进内存,返回JSON给前端。
# app.py from flask import Flask, jsonify import pandas as pd app = Flask(__name__) @app.route("/api/category_count") def category_count(): df = pd.read_csv("output/category_count/*.csv") data = df.to_dict(orient="records") return jsonify(data) if __name__ == "__main__": app.run(host="0.0.0.0", port=5000)前端再用fetch调用这个接口,把返回的数据填进ECharts的setOption里。这里就体现了我前面说的“统计结果统一管理”的好处:后端接口从固定目录读数据,加新图表时几乎不用改后端代码。
5.3 前后端联调时绕不开的几个细节
联调是最容易出“小问题”的地方,但这些问题往往耗掉大把时间。我遇到过的几个典型情况:
- 编码问题:PySpark导出的CSV是UTF-8编码,但Windows下的浏览器或Pandas读取时默认可能是GBK,出现乱码。解决办法是在PySpark写出时指定
encoding="utf-8",或者后端读取时显式指定encoding="utf-8"。 - 数据格式不匹配:ECharts的饼图要求
{ name, value }结构,而柱状图要求{ category: [...], value: [...] }的数组格式。后端直接返回原始CSV的数据字典往往不能直接用,最好在后端就做一次格式转换。 - 数字精度:PySpark算出来的平均价格可能带很多位小数,传给前端时要做保留两位或一位小数的处理,不然图表标签上一串小数很难看。
- 跨域问题:Flask默认是同源的,如果前端是单独打开的HTML文件(file协议),请求
http://localhost:5000/api/...时可能存在跨域限制。解决办法是用Flask-CORS开启跨域:CORS(app)。
6. 环境部署、性能调优与毕设答辩准备
6.1 从零搭建开发测试环境的清单
这个项目涉及的技术栈比较多,环境搭建本身就是一个不小的坎。我的环境清单如下,尽量选兼容性好的搭配:
- Linux系统(Ubuntu 20.04/22.04,虚拟机即可),Hadoop对Windows的原生支持很差,不建议在Windows上硬调。
- JDK 1.8(Hadoop 3.x依赖)
- Hadoop 3.x,伪分布式模式(默认单节点)
- Python 3.8+,配套PySpark 3.x
- Spark 3.x(PySpark与Spark版本要一致)
- MySQL(可选,用于存用户行为数据和推荐结果)
- Flask + ECharts做web展示层
安装顺序上,先装JDK,再解压Hadoop改配置,再装Python和PySpark。每次装完都要在命令行确认一下版本,不要等到写代码跑任务才发现环境不对。
6.2 运行性能调优的几个经验
虽然毕设数据量不大,但该关注性能的地方还是要注意,不然答辩演示时卡顿或报错就很尴尬。
- Spark任务默认分配的Executor内存可能不够,跑大数据量或复杂操作时报OOM。提交任务时加上参数:
spark.executor.memory=2g。 pivot操作非常吃内存,如果商品数量多,建议先做过滤或抽样,缩小矩阵规模。- PySpark写CSV时会产生多个Partition文件,文件名类似
part-00000-xxx.csv,这种现象很正常。但后端读取时要用通配符*.csv,如果只写一个具体的part文件名,换了任务或重新跑就会出错。 - 爬虫采集时控制频率,
DOWNLOAD_DELAY设1~2秒,大概几分钟到十几分钟就能爬完几千条数据,不会影响整体进度。
6.3 毕设文档、PPT与讲解视频的准备思路
题目里包含了“LW文档+PPT+讲解视频”,这些都是毕设答辩的硬性材料。LW文档(论文/设计报告)的结构一般按照毕业设计标准模板来,不要天马行空。我的建议是:
论文结构可以对照系统架构来写:
- 绪论:写背景、意义、国内外研究现状
- 相关技术介绍:爬虫技术、Hadoop、Spark、推荐算法、可视化技术
- 系统需求分析与设计:功能需求、非功能需求、总体架构、数据库设计
- 系统实现:爬虫模块、数据处理模块、推荐模块、可视化模块,配核心代码和截图
- 系统测试:功能测试、性能测试、测试结论
- 总结与展望
PPT的要点是“图多字少、逻辑清楚”。每页PPT放一个核心内容:架构图放一页、爬虫模块放一页、推荐算法流程放一页、可视化效果图放一页。不要贴大段代码,用关键代码片段说明逻辑即可。
讲解视频的录制建议是先演示系统再讲技术。演示系统时,按“数据采集 -> 数据展示 -> 推荐功能”的顺序操作,边说边点。讲解技术时,PPT跟着翻,重点讲清楚“怎么做”和“为什么这么做”。视频时间控制在5-8分钟即可,语速不要太快,保证每个模块都有画面支撑。
注意:论文中提到的数据和代码一定要能复现。答辩时老师很可能现场让你跑一次爬虫或者展示一下推荐结果。提前准备好干净的运行环境,并把数据链路完整走一遍,确保演示时不出岔子。
6.4 常见问题速查表
把项目里最容易出问题和最常被问到的点汇总一下:
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| Scrapy爬虫抓不到数据 | 页面动态渲染/反爬拦截 | 先scrapy shell测静态内容,必要时加插件 |
| HDFS导入文件失败 | 未启动Hadoop服务/端口占用 | start-dfs.sh启动,检查jps进程 |
| PySpark读CSV报错 | 文件路径写错/Schema推断失败 | 显式指定schema,用通配符读目录 |
| 推荐结果全是空 | 行为数据太少/相似度阈值太高 | 增加行为数据,降低过滤阈值 |
| 可视化页面白屏 | 跨域/数据格式不对 | 加Flask-CORS,检查接口返回JSON格式 |
| 运行OOM | Executor内存不足 | 调整spark.executor.memory参数 |
这个表可以直接放进论文的“常见问题分析”或“系统测试”章节,既展示了你的问题排查能力,也能在答辩前帮自己理清思路。
7. 复盘与经验杂谈
整套项目做下来,我自己最深的体会是:这种综合性的毕设题目,真正的难点不在任何一个单一技术,而在“技术栈之间的衔接”。爬虫爬得再好,数据格式不规范,后面Spark全部白做;Spark算得再快,输出目录管理混乱,可视化那边连读都读不对。
有一件事我印象特别深:有次我把PySpark统计结果输出到一个目录,后来又重跑了一次任务,目录里多了好几个part文件。前端读取的时候只读了最早那个文件,导致图表数据少了一半。排查了半天才发现是文件没合并。后来我把输出统一改成:每次任务先删除旧目录再写新目录,前端一律用通配符读取。这个习惯后来帮我省了无数次麻烦。
如果你准备用这个题目,我的建议是给自己排一个阶段性的时间表:一至两周搭环境和爬虫,一周做Hadoop+PySpark的数据处理,一周做推荐算法,一周做可视化,再留一周写论文和准备答辩。别卡在环境搭建上超过三天,实在搞不定就找人问,或者退回本地模式,先把流程走通再说。
另外一个小技巧:把整个项目的运行过程录下来,每一步怎么启动服务、怎么跑爬虫、怎么提交Spark任务、怎么打开可视化页面,都录成操作录像。这不光是讲解视频的素材,更是你自己排查问题时的重要参考。有时候系统过了两周你再打开,完全不记得当时是怎么启动的了——有录像就好办得多。
这里分享一个多数教程不会提到的经验:做这类大数据毕设时,数据一定要“看着真、用着稳”。最好是找真实的农产品公开数据网站,抓取真实价格和产地信息。哪怕数据量不算大,只要数据链路是完整的——从爬虫到HDFS、从Spark到推荐到可视化——就足以支撑一篇合格的毕设论文。千万别用纯手工编的数据糊弄全过程,盘点的时候你的分数会被拉低很多。
最后说一句实在话:这个题目确实需要花时间,但它值得。整套做完,你对大数据处理的认知会从一个模糊的“听说过”变成“我真的把数据从零到一跑通了”,这种底气和自信,是任何捷径都给不了的。