基于Hadoop的网络小说数据分析系统设计与实现:爬虫到可视化 如果你正在为计算机毕业设计选题发愁又希望做一套“有点技术含量、能讲清原理、还能拿出漂亮可视化页面”的系统那么“基于 Hadoop 的网络小说数据分析系统”是一个性价比很高的选择。它既覆盖了 Python 爬虫、Hadoop 大数据存储与离线统计也加入了推荐算法和可视化看板文理工科同学都能找到切入点。本文会用完整的模块拆解、可运行代码示例和避坑思路帮你从零梳理这个毕设项目的核心实现方式适合正在选题目、写开题报告或已经进入编码阶段的同学直接参考。1. 项目背景与系统功能拆解1.1 这个毕设项目要解决什么传统的小说阅读网站每天会产生大量书籍信息、点击量、推荐票和用户行为数据。如果只是把数据存到 MySQL 中再写几个增删改查接口对毕业设计来说功能太单薄也体现不出大数据处理能力。随着数据规模增加我们需要一个更完整的处理链路把爬虫采集到的网络小说数据统一存储到分布式文件系统 HDFS 中使用 MapReduce 完成离线统计再把统计分析结果交给 Web 端做可视化展示同时根据用户阅读行为构建推荐算法。从业务场景看这个项目可以理解为一个小型“小说数据中台”的简化版。爬虫负责数据接入Hadoop 负责数据存储与批量计算协同过滤负责个性化推荐ECharts 可视化大屏负责把统计结果直观呈现出来。整个系统涉及数据采集、清洗、存储、计算、分析和挖掘层次非常完整。1.2 功能模块拆解整个系统可以拆成以下四大核心模块模块名称核心功能关键技术数据爬虫模块抓取小说书名、作者、分类、字数、点击量、推荐票等字段requests、BeautifulSoup、pandas大数据存储与统计模块将清洗后的数据上传 HDFS按分类等维度离线聚合Hadoop HDFS、MapReduce、Hadoop Streaming推荐算法模块根据用户评分或阅读行为推荐相似小说Item-Based Collaborative Filtering可视化与 Web 展示模块展示小说分类占比、阅读量 Top10、推荐结果等Flask、ECharts、MySQL简单来说爬虫解决“数据从哪来”的问题Hadoop 解决“海量数据怎么存、怎么算”的问题推荐算法解决“如何从数据中挖掘用户兴趣”的问题可视化解决“计算结果如何表达”的问题。把这四个问题讲明白毕业设计的内容就会非常充实。1.3 为什么推荐选这个题目这两年毕业设计对“工作量”的要求越来越高。纯 Python 爬虫项目容易显得深度不足纯 Hadoop 项目又缺少业务场景和前端展示容易出现“只讲技术流程、没有实际应用”的问题。“爬虫 Hadoop 推荐算法 可视化”的组合正好解决了这个矛盾。对这个题目感兴趣的同学通常需要具备 Python 基础、熟悉 pandas 数据处理、能理解 MapReduce 思想并且愿意花一点时间折腾 Linux 和 Hadoop 环境。完成这个项目后你简历上可以写的技能点会比较清晰Python、爬虫、Hadoop、数据分析、可视化、推荐算法这些关键词也是很多互联网岗位关注的基础方向。2. 系统总体架构与技术选型2.1 系统总体架构从分层架构角度看这个系统可以设计为五层采集层 - 存储层 - 计算层 - 服务层 - 展示层 爬虫 HDFS MapReduce Flask ECharts采集层是爬虫程序负责从目标小说网站抓取数据并做基础清洗。存储层由 HDFS 承载原始数据和清洗后的数据文件MySQL 存储统计结果和用户行为数据。计算层使用 MapReduce 对小说数据进行离线统计例如不同分类的书籍数量、点击总量等。服务层是 Flask 后端对外提供 JSON 格式的接口。展示层是浏览器前端使用 ECharts 绘制图表。五层分工明确也方便在毕业论文中画出架构图进行说明。2.2 技术栈说明整个项目的技术栈以 Python 生态为主辅以 Hadoop 大数据组件组成部分推荐技术爬虫requests BeautifulSoup数据处理pandas openpyxl大数据存储Hadoop HDFS离线计算MapReduce Hadoop Streaming关系型数据库MySQL 5.7 / 8.0后端服务Flask可视化ECharts协同过滤scikit-learn pandas需要说明的是Hadoop Streaming 允许我们使用 Python 编写 Mapper 和 Reducer这样不需要额外学习 Java 也能跑通 MapReduce对计算机毕业设计来说非常友好。虽然 Streaming 比原生 Java 在性能上有一些损耗但在这个项目中完全够用而且能展示出你对 MapReduce 机制的理解。2.3 数据流向整个系统的数据流向可以概括为一条线爬虫程序采集小说信息保存为 CSV 文件pandas 对数据进行去重、清洗、格式转换HDFS 客户端把 CSV 上传到指定目录MapReduce 任务读取 HDFS 上的 CSV输出分类统计结果统计结果导入 MySQLFlask 从 MySQL 查询数据并提供给前端ECharts 渲染图表推荐算法则通过用户行为数据输出 TopN 推荐结果。每一次数据流转都需要在答辩时讲清楚尤其是“为什么 Hadoop 统计后还要导入 MySQL”这个问题。比较合理的解释是HDFS 适合海量原始文件的存储和批量分析不适合高并发的交互式查询可视化页面需要快速响应因此把聚合后的结果放在 MySQL 中更合适。这种“冷热数据分离”的思路也是大数据系统中的常见实践。3. 环境准备与项目目录结构3.1 开发环境清单版本需要根据你的实际项目环境调整本文示例以常见环境为例重点演示配置思路操作系统Windows 10/11 或 Ubuntu 20.04/22.04开发语言Python 3.8建议使用 3.8 到 3.10 版本Hadoop2.10.x 或 3.3.x单机伪分布式即可满足毕设演示JDKHadoop 3.x 通常要求 JDK 8 或 JDK 11MySQL5.7 或 8.0Flask2.x浏览器Chrome / Edge如果电脑内存只有 8G建议使用 Hadoop 伪分布式模式不要强行搭建三个节点的集群。伪分布式模式启动的进程足够支撑你演示 HDFS 和 MapReduce 功能对毕业设计来说完全够用。3.2 Hadoop 环境搭建要点Hadoop 环境配置是很多同学第一次接触 Linux 时最头疼的部分。这里整理一些关键步骤更详细的安装过程可以结合网上教程逐步操作。首先安装 JDK 并配置环境变量。Hadoop 启动时会依赖 JAVA_HOME如果环境变量没有配置正确启动脚本会直接报错。然后解压 Hadoop 安装包修改core-site.xml、hdfs-site.xml和yarn-site.xml。伪分布式模式下core-site.xml中主要配置 NameNode 地址hdfs-site.xml中配置副本数为 1。!-- core-site.xml 核心配置片段 -- configuration property namefs.defaultFS/name valuehdfs://localhost:9000/value /property /configuration!-- hdfs-site.xml 核心配置片段 -- configuration property namedfs.replication/name value1/value /property /configuration启动 Hadoop 之前需要对 NameNode 进行格式化格式化命令为hdfs namenode -format。需要注意的是格式化操作会清空 NameNode 上的元数据因此只在第一次启动时执行即可。如果你的 Hadoop 格式化失败或反复重启异常通常是/tmp目录权限不足或端口被占用需要根据日志仔细排查。很多同学在这个阶段会被环境问题劝退但实际上只要按照“安装 JDK - 配置免密登录 - 配置 Hadoop 文件 - 格式化 - 启动”的顺序执行问题通常不大。3.3 项目目录结构合理的项目目录能让导师一眼看出你的工程能力同时也方便自己维护。下面给出一个推荐结构novel-analysis-system/ ├── spider/ # 爬虫模块 │ ├── novel_spider.py │ ├── data_clean.py │ └── demo_pages/ # 本地测试页面 ├── data/ # 数据目录 │ ├── raw/ # 爬虫原始数据 │ └── clean/ # 清洗后数据 ├── hadoop/ # Hadoop 相关脚本 │ ├── upload_hdfs.sh │ ├── mapper.py │ ├── reducer.py │ └── export_to_mysql.py ├── recommend/ # 推荐算法模块 │ ├── item_cf.py │ └── run_recommend.py ├── web/ # Flask 可视化系统 │ ├── app.py │ ├── templates/ │ │ └── index.html │ └── static/ │ └── js/ │ └── charts.js ├── docs/ # 论文相关文档 ├── requirements.txt └── README.md这样划分后爬虫、大数据处理、推荐、Web 展示是互相解耦的。如果某个模块出了问题不需要重写其他部分。论文第 4 章和第 5 章也可以按照这个项目结构来组织系统设计直接贴近代码实现老师追问时你也能快速找到对应文件。4. 模块一网络小说爬虫设计与实现4.1 爬虫整体设计爬虫模块是整个系统的数据来源。在设计爬虫时建议先确定要抓取哪些字段。对于小说数据分析来说字段至少应该包括小说编号、书名、作者、分类、连载状态、字数、点击量、推荐量。有了这些字段才能支撑后续的分类统计、阅读量排行和同类型推荐。实际开发中我不会一上来就直接对线上网站发起大量请求而是先在本地准备一个模拟 HTML 页面保证解析逻辑能跑通再切换到线上地址。这样做有两个好处第一网络请求失败时不会影响代码调试第二演示时更可控避免给目标站点造成访问压力。真实抓取前一定要查看目标站点的 robots.txt 协议并评估抓取行为的合法合规性这是毕业设计爬虫项目必须注意的边界。4.2 页面解析核心代码下面给出一个可运行的爬虫思路重点展示请求、解析、字段抽取三层逻辑。代码中使用了sourcelocal本地页面模式和sourceonline在线模式方便你在毕设环境中先跑通流程。# 文件路径spider/novel_spider.py import os import time import random import pandas as pd import requests from bs4 import BeautifulSoup HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0 Safari/537.36 } FIELD_NAMES [ book_id, name, author, category, status, word_count, click_count, recommend_count ] class NovelSpider: def __init__(self, sourcelocal, total_pages5): self.source source self.total_pages total_pages self.data [] def load_html(self, page): if self.source local: path os.path.join(demo_pages, fpage_{page}.html) with open(path, r, encodingutf-8) as f: return f.read() # 正式抓取时替换为目标站点地址并确认抓取行为的合规性 url fhttps://your-site.example.com/novel/list?page{page} resp requests.get(url, headersHEADERS, timeout10) resp.encoding resp.apparent_encoding return resp.text def parse_html(self, html): soup BeautifulSoup(html, html.parser) rows [] # 示例站点的列表结构每个小说条目对应 .novel-item # 真实站点选择器需要根据页面结构调整 for item in soup.select(.novel-item): row { book_id: item.get(data-id), name: item.select_one(.name).text.strip(), author: item.select_one(.author).text.strip(), category: item.select_one(.category).text.strip(), status: item.select_one(.status).text.strip(), word_count: item.select_one(.word-count).text.strip(), click_count: item.select_one(.click-count).text.strip(), recommend_count: item.select_one(.recommend-count).text.strip(), } rows.append(row) return rows def run(self): for page in range(1, self.total_pages 1): print(f正在抓取第 {page} 页...) html self.load_html(page) rows self.parse_html(html) self.data.extend(rows) # 控制抓取频率避免对目标站造成压力 time.sleep(random.uniform(1, 3)) df pd.DataFrame(self.data, columnsFIELD_NAMES) os.makedirs(../data/raw, exist_okTrue) df.to_csv(../data/raw/novel_raw.csv, indexFalse, encodingutf-8-sig) print(f抓取完成共保存 {len(df)} 条数据) if __name__ __main__: spider NovelSpider(sourcelocal, total_pages3) spider.run()这段代码里有两个地方需要特别说明。第一BeautifulSoup 的选择器依赖目标站点页面结构如果页面结构调整解析结果就会为空因此建议先通过浏览器开发者工具确认真实标签类名再替换select和select_one中的参数。第二代码中使用sourcelocal时需要在demo_pages中存放静态 HTML 文件这样可以在没有网络的情况下验证整个流程。4.3 数据清洗与落盘爬虫抓到的原始数据通常存在重复、缺失和格式不一致问题不能直接上传到 HDFS 做统计。数据清洗的主要任务包括删除重复小说、过滤缺少书名或作者的记录、将字数和阅读量字段转换为数值类型。# 文件路径spider/data_clean.py import pandas as pd def convert_word_count(value): # 处理类似 521.3万 的文本统一转换为数值 value str(value).strip() if 万 in value: return float(value.replace(万, )) * 10000 if 亿 in value: return float(value.replace(亿, )) * 100000000 try: return float(value.replace(,, )) except ValueError: return 0.0 def clean_novel_data(input_path, output_path): df pd.read_csv(input_path) print(清洗前数据量:, len(df)) df df.drop_duplicates(subset[book_id], keepfirst) df df.dropna(subset[name, author, category]) df[word_count] df[word_count].map(convert_word_count) df[click_count] pd.to_numeric(df[click_count].astype(str).str.replace(,, ), errorscoerce).fillna(0) df[recommend_count] pd.to_numeric(df[recommend_count].astype(str).str.replace(,, ), errorscoerce).fillna(0) df df[df[word_count] 0] df df.sort_values(click_count, ascendingFalse) import os os.makedirs(os.path.dirname(output_path), exist_okTrue) df.to_csv(output_path, indexFalse, encodingutf-8-sig) print(清洗后数据量:, len(df)) print(分类分布) print(df.groupby(category)[book_id].count()) if __name__ __main__: clean_novel_data(../data/raw/novel_raw.csv, ../data/clean/novel_clean.csv)清洗后的数据会保存到data/clean/novel_clean.csv这个文件就是后续 Hadoop 离线统计的数据源。把清洗逻辑独立成脚本的好处在于当爬虫字段增加或页面规则变化时只需要修改对应函数不需要改动 Hadoop 计算代码。5. 模块二Hadoop 存储与离线统计5.1 清洗后数据上传 HDFS数据清洗结束以后需要把 CSV 文件上传到 HDFS。HDFS 的目录规划可以按业务来设计例如把小说原始数据统一放在/novel/input下。#!/bin/bash # 文件路径hadoop/upload_hdfs.sh HDFS_INPUT/novel/input hdfs dfs -mkdir -p $HDFS_INPUT hdfs dfs -put -f ../data/clean/novel_clean.csv $HDFS_INPUT/ hdfs dfs -ls $HDFS_INPUT执行脚本后可以用hdfs dfs -cat /novel/input/novel_clean.csv | head命令查看文件是否正确写入。这里需要注意的是如果 CSV 中的字段包含中文且编码不一致HDFS 上读取时可能会出现乱码因此在上传之前最好统一使用 UTF-8 编码。Windows 下 pandas 默认保存为utf-8-sig会带有 BOM脚本读取时要注意跳过 BOM 或使用 UTF-8 读取。5.2 Mapper 与 Reducer 编写MapReduce 是本项目展示大数据处理能力的重要部分。这里使用 Hadoop Streaming 技术允许用 Python 编写 Mapper 和 Reducer。以一个常见统计需求为例统计每个小说分类下的书籍数量、总点击量和平均点击量。Mapper 的职责是从标准输入中逐行读取 CSV 数据按照字段分割后输出中间键值对。字段顺序以清洗后的 CSV 为准book_id, name, author, category, status, word_count, click_count, recommend_count。#!/usr/bin/env python # 文件路径hadoop/mapper.py import sys def main(): for line in sys.stdin: line line.strip() if not line or line.startswith(book_id): continue parts line.split(,) # 期望至少包含 7 个字段 # book_id, name, author, category, status, word_count, click_count if len(parts) 7: category parts[3].strip() word_count parts[5].strip() click_count parts[6].strip() # 输出: category 1 word_count click_count print(f{category}\t1\t{word_count}\t{click_count}) if __name__ __main__: main()Reducer 的职责是按照 category 维度对 Mapper 输出进行聚合最终输出每个分类的书籍数量、总点击量和平均点击量。#!/usr/bin/env python # 文件路径hadoop/reducer.py import sys from collections import defaultdict def main(): stats defaultdict(lambda: {book_count: 0, total_click: 0}) for line in sys.stdin: line line.strip() if not line: continue parts line.split(\t) if len(parts) ! 4: continue category parts[0] stats[category][book_count] int(parts[1]) stats[category][total_click] int(parts[2]) for category, value in sorted( stats.items(), keylambda x: x[1][total_click], reverseTrue ): total_click value[total_click] book_count value[book_count] avg_click total_click / book_count if book_count else 0 print(f{category}\t{book_count}\t{total_click}\t{avg_click:.2f}) if __name__ __main__: main()提交到 Hadoop 之前可以先在本地用 Linux 管道模拟测试确保逻辑正确cd hadoop python3 mapper.py ../data/clean/novel_clean.csv | sort | python3 reducer.py本地输出正常后再提交 Hadoop Streaming 作业# 不同 Hadoop 版本中 streaming jar 路径可能不同 hadoop jar $HADOOP_HOME/share/hadoop/tools/lib/hadoop-streaming-*.jar \ -files mapper.py,reducer.py \ -mapper python3 mapper.py \ -reducer python3 reducer.py \ -input /novel/input/novel_clean.csv \ -output /novel/output/category_stats需要注意Hadoop Streaming 的输出目录不能已存在每次重新提交前需要先删除旧输出目录hdfs dfs -rm -r /novel/output/category_stats这里特别建议在论文中把 MapReduce 的数据流转画清楚一行 CSV 如何经过 Mapper 变成category, 1, word_count, click_count键值对又如何经过 Shuffle 和 Sort 阶段最终由 Reducer 输出聚合结果。导师很喜欢听这部分因为这是真正能证明你理解大数据计算原理的地方。5.3 导入 MySQL 供可视化展示MapReduce 的输出结果一般是文本文件保存在 HDFS 的/novel/output/category_stats目录中。可视化系统为了快速查询需要把聚合结果导入 MySQL 表。推荐新建一张category_stats表字段包括分类名、书籍数量、总点击量、平均点击量。-- 建表语句部分字段 CREATE TABLE category_stats ( id INT PRIMARY KEY AUTO_INCREMENT, category_name VARCHAR(50) NOT NULL, book_count INT DEFAULT 0, total_click BIGINT DEFAULT 0, avg_click DECIMAL(12, 2) DEFAULT 0, update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;实际课堂演示时也可以直接用 pandas 读取 Hadoop 输出目录中的part-00000文件再批量插入 MySQL。这个方案能避免让用户手工执行太复杂的数据导入流程适合毕业设计场景。6. 模块三协同过滤推荐算法6.1 算法选型与思路推荐算法是这个系统比较出彩的加分模块。毕业设计中常用的推荐算法包括基于内容的推荐、基于协同过滤的推荐以及混合推荐。这里选择“基于物品的协同过滤算法Item-Based Collaborative Filtering”主要原因是它非常依赖用户行为数据能与爬虫采集到的小说数据形成自然联动而且解释起来相对直观。ItemCF 的核心思想是如果很多用户同时喜欢小说 A 和小说 B那么 A 和 B 是相似的。当用户阅读了小说 A 以后系统就可以把与 A 相似的其他小说推荐给用户。具体实现时我们需要准备用户-物品评分矩阵然后用余弦相似度计算小说两两之间的相似度再根据用户已评分小说对候选小说进行加权打分最终输出 TopN 推荐结果。6.2 基于物品的协同过滤实现假设我们有一份用户行为数据字段为 user_id, book_id,

