基于Hadoop的网络小说数据分析系统设计与实现:爬虫到可视化
2026/9/5 23:25:25 网站建设 项目流程

如果你正在为计算机毕业设计选题发愁,又希望做一套“有点技术含量、能讲清原理、还能拿出漂亮可视化页面”的系统,那么“基于 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,输出分类统计结果;统计结果导入 MySQL;Flask 从 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 版本
  • Hadoop:2.10.x 或 3.3.x,单机伪分布式即可满足毕设演示
  • JDK:Hadoop 3.x 通常要求 JDK 8 或 JDK 11
  • MySQL:5.7 或 8.0
  • Flask:2.x
  • 浏览器:Chrome / Edge

如果电脑内存只有 8G,建议使用 Hadoop 伪分布式模式,不要强行搭建三个节点的集群。伪分布式模式启动的进程足够支撑你演示 HDFS 和 MapReduce 功能,对毕业设计来说完全够用。

3.2 Hadoop 环境搭建要点

Hadoop 环境配置是很多同学第一次接触 Linux 时最头疼的部分。这里整理一些关键步骤,更详细的安装过程可以结合网上教程逐步操作。

首先安装 JDK 并配置环境变量。Hadoop 启动时会依赖 JAVA_HOME,如果环境变量没有配置正确,启动脚本会直接报错。然后解压 Hadoop 安装包,修改core-site.xmlhdfs-site.xmlyarn-site.xml。伪分布式模式下,core-site.xml中主要配置 NameNode 地址,hdfs-site.xml中配置副本数为 1。

<!-- core-site.xml 核心配置片段 --> <configuration> <property> <name>fs.defaultFS</name> <value>hdfs://localhost:9000</value> </property> </configuration>
<!-- hdfs-site.xml 核心配置片段 --> <configuration> <property> <name>dfs.replication</name> <value>1</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 页面解析核心代码

下面给出一个可运行的爬虫思路,重点展示请求、解析、字段抽取三层逻辑。代码中使用了source="local"本地页面模式和source="online"在线模式,方便你在毕设环境中先跑通流程。

# 文件路径: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, source="local", total_pages=5): 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", f"page_{page}.html") with open(path, "r", encoding="utf-8") as f: return f.read() # 正式抓取时替换为目标站点地址,并确认抓取行为的合规性 url = f"https://your-site.example.com/novel/list?page={page}" resp = requests.get(url, headers=HEADERS, timeout=10) 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, columns=FIELD_NAMES) os.makedirs("../data/raw", exist_ok=True) df.to_csv("../data/raw/novel_raw.csv", index=False, encoding="utf-8-sig") print(f"抓取完成,共保存 {len(df)} 条数据") if __name__ == "__main__": spider = NovelSpider(source="local", total_pages=3) spider.run()

这段代码里有两个地方需要特别说明。第一,BeautifulSoup 的选择器依赖目标站点页面结构,如果页面结构调整,解析结果就会为空,因此建议先通过浏览器开发者工具确认真实标签类名,再替换selectselect_one中的参数。第二,代码中使用source="local"时,需要在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"], keep="first") 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(",", ""), errors="coerce").fillna(0) df["recommend_count"] = pd.to_numeric(df["recommend_count"].astype(str).str.replace(",", ""), errors="coerce").fillna(0) df = df[df["word_count"] > 0] df = df.sort_values("click_count", ascending=False) import os os.makedirs(os.path.dirname(output_path), exist_ok=True) df.to_csv(output_path, index=False, encoding="utf-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(), key=lambda x: x[1]["total_click"], reverse=True ): 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 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

实际课堂演示时,也可以直接用 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,

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询