简介:这份毕业设计论文文档面向计算机相关专业本科生,围绕《Hadoop+爬虫+Spark线上教育平台大数据分析系统》展开,适合正在准备大数据方向毕业设计、需要完整论文框架与实现思路的学生参考。压缩包内仅含1个docx文件,约6.25MB,为完整论文正文,涵盖绪论、开发技术、需求分析、概要设计、系统实现与系统测试等章节。论文以Python、MySQL、Django为基础,结合爬虫与Spark完成线上教育平台数据的采集、存储、处理与可视化分析,并给出管理员功能实现与数据可视化看板展示。读者可从中获取选题背景与意义、技术选型说明、功能与非功能需求拆解、数据库设计、模块流程及测试方法等完整写作素材,也可借鉴其章节组织与论证逻辑,用于搭建自己的论文结构或对照修改。目前已有100人学习,适合作为大数据分析类毕业设计的参考范本。
1. 从一份毕业设计标题拆出来的真实工程链路
线上教育平台每天产生大量行为数据:课程点击、视频播放进度、章节停留时长、搜索关键词、下单与退款记录。这些数据散落在 Web 日志、业务库和第三方接口里,单机脚本处理到几十万行就开始吃力。Hadoop + 爬虫 + Spark 这套组合,恰好对应了「采集 → 存储 → 计算 → 分析」四个环节,也是毕业设计里最容易被答辩老师追问技术细节的一条链路。
爬虫负责把课程目录、评论、价格等外部数据抓回来;Hadoop 的 HDFS 负责把原始数据和日志稳定落盘;Spark 负责清洗、聚合、指标计算,最后输出到可视化层。适合谁?适合正在做大数据方向毕业设计、需要一套能跑通又能讲清楚原理的本科生,也适合刚转数据开发、想用一个完整项目把 Hadoop 和 Spark 串起来的工程师。下面按采集、存储、计算、调优的顺序,把每一步落到可复现的命令和代码上。
2. 爬虫采集线上教育平台数据的落地写法
2.1 用 requests + BeautifulSoup 抓课程列表的最小闭环
线上教育平台的课程列表页通常是服务端渲染,结构稳定,用 requests 配合解析库就能拿到。先写一个最小可运行版本,把课程标题、价格、评分抓下来存成 CSV,后续再交给 Hadoop 处理。
import requests from bs4 import BeautifulSoup import csv import time HEADERS = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" } def fetch_courses(base_url, pages=5): rows = [] for page in range(1, pages + 1): url = f"{base_url}?page={page}" resp = requests.get(url, headers=HEADERS, timeout=10) resp.encoding = resp.apparent_encoding soup = BeautifulSoup(resp.text, "html.parser") for item in soup.select(".course-item"): rows.append({ "title": item.select_one(".title").get_text(strip=True), "price": item.select_one(".price").get_text(strip=True), "score": item.select_one(".score").get_text(strip=True), }) time.sleep(1) # 控制频率,避免给目标站点造成压力 return rows if __name__ == "__main__": data = fetch_courses("https://example-edu.com/courses") with open("courses.csv", "w", newline="", encoding="utf-8") as f: writer = csv.DictWriter(f, fieldnames=["title", "price", "score"]) writer.writeheader() writer.writerows(data)逻辑说明:fetch_courses按页码循环请求,select用 CSS 选择器定位课程卡片。time.sleep(1)是必须的,既降低被封风险,也符合基本的采集礼仪。参数上,timeout=10防止单次请求卡死,apparent_encoding解决中文乱码。如果目标页面是前端渲染,requests 拿不到数据,这时换 Playwright 更合适——它能等 DOM 渲染完成再取内容,代价是资源占用更高。
2.2 分布式爬虫与数据落盘的取舍
单机爬虫跑到几万条就会遇到瓶颈:IP 限速、解析慢、断点难恢复。常见做法是把 URL 队列放到 Redis,多个爬虫进程从队列里取任务,抓完的结果直接写本地文件,再统一上传 HDFS。这样做的原因是 HDFS 不适合高频小文件写入,先本地攒批再上传,能显著减少 NameNode 压力。
| 方案 | 适用规模 | 优点 | 代价 |
|---|---|---|---|
| 单机 requests | 万级以内 | 简单、调试快 | 慢、易断 |
| Redis 队列 + 多进程 | 十万级 | 可断点、可扩展 | 需维护 Redis |
| Playwright 渲染抓取 | 动态页面 | 能拿 JS 数据 | 内存占用高 |
落盘时统一用 UTF-8,字段之间用逗号或制表符分隔,避免后续 Spark 读取时解析歧义。抓完先本地wc -l确认行数,再执行上传:
hdfs dfs -mkdir -p /edu/raw/courses hdfs dfs -put courses.csv /edu/raw/courses/ hdfs dfs -ls /edu/raw/courses-mkdir -p递归建目录,-put上传本地文件,-ls验证结果。这一步做完,原始数据就进了 HDFS,后面 Spark 直接从这个路径读。
3. Hadoop 伪分布式环境搭建与 HDFS 数据组织
3.1 Hadoop 3 单机伪分布式的最小配置
毕业设计不需要真集群,伪分布式足够跑通全流程。核心是改三个配置文件,让 NameNode 和 DataNode 都跑在本机。先确认 JDK 已装,然后解压 Hadoop,进入etc/hadoop目录。
# core-site.xml 指定 HDFS 入口 <configuration> <property> <name>fs.defaultFS</name> <value>hdfs://localhost:9000</value> </property> </configuration> # hdfs-site.xml 设置副本数为 1 <configuration> <property> <name>dfs.replication</name> <value>1</value> </property> </configuration> # mapred-site.xml 指定用 YARN 跑任务 <configuration> <property> <name>mapreduce.framework.name</name> <value>yarn</value> </property> </configuration>参数说明:fs.defaultFS是客户端连接 HDFS 的地址,端口 9000 是默认值;dfs.replication伪分布式只有一台机器,必须设为 1,否则会一直报副本不足;mapreduce.framework.name设为 yarn 后,MapReduce 任务走 YARN 调度。改完执行格式化:
hdfs namenode -format start-dfs.sh start-yarn.sh jpsjps应该看到 NameNode、DataNode、ResourceManager、NodeManager 四个进程。少任何一个,先去看logs目录下对应的日志。常见坑是 SSH 免密没配好,start-dfs.sh会卡在输入密码;另一个是/tmp下残留旧数据导致 DataNode 起不来,清掉dfs.data.dir指向的目录再重启即可。
3.2 HDFS 目录规划与数据分层
数据进 HDFS 不能乱放,按「原始层 / 清洗层 / 结果层」分目录,后面 Spark 读写路径清晰,答辩时也好讲。
hdfs dfs -mkdir -p /edu/raw/courses # 爬虫原始数据 hdfs dfs -mkdir -p /edu/raw/logs # 平台行为日志 hdfs dfs -mkdir -p /edu/clean/courses # 清洗后数据 hdfs dfs -mkdir -p /edu/result/indicators # 分析结果分层的好处是每层职责单一:raw 层只追加不修改,clean 层由 Spark 写入,result 层给可视化读。如果后续要接 Hive,直接把外部表指向 clean 层即可,不用挪数据。注意 HDFS 不适合存大量小文件,爬虫抓下来的 CSV 最好按天合并成一个文件再上传,否则 NameNode 内存会被元数据撑爆。
4. Spark 清洗与指标计算的核心代码
4.1 Spark SQL 读取 HDFS 数据并做清洗
Spark 读 HDFS 上的 CSV,先定义 Schema,避免它把表头当数据。清洗主要做三件事:去空、去重、类型转换。
from pyspark.sql import SparkSession from pyspark.sql.functions import col, to_date, regexp_replace spark = SparkSession.builder \ .appName("EduCourseClean") \ .master("local[*]") \ .getOrCreate() df = spark.read.option("header", True).option("inferSchema", True) \ .csv("hdfs://localhost:9000/edu/raw/courses/courses.csv") clean = df.dropna(subset=["title", "price"]) \ .dropDuplicates(["title"]) \ .withColumn("price", regexp_replace(col("price"), "[^0-9.]", "").cast("double")) \ .withColumn("score", col("score").cast("double")) clean.write.mode("overwrite").parquet("hdfs://localhost:9000/edu/clean/courses")逻辑说明:dropna去掉关键字段为空的行,dropDuplicates按标题去重,regexp_replace把价格里的「¥」「元」等字符清掉再转 double。写 parquet 而不是 CSV,是因为 parquet 列式存储,后续聚合查询快很多。参数上,master("local[*]")用本机所有核,毕业设计够用;生产环境换成yarn提交。
4.2 用 Spark SQL 算课程热度与价格分布
清洗完直接建临时视图,用 SQL 算指标,比 DataFrame API 更直观,也方便写进论文。
clean.createOrReplaceTempView("courses") # 价格区间分布 spark.sql(""" SELECT CASE WHEN price = 0 THEN '免费' WHEN price < 100 THEN '0-100' WHEN price < 300 THEN '100-300' ELSE '300以上' END AS price_range, COUNT(*) AS cnt, ROUND(AVG(score), 2) AS avg_score FROM courses GROUP BY 1 ORDER BY cnt DESC """).show() # 按日期统计新增课程(假设有 create_date 字段) spark.sql(""" SELECT date_format(to_date(create_date), 'yyyy-MM') AS month, COUNT(*) AS new_courses FROM courses GROUP BY 1 ORDER BY 1 """).show()CASE WHEN做分桶,GROUP BY 1引用第一个表达式,date_format把日期转成月份。这里如果字段是字符串,先用to_date转换,否则date_format会报错。结果可以再写回 HDFS 的 result 层,供后续可视化读取。
4.3 Spark 内存与并行度参数怎么调
Spark 跑小数据看不出问题,数据一上来就 OOM 或跑得慢,多半是内存和并行度没配好。提交任务时用--executor-memory和--num-executors控制资源,本地模式则调spark.default.parallelism。
| 参数 | 作用 | 建议值 |
|---|---|---|
| spark.executor.memory | 每个 executor 内存 | 数据量 1GB 以内给 2g |
| spark.sql.shuffle.partitions | shuffle 后分区数 | 默认 200,小数据调到 8-16 |
| spark.default.parallelism | 本地模式并行度 | 设为 CPU 核数的 2-3 倍 |
小数据把spark.sql.shuffle.partitions从 200 降到 8,能减少大量空任务开销。数据倾斜时,先看spark.sql的 stage 详情,哪个 task 耗时特别长就是倾斜点,常见做法是加盐打散 key 再聚合。
5. 分析结果验证与可视化前的数据检查
5.1 用 Spark 结果反查清洗是否漏数据
分析跑完不能直接画图,先做一致性检查:清洗后的行数应该小于等于原始行数,且关键字段无空值。
raw_count = spark.read.csv("hdfs://localhost:9000/edu/raw/courses/courses.csv", header=True).count() clean_count = spark.read.parquet("hdfs://localhost:9000/edu/clean/courses").count() print(f"raw={raw_count}, clean={clean_count}, dropped={raw_count - clean_count}") # 检查是否有空值残留 spark.read.parquet("hdfs://localhost:9000/edu/clean/courses") \ .filter(col("price").isNull() | col("score").isNull()) \ .count()如果 dropped 数量异常大,回去看爬虫是不是抓到了空卡片;如果空值检查不为 0,说明清洗条件写漏了。这一步花几分钟,能避免后面图表数据对不上被追问。
5.2 导出结果到本地做可视化
Spark 结果默认是目录,多个 part 文件,导出时合并成一个 CSV 再交给前端或 Excel。
hdfs dfs -getmerge /edu/result/indicators/price_range ./price_range.csv-getmerge把目录下所有 part 文件合并下载到本地一个文件。如果结果里有中文,确认导出时编码是 UTF-8。可视化层用 ECharts 或 Pyecharts 都行,数据源就是这份 CSV。注意别在 Spark 里直接连数据库写结果,毕业设计阶段导出文件更稳,也方便反复调整图表。
6. 让整套流程可复现的三个技巧
第一个技巧是把所有路径和参数抽到配置文件里。爬虫的 base_url、HDFS 的根路径、Spark 的 master 地址,全部写进config.yaml,代码里读配置。这样换环境只改一个文件,答辩演示时不会因为路径写死而翻车。
import yaml with open("config.yaml") as f: cfg = yaml.safe_load(f) base_url = cfg["crawler"]["base_url"] hdfs_root = cfg["hdfs"]["root"]第二个技巧是给每个阶段加运行日志和耗时统计。爬虫记录抓了多少条、失败多少条;Spark 任务记录每个 stage 耗时。出问题时能快速定位是采集慢还是计算慢,而不是从头猜。
第三个技巧是保留一份最小可运行数据集。把爬虫抓到的前 1000 行单独存一份sample.csv,调 Spark 逻辑时先用它跑,秒级出结果,逻辑验证通过再换全量数据。这样迭代速度快,也不容易把集群跑挂。最后,所有命令按顺序写进一个run.sh,从建目录到导出结果一条龙,换台机器也能照着跑通。
本文还有配套的精品资源,点击获取