相关新闻

最新新闻

调试记录2026

调试记录2026

2026年7月20日 Depth Anything V3生成的点云,效果看上去还可以 在Photoshop中进行高斯模糊(模拟失焦)后再检测,依然可以保持尺相对度: 更大的高斯模糊 在图片中添加纯色块 2026年8月10日 MUJOCO动力学仿真

2026/9/6 0:00:46
AI Agent之后,企业真正需要的不是一个机器人,而是一支AI员工团队

AI Agent之后,企业真正需要的不是一个机器人,而是一支AI员工团队

系列文章目录 《AI Agent之后,企业真正需要的不是一个机器人,而是一支AI员工团队》 《为什么很多企业用了AI工具,却没有真正实现AI转型?》 《未来企业组织架构:人类员工 AI数字员工》 《企业建设AI Agent&#xff0c…

2026/9/6 0:00:46
基于CNN的调制信号识别:MATLAB实现时频图分类实战

基于CNN的调制信号识别:MATLAB实现时频图分类实战

简介:本资源是一套面向通信工程与信号处理方向学习者、研究者的深度学习实践方案,聚焦调制信号自动检测与识别这一典型无线通信任务,解决传统方法依赖人工特征、低信噪比下性能下降等痛点。压缩包共12个文件(10.73MB)&…

