数据科学与大数据技术毕设题目实战:从选题到可部署系统的完整路径
摘要:毕设最怕“跑不通”。本文用一次真实落地的“实时舆情分析”项目,把从选题、搭环境、写代码到部署上线的完整踩坑记录摊开讲,目标是让任何一位本科/硕士同学都能复现并扩展成自己的毕业设计。
1. 典型痛点:为什么 70% 的大数据毕设“演示必崩”
数据源不可靠
老师一句“最好有真实数据”,结果大家去爬微博,反爬升级 302 跳转+滑块,IP 秒封,数据断档,PPT 里只能贴“模拟数据”。模型无法复现
Jupyter 里跑得好好的,拖到实验室 Ubuntu 机器上import torch直接 Segmentation fault,版本差 0.0.1 就全崩。系统无法演示
答辩现场 5 分钟,一打开 Grafana 面板空白,后台 Spark 任务早因内存溢出被 YARN kill,只剩一张“架构图”硬撑。
一句话:理论堆砌易,工程落地难。下面用一条“能跑起来”的流水线,把坑填平。
2. 技术栈选型:Spark vs Flink vs Dask 速览
| 维度 | Spark 3.4 | Flink 1.17 | Dask 2023.6 |
|---|---|---|---|
| 批处理 | 成熟、内置优化 | 支持但语法略重 | 轻量、与 Pandas 无缝 |
| 流处理 | Structured Streaming 微批 | 原生事件驱动、毫秒级 | 支持但社区生态小 |
| 语言 | Scala/Java/Python | Java/Scala/Python | Python Only |
| 资源占用 | JVM 堆内存大 | 堆外内存多 | Python 进程级,最省 |
| 学习曲线 | 中等 | 陡峭 | 最平滑 |
结论:
- 如果指导老师一句“要实时”,选 Flink;
- 如果实验室只有 4 台 8 G 老机器,选 Spark 稳;
- 如果全组只会 Python,又想快速出图,选 Dask。
下面示例用“Spark Structured Streaming + Kafka”——兼顾实时与资源,答辩老师都认得。
3. 可运行项目示例:实时舆情分析系统
3.1 业务目标
输入:微博话题实时抓取的文本流
输出:情感极性、关键词云、热度曲线,三张大屏在 Grafana 实时刷新
3.2 架构一览
微博爬虫 → Kafka(Topic:weibo) → Spark Structured Streaming → 1. 情感分类(微调的 BERT 模型) 2. 关键词抽取(Yake) → Redis(缓存) → Grafana(可视化)3.3 环境与版本
- Ubuntu 20.04
- Kafka 2.13-33.5.1(嵌入式 Zookeeper)
- Spark 3.4.1(Hadoop 3.3.6 预编译版)
- Python 3.9,要求包见
requirements.txt(文末仓库)
3.4 核心代码片段
- Kafka 生产端(爬虫脚本片段)
# producer.py from kafka import KafkaProducer import json, requests, time, random producer = KafkaProducer( bootstrap_servers=['localhost:9092'], value_serializer=lambda v: json.dumps(v, ensure_ascii=False).encode('utf-8') ) def fetch_weibo(topic): """伪装 header,简单轮询""" headers = {'User-Agent': 'Mozilla/5.0'} url = f'https://m.weibo.com/api/container/getIndex?containerid=231522type=60&q={topic}' resp = requests.get(url, headers=headers, timeout=5) if resp.status_code == 200: cards = resp.json()['data']['cards'] for c in cards: text = c['mblog']['text'] ts = c['mblog']['created_at'] producer.send('weibo', {'text': text, 'ts': ts}) time.sleep(random.randint(1, 3)) if __name__ == '__main__': fetch_weibo('毕业季')- Spark 消费端(主程序)
# sentiment_stream.py from pyspark.sql import SparkSession from pyspark.sql.functions import from_json, col, udf from pyspark.sql.types import StructType, StructField, StringType, TimestampType import torch, redis, json, os # 1. 会话入口 spark = SparkSession.builder \ .appName("WeiboSentiment") \ .config("spark.sql.shuffle.partitions", 200) \ .config("spark.executor.memory", "2g") \ .getOrCreate() # 2. 定义 schema schema = StructType([ StructField("text", StringType()), StructField("ts", StringType()) ]) # 3. 加载微调模型(仅一次,广播) model = torch.load('bert_cn_sentiment.pt', map_location='cpu') bc_model = spark.sparkContext.broadcast(model) # 4. 情感预测 UDF @udf("double") def predict_sentiment(text: str) -> float: # 简化:返回 0~1 概率,>0.5 积极 from transformers import BertTokenizer tok = BertTokenizer.from_pretrained('bert-base-chinese') inputs = tok(text, return_tensors='pt', truncation=True, max_length=128) with torch.no_grad(): out = bc_model.value(**inputs) prob = float(torch.softmax(out.logits, dim=1)[0][1]) return prob # 5. 读取 Kafka Stream df = spark.readStream \ .format("kafka") \ .option("kafka.bootstrap.servers", "localhost:9092") \ .option("subscribe", "weibo") \ .option("startingOffsets", "latest") \ .load() parsed = df.select(from_json(col("value").cast("string"), schema).alias("v")) \ .select("v.text", "v.ts") scored = parsed.withColumn("score", predict_sentiment(col("text"))) # 6. 写入 Redis(Grafana 通过 redis-datasource 插件读取) def write_to_redis(batch_df, batch_id): r = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True) for row in batch_df.collect(): key = f"sentiment:{row.ts}" r.hset(key, mapping={"text": row.text, "score": row.score}) r.expire(key, 3600) query = scored.writeStream \ .foreachBatch(write_to_redis) \ .outputMode("append") \ .trigger(processingTime='10 seconds') \ .start() query.awaitTermination()- 一键启动脚本(start.sh)
#!/usr/bin/env bash set -e echo "1. 启动 Kafka" $KAFKA_HOME/bin/zookeeper-server-start.sh -daemon $KAFKA_HOME/config/zookeeper.properties sleep 5 $KAFKA_HOME/bin/kafka-server-start.sh -daemon $KAFKA_HOME/config/server.properties echo "2. 创建 topic" $KAFKA_HOME/bin/kafka-topics.sh --create --topic weibo --bootstrap-server localhost:9092 --partitions 3 --replication-factor 1 echo "3. 提交 Spark 作业" spark-submit --master local[*] --packages org.apache.spark:spark-sql-kafka-0-10_2.12:3.4.1 sentiment_stream.pyClean Code 要点
- 函数单一职责:
fetch_weibo只负责抓与发,predict_sentiment只负责推理 - 配置与代码分离:Kafka、Redis 地址统一写进
application.conf - 资源广播:BERT 模型只序列化一次,避免每批重复加载
4. 性能与资源考量:让 4 台 8 G 老机器撑住 2000 条/秒
内存溢出预防
- executor.memory 设 2 g 留 1 g 给 off-heap;
spark.default.parallelism=300避免任务堆积 - Structured Streaming 的
maxOffsetsPerTrigger=10000限制每批摄取,防止一次性拉爆
- executor.memory 设 2 g 留 1 g 给 off-heap;
小集群调度策略
- 采用
local[*]本地伪分布式调试,生产换spark://master:7077 - 每台节点预起 Redis 哨兵,Grafana 连就近节点,减少网络往返
- 采用
检查点与 Exactly 兜底
- 设置
checkpointLocation=/tmp/weibo_chk,程序崩溃重启可断点续跑 - Kafka 采用
enable.idempotence=true生产,避免重复抓取
- 设置
5. 生产环境避坑指南
依赖版本冲突
- Spark 3.4 与 Kafka 客户端 3.5 兼容,但
spark-sql-kafka必须对应 2.12 版;混用 2.13 会报NoSuchMethodError - Python 端 transformers 4.30 以上才支持 torch 2.0,提前锁定
requirements.txt版本号
- Spark 3.4 与 Kafka 客户端 3.5 兼容,但
本地 vs 集群差异
- Windows 开发机路径分隔符导致
checkpointLocation无法识别;统一用/tmp/并在 WSL 下调试 - 集群无外网,pip 安装失败;提前在堡垒机建私有 PyPI 镜像,或把 whl 打进 zip 包
--py-files上传
- Windows 开发机路径分隔符导致
结果可复现
- 随机种子固定:
random.seed(42)、torch.manual_seed(42) - 训练/推理代码、模型权重、依赖列表、采样数据全部进 Git LFS,保证评审老师 checkout 即可复现
- 随机种子固定:
6. 答辩加分小技巧
- 提前录屏:把“爬虫→Kafka→Spark→Grafana”全流程录成 2 分钟 GIF,断网也能播
- 指标看板放“实时情感值”与“关键词 Top10”足够,别炫 3D 旋转饼图,老师看不懂
- 准备一页“失败截图”:内存溢出日志、反爬 302 页面,展示你踩坑+解决的过程,反而拉好感
7. 可扩展方向(把项目变成你的唯一)
- 把情感模型换成 Flink CEP,做“异常热度话题”预警,秒级延迟更酷
- 引入 Dask 做离线回扫,对比批/流结果,写一段“Lambda 架构”经验
- 用 Airflow 调度每日模型重训练,把“在线学习”写进论文未来工作
- 前端加 Vue + ECharts,把 Grafana 替换掉,全栈标签贴满,简历直接+1
结语:毕设不是论文装饰,而是你的第一个“可上线”产品。把上面的脚本拉到实验室机器,先跑通,再换数据源、加模型、补监控,一步步留下 commit 记录,最后写论文就是“翻译”你的 README。祝你答辩顺利,代码常跑不挂!