大数据毕设推荐:Hadoop+Hive+PySpark小说推荐系统全流程解析

发布时间:2026/9/29 23:57:52
大数据毕设推荐:Hadoop+Hive+PySpark小说推荐系统全流程解析 每年毕业季都会冒出同一个问题大数据方向到底选什么题目才能兼顾“能做完”和“能讲清楚”我给过很多学弟学妹的建议其中之一就是这个组合Hadoop Hive PySpark 小说推荐系统再配一个小说爬虫和可视化面板。它几乎把大数据开发的主线流程都串起来了数据采集、分布式存储、离线数仓、推荐算法、结果展示。这篇文章我会把这套项目的设计思路、关键技术选型、实际踩坑和落地细节完整拆开讲适合正在做毕设、想找项目练手、或者准备大数据开发面试的人参考。项目的完整交付物包含源码、文档、PPT和详细讲解但比这些更值钱的是你能说清楚“每一层在干什么、为什么这么干”。下面我从整体架构开始一层层把项目讲明白。1. 项目整体设计与技术选型1.1 为什么选“小说推荐系统”作毕业设计选毕设题目有个原则功能要完整、工作量要看得见、技术栈要有亮点还得能演示。小说推荐系统恰好全占。先说数据小说网站的结构化程度很高书名、作者、分类、简介、字数、评分、阅读记录都可以爬。这些数据天然适合做用户-物品评分矩阵直接喂给推荐算法。再说业务逻辑小说推荐和电商推荐本质上一样协同过滤、热点兜底、冷启动处理这些说法在答辩时都能展开。最后说展示面爬了多少书、清洗了多少条数据、Hive里的宽表长什么样、推荐结果如何呈现全都可以通过可视化面板直观展示。这套题还有一个隐形的优势它可以拆成多个模块单独推进。就算时间只剩两周最基础版本也可以只做“一个分类的小说爬取 Hive建表 PySpark ALS推荐 两张图表”之后再逐步扩展。这种可裁剪性对毕设来说非常重要。1.2 Hadoop、Hive、PySpark在项目里的分工很多人第一次看到三个框架堆在一起会懵其实它们各管一段分工非常清晰。Hadoop提供最底层的分布式存储和资源调度。小说数据和用户行为数据最终落到HDFS上MapReduce不是重点YARN则负责给后续Spark任务分配资源。项目里我把Hadoop当“数据底座”使用它是Hive和Spark能跑起来的前提。Hive解决的是“怎么用SQL操作分布式文件”。爬虫采集的数据先落到MySQL再导入HDFS然后通过Hive建库建表把半结构化数据转成结构化的表。之后用Hive SQL做ETL把原始日志清洗成推荐算法可以直接消费的评分宽表。这一步非常符合真实数仓场景。PySpark负责真正的计算和推荐。推荐算法ALS不能直接用SQL实现需要分布式计算框架。Spark可以读取Hive表可以使用MLlib里的推荐算法还能把结果写回Hive或MySQL。所以PySpark是整个项目的大脑Hive是数据加工厂Hadoop是仓库。1.3 从爬虫到可视化的完整数据链路这套项目的数据流可以用一句话概括从书站爬数据存进MySQL再搬到HDFS用Hive清洗用PySpark建模最后把推荐结果展示在网页上。更细的链路是爬虫采集小说基本信息、用户评分和阅读记录写入MySQL。通过Sqoop或直接导出CSV的方式把数据上传到HDFS。Hive创建外部表关联HDFS目录使用分区表按日期或分类组织数据。ETL阶段做去重、空值处理、类型转换生成用户ID、小说ID、评分的三列宽表。PySpark读取宽表切分训练集和测试集训练ALS模型生成每个用户的TopN推荐列表。推荐结果写回MySQL可视化后端Flask从MySQL查询接口前端ECharts渲染图表。这条链路上每一个环节都能单独截图放在论文里每一段都可以讲出“输入-处理-输出”答辩时非常占优势。2. 小说爬虫先把数据喂进来2.1 目标站选择与合规边界爬虫是很多初学者一开始最兴奋、也最容易被封的部分。做毕设不建议一上来就挑战高难度站点目标站选择有几个原则。第一优先选允许爬取或结构清晰的书站。有些站会有robots.txt说明爬虫规则虽然它不是法律文件但为了项目安全和学术规范尽量选择公开、友好的数据源。第二只爬元数据不爬小说全文。毕设做推荐系统需要书名、作者、分类、简介、字数、评分、用户阅读记录这些足够支撑模型完全没必要把整本小说的正文抓下来那样版权风险很高。第三做好限速别给对方服务器造成压力。我在这个项目里实际爬取的是测试环境和自己构造的样本数据然后用爬虫补充真实字段分布。这样做的好处是可复现不会因为目标站改版而影响项目进度。2.2 数据模型设计用什么字段支撑推荐爬虫之前先想清楚表结构这比爬虫代码更重要。我设计了四张核心表。小说信息表books包含book_id、title、author、category、intro、words、score等字段。用户信息表users包含user_id、user_name、gender。评分表ratings包含user_id、book_id、rating、timestamp这是推荐算法的核心输入。阅读行为表reads包含user_id、book_id、read_duration、read_time用于构造隐式反馈。这里有一个项目经验不要只盯着评分表。小说站很多没有显式评分但有阅读时长、收藏、加入书架等行为这些都能作为隐式反馈。ALS模型可以设置implicitPrefsTrue来训练所以我在设计表结构时就预留了行为数据字段。2.3 requestsBeautifulSoup爬虫实操项目里我用的是requests加BeautifulSoup足够处理静态页面。对于动态渲染的页面才考虑Selenium或Playwright但尽量少用因为速度慢、资源占用高。一个典型的信息抓取流程是先请求列表页拿到每本书的详情页URL再请求详情页解析字段。列表页抓取示例import requests from bs4 import BeautifulSoup headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 } resp requests.get(https://example.com/book_list?page1, headersheaders, timeout10) soup BeautifulSoup(resp.text, html.parser) book_links [] for a in soup.select(h2.book_name a): book_links.append(a.get(href))拿到详情页URL之后再用相同方式解析书名、作者、分类等字段。需要注意详情页字段有可能为空解析时要用try-except包裹避免单个页面出错导致整个爬虫崩溃。数据入库我用SQLAlchemy先定义ORM模型再批量写入。这个选择是因为ORM能让插入逻辑统一后续重跑爬虫也方便去重。模型示例from sqlalchemy import create_engine, Column, Integer, String, Float, Text from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker Base declarative_base() class Book(Base): __tablename__ books book_id Column(Integer, primary_keyTrue) title Column(String(200), uniqueTrue) author Column(String(100)) category Column(String(50)) intro Column(Text) words Column(Integer) score Column(Float)每次写入前判断title是否已存在实现简单的增量更新。对爬虫项目断点续爬能力很重要否则中途断网就要从头再来。2.4 反爬与数据质量处理的几个细节反爬不是要你对抗整个互联网而是应对常见的基础防护。重点做了三件事随机切换User-Agent、每次请求间隔0.5到1秒、失败自动重试。简单的Cookie或验证码站点可以在请求头中带上登录态Cookie但不推荐为了毕设去啃复杂验证码。数据质量方面我遇到的坑是编码问题。很多小说站页面是GBK编码直接用默认的UTF-8解析会乱码。解决办法是在拿到响应后先判断编码resp.encoding resp.apparent_encoding另一个坑是字段缺失。有的书没分类有的没评分。对于这类脏数据我的策略是在爬虫阶段先保留为NULL后续由Hive ETL统一处理。还有一个经验是给books表的title加唯一索引这样即使脚本重复执行也不会出现大量重复数据。3. HadoopHive离线数仓的搭建和ETL3.1 Hadoop环境搭建伪分布式至少要有Hadoop环境是毕设里最容易卡人的地方。如果学校没有现成集群我建议先在虚拟机或云服务器上搭伪分布式三台节点的真实集群可以作为加分项但不是必需。伪分布式最关键的三个配置文件是core-site.xml、hdfs-site.xml和yarn-site.xml。core-site.xml指定NameNode地址configuration property namefs.defaultFS/name valuehdfs://localhost:8020/value /property /configurationhdfs-site.xml设置副本数和NameNode/DataNode目录configuration property namedfs.replication/name value1/value /property property namedfs.namenode.name.dir/name value/home/hadoop/data/namenode/value /property /configuration启动顺序是先启动Zookeeper如果配置了HA或依赖ZK再执行start-dfs.sh和start-yarn.sh。很多人会把顺序搞反结果NameNode起不来。如果是在Windows上用IDEA写代码跑Hadoop会遇到另一个经典问题缺少winutils.exe。解决办法是把对应Hadoop版本的winutils.exe放到HADOOP_HOME/bin目录下并设置HADOOP_USER_NAME环境变量。否则本地调试时会报Failed to locate the winutils binary。3.2 把MySQL数据导入HDFS和Hive数据从MySQL进入Hive常见方式有Sqoop、DataX、或先把数据导出为CSV再手动上传。毕设里用Sqoop比较省事sqoop import \ --connect jdbc:mysql://localhost:3306/novel \ --username root \ --password 123456 \ --table books \ --target-dir /data/ods/books \ --delete-target-dir \ --fields-terminated-by \t如果不想引入Sqoop也可以直接通过Spark读取MySQL再写入HDFS代码量差不多。我建议毕设至少用一次Sqoop或Spark-SQL因为在文档里能体现出“多源数据导入”能力。Hive建表关联HDFS目录时我选择外部表加分区。外部表的好处是删表时不会连带删除HDFS数据对毕设场景更安全。建表语句CREATE EXTERNAL TABLE IF NOT EXISTS ods_book_info ( book_id INT, title STRING, author STRING, category STRING, words INT, score DOUBLE ) PARTITIONED BY (dt STRING) STORED AS ORC LOCATION /data/ods/books;这里有个细节表的分区字段dt是虚拟列值写在分区路径里。如果原始数据没有日期字段可以统一用dt2024-12-01这样的固定值或者按爬取批次生成分区。3.3 Hive ETL清洗、去重、构建评分宽表ETL是整个数仓最核心的环节。我用Hive SQL完成三步操作。第一步去重。爬虫写入HDFS的数据可能重复按业务主键去重INSERT OVERWRITE TABLE dwd_book_info SELECT book_id, MAX(title), MAX(author), MAX(category), MAX(words), MAX(score) FROM ods_book_info GROUP BY book_id;第二步处理空值和异常值。评分字段可能为NULL或者超出1到5分范围用COALESCE和CASE WHEN处理SELECT user_id, book_id, CASE WHEN rating IS NULL OR rating 0 THEN 3.0 WHEN rating 5 THEN 5.0 ELSE rating END AS rating FROM ods_user_rating;第三步构建评分宽表。推荐算法只需要三列用户ID、小说ID、评分。可以从评分表直接清洗也可以把阅读时长归一化成分数。两种数据我最终合并成一张dwd_user_book_rating表CREATE TABLE IF NOT EXISTS dwd_user_book_rating AS SELECT user_id, book_id, rating FROM rating_clean UNION ALL SELECT user_id, book_id, 3.0 read_duration / 600.0 FROM read_behavior_clean;这种宽表的好处是PySpark读取时不需要再做复杂关联直接在Hive表上训练。3.4 小文件、乱码和自定义UDF那些坑Hive用多了就会遇到两个高频问题小文件过多和中文乱码。小文件过多是因为每条SQL写入都可能产生大量小文件尤其是爬虫数据按批次导入时。解决思路是合并小文件。可以开启Hive的合并参数SET hive.merge.mapfilestrue; SET hive.merge.mapredfilestrue; SET hive.merge.size.per.task256000000; SET hive.merge.smallfiles.avgsize16000000;或者在写入时用DISTRIBUTE BY打散数据让每个Reducer输出均匀的大文件INSERT OVERWRITE TABLE dwd_book_info SELECT ... FROM ods_book_info DISTRIBUTE BY book_id;中文乱码问题主要出在Hive元数据库。如果是用MySQL存Hive元数据要在JDBC连接串里加上UTF-8参数并且保证MySQL表字符集是utf8。还有分区字段出现乱码的情况通常是动态分区写入时把中文分类直接作为分区值解决方法是规范编码避免在分区路径中使用特殊字符。如果清洗逻辑复杂到SQL不便表达就需要自定义UDF或UDAF。Hive UDF继承org.apache.hadoop.hive.ql.exec.UDF重写evaluate方法打包后在Hive客户端中执行ADD JAR /path/hive-udf.jar; CREATE TEMPORARY FUNCTION parse_tag AS com.example.ParseTagUDF;UDAF相对复杂适合做多行聚合成一个指标的场景。毕设中如果只是求平均、求和Hive内置函数就够了自定义UDAF可以放在文档中作为扩展点讲不建议在核心流程里强行引入。4. PySpark推荐系统核心算法和落地4.1 算法选型为什么用ALS协同过滤推荐算法有基于内容、基于协同过滤、基于矩阵分解、深度推荐等路线。毕设里我选ALS协同过滤原因是它在学术和工程之间取得了很好的平衡。ALS的全称是交替最小二乘法把用户-物品评分矩阵分解成用户隐因子矩阵和物品隐因子矩阵再用两个矩阵相乘预测未知评分。它不需要复杂的特征工程Spark MLlib自带实现训练和调参都很方便。选ALS还有一个原因是可解释性够强。答辩时你可以说把用户和小说映射到同一个隐因子空间通过降维找到相似兴趣的用户从而完成推荐。这句话比“我用了深度学习模型”要好讲得多也不容易翻车。4.2 从Hive取数到ALS训练PySpark读取Hive表要开启Hive支持。在代码里这样初始化from pyspark.sql import SparkSession spark SparkSession.builder \ .appName(NovelRec) \ .enableHiveSupport() \ .getOrCreate()读取宽表后只需要保留三列df spark.sql( SELECT user_id, book_id, rating FROM dwd.dwd_user_book_rating WHERE dt 2024-12-01 ).select( user_id, book_id, rating ).cache()ALS要求用户ID和物品ID是整数如果不是需要先用StringIndexer转换。字段类型不对会直接报错这是初学者最容易忽略的。切分数据集并训练train, test df.randomSplit([0.8, 0.2], seed42) from pyspark.ml.recommendation import ALS als ALS( userColuser_id, itemColbook_id, ratingColrating, rank10, maxIter10, regParam0.1, coldStartStrategydrop ) model als.fit(train)coldStartStrategydrop的意思是遇到测试集中从未出现过的用户或物品时直接丢弃预测结果不填NULL。这样后续计算评估指标不会报错。4.3 推荐结果写回和冷启动兜底模型训练完后可以为每个用户生成TopN推荐user_recs model.recommendForAllUsers(10)推荐结果可以直接写回Hive也可以写到MySQL供可视化查询。我更推荐写回MySQL因为Flask网页查询MySQL比查Hive快得多也符合“离线计算、在线展示”的分层思想。通过PySpark写MySQL需要JDBC驱动代码大致是user_recs.write.mode(overwrite) \ .format(jdbc) \ .option(url, jdbc:mysql://localhost:3306/novel) \ .option(driver, com.mysql.jdbc.Driver) \ .option(dbtable, rec_user_book) \ .option(user, root) \ .option(password, 123456) \ .save()冷启动是推荐系统绕不开的问题。新用户没有历史行为新书没有评分数据。我的兜底方案是模型推荐结果为空时返回最新上架的热门小说Top10。热门定义可以用分类热度或总体点击量。这个兜底逻辑写在Flask接口里而不是模型里因为模型能算出分数但无法解决新物品问题。4.4 模型效果评估与调参评估回归预测质量常用RMSEfrom pyspark.ml.evaluation import RegressionEvaluator predictions model.transform(test) evaluator RegressionEvaluator( metricNamermse, labelColrating, predictionColprediction ) rmse evaluator.evaluate(predictions) print(fRMSE {rmse})RMSE越低说明预测评分和真实评分越接近。但毕设里不要只看RMSE建议再把推荐结果抽样打印出来看看是否合理。有时候RMSE不高但推荐的小说受众明显不对说明训练数据质量有问题这时候要回溯到ETL阶段。调参方面第一个调整的是rank代表隐因子数量。rank设太小会欠拟合设太大训练慢且容易过拟合。一般从8到20之间试。第二个是regParam正则化参数0.01到1之间试防止模型在稀疏数据上过拟合。第三个是maxIter训练轮数10到20足够。如果数据量不大不要一上来就做交叉验证用几种参数组合分别跑一遍看RMSE更高效。我最终参数落在rank10、regParam0.1、maxIter15实测定点效果稳定。5. 小说可视化把推荐过程讲清楚5.1 可视化面板到底要展示什么很多毕设的可视化只是画几张饼图凑数这是很扣分的。可视化的核心目的不是炫技而是把数据的价值和推荐系统的效果讲清楚。我的面板分为四个区域。第一个区域是小说热度榜展示Top10热门小说用柱状图呈现。第二个区域是分类分布展示各分类书籍数量或热度占比用饼图或旭日图。第三个区域是阅读行为趋势展示不同时间段的阅读量用折线图。第四个区域是推荐结果演示输入用户ID后返回该用户的Top5推荐小说列表并展示推荐分数。这样的面板设计在答辩时能形成一个完整故事数据量大概是多少热门集中在哪些分类推荐算法给某个用户推了什么为什么可能推这些书。5.2 FlaskECharts实现数据接口和图表后端我选了Flask原因就是轻量适合把MySQL中的数据直接变成JSON接口。一个接口示例from flask import Flask, jsonify import pymysql app Flask(__name__) def query_db(sql, argsNone): conn pymysql.connect( hostlocalhost, userroot, password123456, databasenovel, charsetutf8mb4 ) cursor conn.cursor() cursor.execute(sql, args) rows cursor.fetchall() cursor.close() conn.close() return rows app.route(/api/hot_books) def hot_books(): rows query_db( SELECT title, score FROM books ORDER BY score DESC LIMIT 10 ) return jsonify([{title: r[0], value: float(r[1])} for r in rows])前端页面用ECharts渲染。以柱状图为例页面引入ECharts后通过fetch拉取接口数据再设置optionfetch(/api/hot_books) .then(res res.json()) .then(data { let myChart echarts.init(document.getElementById(hot)); myChart.setOption({ xAxis: { type: category, data: data.map(d d.title) }, yAxis: { type: value }, series: [{ type: bar, data: data.map(d d.value) }] }); });实际项目中我会把多个图表的初始化函数拆成独立JS文件避免页面一打开就请求全部数据。推荐结果展示也可以用表格组件或卡片布局。5.3 前端联调中的几个小问题联调最容易遇到的问题是跨域和编码。Flask跑在5000端口前端如果单独起一个开发服务器就会端口不一致需要配置CORS或者直接把静态页放到Flask的templates目录下。我在项目里选择后者省去跨域处理。编码问题上MySQL连接字符串一定要加charsetutf8mb4否则中文书名会在JSON里乱码。接口返回时Flask默认支持中文但前端拿到后要确认HTML页面meta设置了UTF-8。还有一个经验是不要把SparkContext放在Flask请求里。初始化Spark很重如果每次点击推荐都创建一个SparkSession内存直接爆掉。推荐结果应该提前离线算好存到MySQLFlask只做普通数据库查询。6. 常见问题与排查技巧实录6.1 Hadoop/Zookeeper问题速查Hadoop环境出问题最磨人。我遇到过的典型问题可以整理成一张速查表。现象原因处理方式NameNode启动失败NameNode目录没有格式化或格式化多次导致ID不一致删除data目录后重新执行hdfs namenode -formatDataNode起不来clusterID与NameNode不一致检查data目录的VERSION文件统一clusterID本地IDEA报winutils错误缺少Windows下Hadoop运行所需的winutils.exe下载对应版本winutils放入bin目录并配置HADOOP_HOMEZookeeper连接超时三节点时间不同步或端口未放行同步时间检查2181端口连通性YARN任务卡在Accepted内存资源不足调低yarn.scheduler最小分配内存或增加节点内存有一个血泪教训是不要反复格式化NameNode。格式化前一定确认已经清空了旧的data目录否则会产生命名空间不一致DataNode起不来。正确顺序是先停掉所有进程再清理数据目录最后格式化、启动。6.2 Hive和Spark异常处理Hive写数据时经常遇到“Failed with exception java.io.IOException: ... Permission denied”目录权限问题通常是操作HDFS的用户不一致。可以用如下命令授权hdfs dfs -chmod -R 777 /data hdfs dfs -chown -R hadoop /data如果是动态分区写入失败需要在Hive会话中开启参数SET hive.exec.dynamic.partitiontrue; SET hive.exec.dynamic.partition.modenonstrict;Spark方面ALS训练时最常报“Column user_id must be of type numeric but was actually of type string”。解决方法是提前用StringIndexer或cast转成整数。另一个常见的OOM问题是executor内存不够提交任务时可以这样控制spark-submit \ --master yarn \ --executor-memory 2g \ --driver-memory 2g \ --num-executors 2 \ rec_train.py本地调试小数据时我用--master local[2]等逻辑跑通再提交到YARN。这样能减少排队时间也方便打印日志。6.3 爬虫和中文乱码问题爬虫最大的变数是目标站改版。今天能解析的选择器明天可能就失效。我的应对方法是在解析函数里统一加日志抓不到字段时打印URL和状态码方便定位。同时把已采集的书籍标题保存到一个表里用唯一索引做去重避免断点续爬时重复入库。中文乱码还有一处容易忽略用Sqoop导入MySQL到HDFS时如果MySQL字段是utf8而HDFS文件没有指定编码格式Hive读出来可能乱码。可以在Sqoop导入时增加--connection-param-file或直接通过--options-file指定编码相关配置但最稳妥的是在Hive建表时固定为ROW FORMAT SERDE或使用ORC列式存储ORC对中文字符串的支持比较稳定。6.4 毕业设计文档、PPT和答辩的思考最后说点文档和PPT的经验因为标题里也提到了源码、文档、PPT和详细讲解这些配套内容。写文档时不能把代码贴一遍就结束。我建议按这个结构写第一章业务背景说明为什么要做小说推荐数据规模有多大解决什么痛点。第二章需求分析把功能性需求和非功能性需求拆成表格。第三章系统设计画数据流图、模块图、表结构设计。第四章实现每个模块给出核心代码片段并解释流程。第五章测试放运行截图、效果对比、RMSE指标。第六章总结与展望。PPT控制在20页左右。首页放题目和技术栈第二页放数据流图中间放三到五页关键技术讲解后面放演示截图和测试结果。讲的时候不要照念PPT要把数据流从爬虫、Hive、PySpark到可视化讲成一条线。面试官或老师最关心的就是“你做了之后到底懂不懂每一层在干什么”。这套项目里最能体现思考深度的扩展点是如果数据量再翻十倍Hive上用什么分区策略Spark训练能不能改用增量更新冷启动怎样结合内容特征这些不一定在毕设里实现但能在文档和答辩里体现你的工程视野。我个人的体会是这套项目最磨人的并不是ALS算法本身而是把“爬虫数据怎么安全进入HDFS”“Hive表怎么建更合理”“Spark怎么读Hive不乱码”“推荐结果怎么快速展示”这串流程真正联通。很多小问题在单个模块里都不致命但串在一起就会让人挠头。最后分享一个非常实用的调试技巧任何时候先跑通最小闭环。不要一开始就爬全站、做全量清洗、全量训练。先爬一个分类、导入HDFS、建一张小表、用local模式训练5分钟、画一张图确认每个环节都正常再逐步增加数据量和复杂度。这套方法帮你把问题隔离在局部也让我在毕设答辩前非常确定整个系统能跑。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询