2026/9/6 0:00:46
超人VS蜘蛛侠:拆解超级IP的影响力与传播方法论

超人VS蜘蛛侠:拆解超级IP的影响力与传播方法论

把“蜘蛛侠 vs 超人”放在 CSDN 上聊,可能很多人第一反应是走错片场了。但如果把这两个角色看成“两个持续运营了 80 多年的文化产品”,你会发现,这场比较本质上是两个不同 IP 策略的长期结果对比:超人赢在定义了整个超级英雄题材…

2026/9/6 0:00:46
超人会飞不算本事:系统稳定依赖清晰规则与边界设计

超人会飞不算本事:系统稳定依赖清晰规则与边界设计

开头先不绕弯子。“#斯坦李吐槽dc 所以超人是无缘无故会飞的嘛哈哈哈哈哈哈哈锤哥真是技术人才啊!#雷神 #复联”这类调侃式短标题,第一波冲击力在于它把两个宇宙的角色塞进同一个吐槽箱里,但细想一下就能发现,它真正碰到的根本不是…

2026/9/6 0:00:45
微信小程序开发实战:从零构建塔罗牌占卜应用

微信小程序开发实战:从零构建塔罗牌占卜应用

简介:这是一份面向微信小程序开发者与塔罗文化爱好者的学习型项目资源,提供完整的塔罗牌占卜类小程序实现方案,解决个性化运势查询、多场景占卜交互及轻量级商业功能集成等实际开发需求。压缩包共72个文件,含49张塔罗牌高清图片&a…

2026/9/5 23:55:45