恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
基于Hadoop的网络小说数据分析系统毕业设计全流程详解
首页
资讯中心
/
基于Hadoop的网络小说数据分析系统毕业设计全流程详解
基于Hadoop的网络小说数据分析系统毕业设计全流程详解
发布时间:2026/9/5 23:26:27
做网络小说数据分析这套毕业设计很多同学第一反应是“能不能直接运行”。但真正决定答辩成败的往往不是代码能不能跑而是你能不能把整条数据链路讲清楚。本文以“基于 Hadoop 的网络小说数据分析系统”为主线按毕业设计交付顺序拆解了 Python 爬虫、HDFS 存储、MapReduce 离线统计、协同过滤推荐、Web 可视化展示等核心模块的实现思路和踩坑点并整理了适合写进文档与答辩准备的工程建议。无论是做大数据方向课设还是用 Python 做毕设这套技术组合都有很强的参考价值。1. 这类毕业设计项目解决什么问题如果你的选题叫《基于 Hadoop 的网络小说数据分析系统》第一反应通常是把小说数据抓下来存进 Hadoop再做点统计和展示。这个理解没错但还不够。把这句话讲成“爬虫 Hadoop 可视化”的拼盘只会让老师觉得你在堆技术。真正能撑起一篇毕业设计论文的是一个完整的问题链路网络小说平台每天产生大量的小说信息、分类标签、用户阅读行为、书评互动数据。这些数据分散、格式不统一、来源多样需要程序化采集和清洗。数据量增长到一定程度后使用单机文件或普通数据库存储和统计会遇到扩容和性能压力因此引入 Hadoop 的分布式文件系统和离线计算能力。光有数据没有应用价值所以需要从“分类分布、热门程度、字数区间、评分趋势”等维度做可视化分析。再进一步如果系统积累了用户阅读、收藏、打分记录就可以配合推荐算法让读者更容易发现喜欢的小说。大多数同学在这个项目里不是被某个算法难住而是被“数据在某个环节断了”这种情况卡住。例如爬虫明明返回 200写入文件却是空的文件传到 HDFS 之后又被当成一堆无意义小文件Mapper 跑完没有任何 Reduce 输出前端图表请求不到后端返回的数据。这些问题单独看都不难放在一个多模块项目里就会让人反复返工。所以这篇文章不仅告诉你模块怎么写还告诉你模块之间如何衔接以及出现问题时先查哪里。这个系统天然适合作为毕业设计是因为它的每一层都有一个明确的验收标准爬虫能产出结构化文件HDFS 能存下文件MapReduce 能算出指标推荐接口能返回结果可视化页面能把结果画出来。五个部分任选其一深入都能写出足够的论文字数而整体实现难度又在本科阶段可承受范围内。2. 系统总体架构与基础概念这个项目可以划分为五层采集层、存储层、计算层、应用层、展示层。每一层职责清晰向外只暴露文件、接口或目录不互相依赖配置细节。这样做的好处是论文里可以画架构图答辩时能按层讲解项目出问题时也能快速定位。层次主要技术核心职责输出产物数据采集层Python、Requests、BeautifulSoup定时抓取小说列表、详情、评论数据并清洗JSON 或 CSV 文件分布式存储层Hadoop HDFS保存原始文件、屏蔽单机磁盘扩容限制HDFS 目录离线计算层MapReduce 或 PySpark统计分类数量、热度排行、指标聚合文本结果文件或 Hive 表应用服务层Flask / Django、MySQL提供 Web 接口、保存用户行为数据、加载推荐结果REST API可视化展示层ECharts、HTML/JavaScript将统计结果转化为柱状图、饼图、排行榜、词云浏览器页面先解释几个容易混淆的概念。Hadoop 是一个开源的大数据基础框架核心包括 HDFS 和 MapReduce。HDFS 解决“多台机器上的大文件怎么存”的问题它会把大文件切成块分散到集群不同节点上并且提供副本机制防止节点故障丢数据。MapReduce 解决的则是“怎么把统计任务拆给多台机器并行计算”的问题它分为 Map 阶段和 Reduce 阶段。Map 阶段对每一条输入数据进行映射加工Reduce 阶段把相同 key 的数据汇总处理。这里的“网络小说数据分析系统”并不只是为了追求用一个大数据框架。从客观角度看如果只有几十 MB 数据单机 Python 脚本处理绰绰有余。但作为毕业设计使用 Hadoop 的价值在于完整理解大数据处理流程同时让系统具备向更大数据量扩展的基础。在论文里要这样表述而不是说“因为数据量巨大所以使用 Hadoop”。只谈前者老师会觉得你技术选型不严谨。Python 在系统里承担两种角色一是写爬虫二是写推荐算法和 Web 后端。这样组合很常见因为 Python 生态在爬虫和数据处理方面都很成熟而 Hadoop 自身也支持通过 Streaming 方式调用 Python 脚本编写 Mapper 和 Reducer这比直接用 Java 写 MapReduce 更容易被初学者接受也能避开繁琐的数据类型定义。可视化层使用 ECharts。它是前端的图表库能绘制柱状图、折线图、饼图、K 线图、词云等常见图表。前端只需要向后端请求 JSON 数据再把数据交给图表配置项即可。这样就把“Hadoop 分析结果”和“用户看得懂的图形”连接了起来。整体设计思路就一句话从网站采集数据先落到本地再进入分布式存储经过离线计算后把结果或推荐结果通过 Web 接口交给图表展示。下面按这一条链路逐步实现。3. 实验环境准备与依赖安装开始写代码之前先把环境准备好。这个系统涉及 Python 和 Hadoop 两部分两者对运行环境的要求不太一样。3.1 Python 环境爬虫、推荐算法、Web 后端都建议使用 Python 3。推荐在开发机上新建独立虚拟环境避免和系统 Python 或 Anaconda 里的依赖互相污染。python -m venv venvWindows 激活虚拟环境venv\Scripts\activateLinux / macOS 激活虚拟环境source venv/bin/activate激活后先升级 pippython -m pip install --upgrade pip依赖可以根据实际需要按模块分批安装。最基础的依赖有pip install requests beautifulsoup4 lxml flask pandas flask-cors如果后续要做词云和中文分词还需要安装pip install jieba wordcloud pymysql scikit-learn不写死版本号的原因是不同时间安装依赖版本会有差异而且不同 Hadoop 发行版对 Python 的动态库也存在细微要求。建议先用上面的命令安装最新稳定版本跑通全流程之后再用下面的命令锁定环境。pip freeze requirements.txt下次重新部署时执行pip install -r requirements.txt3.2 Hadoop 环境Hadoop 可以装在 Linux 上也可以装在 Windows 上。作为毕业设计演示可以使用 Hadoop 的伪分布式模式也就是只启动一台机器上的 NameNode、DataNode 和 ResourceManager模拟一个小型集群。这样做的好处是既能完整体验 HDFS 命令和 MapReduce 执行流程又不需要多台服务器。Hadoop 的运行依赖 Java。以常见的 Hadoop 2.x / 3.x 为例需要提前安装 JDK并配置好JAVA_HOME否则执行hadoop命令时会直接报错。Hadoop 的版本选择应该以你本地实际安装包为准不同版本的配置目录和端口号有细微区别。安装后建议先确认基本命令可用hadoop version如果提示找不到命令通常是环境变量没有配置。需要把 Hadoop 的安装目录和 bin 目录加入PATHexport HADOOP_HOME/your/path/to/hadoop export PATH$PATH:$HADOOP_HOME/bin:$HADOOP_HOME/sbin伪分布式模式启动前需要确认两个核心配置文件core-site.xml中配置 NameNode 地址configuration property namefs.defaultFS/name valuehdfs://localhost:9000/value /property /configurationhdfs-site.xml中配置副本数和 NameNode 数据目录。伪分布式环境副本数通常设为 1因为只有一台 DataNodeconfiguration property namedfs.replication/name value1/value /property property namedfs.namenode.name.dir/name valuefile:///your/path/namenode/value /property property namedfs.datanode.data.dir/name valuefile:///your/path/datanode/value /property /configuration配置完成后第一次启动需要格式化 NameNode。需要特别提醒的是格式化会清空 NameNode 元数据不能在生产环境随便执行。如果你后面因为反复修改配置需要重新初始化先确认这不影响其他模块且这是独立测试环境。hdfs namenode -format启动 HDFS 和 YARNstart-dfs.sh start-yarn.sh启动后检查进程jps正常情况下会看到NameNode、DataNode、SecondaryNameNode、ResourceManager、NodeManager这五个进程。如果缺少某一个要去对应日志目录排查而不是反复格式化。4. Python 爬虫模块设计与代码实现网络小说数据的采集是整个系统的数据源头。这一部分做不好后面 Hadoop 处理的都将是无效数据。爬虫设计要重点考虑三件事抓什么数据、怎么解析、如何稳定输出。4.1 目标数据设计在开始写爬虫之前先设计好要保存的字段。网络小说数据采集字段通常包括小说唯一标识例如 id书名作者分类例如玄幻、都市、历史是否连载完成的状态字数评分推荐票数或热度值简介评论数字段设计要照顾后续 HDFS 分析。如果原始数据用 CSV 保存字段顺序必须固定这样 MapReduce 处理时才能通过列索引取值。如果原始数据用 JSON 保存则要保证每个 JSON 对象结构一致。实际爬取时一定要先检查目标网站的robots.txt、用户协议以及法律政策并在论文中说明采集目的仅用于学习和研究控制请求频率不采集涉及个人隐私的内容。下面代码中的 URL、页面结构和 CSS 选择器都只是示例真实项目中要按目标网站结构调整。4.2 爬虫代码结构推荐把爬虫拆成三个函数请求页面、解析页面、保存结果。这样即使页面改版也只需要修改解析函数不需要推翻整套流程。# 文件路径spider/novel_spider.py import json import random import time import requests from bs4 import BeautifulSoup BASE_URL https://example.com/novel/list?page{page} HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36 } def fetch_page(page): url BASE_URL.format(pagepage) try: resp requests.get(url, headersHEADERS, timeout10) resp.raise_for_status() resp.encoding resp.apparent_encoding return resp.text except requests.RequestException as e: print(f[请求失败] 第 {page} 页: {e}) return None def parse_page(html): if not html: return [] soup BeautifulSoup(html, lxml) novels [] items soup.select(.novel-item) for item in items: title_node item.select_one(.title) if title_node is None: continue novels.append({ novel_id: item.get(data-id, ).strip(), title: title_node.get_text(stripTrue), author: item.select_one(.author).get_text(stripTrue) if item.select_one(.author) else , category: item.select_one(.category).get_text(stripTrue) if item.select_one(.category) else , word_count: item.select_one(.word-count).get_text(stripTrue) if item.select_one(.word-count) else , status: item.select_one(.status).get_text(stripTrue) if item.select_one(.status) else , score: item.select_one(.score).get_text(stripTrue) if item.select_one(.score) else , }) return novels def save_to_json(data, file_pathnovels.json): with open(file_path, w, encodingutf-8) as f: json.dump(data, f, ensure_asciiFalse, indent2) if __name__ __main__: all_data [] for page in range(1, 6): html fetch_page(page) items parse_page(html) print(f第 {page} 页解析到 {len(items)} 条数据) all_data.extend(items) time.sleep(random.uniform(1, 3)) save_to_json(all_data) print(f共保存 {len(all_data)} 条数据)这段代码里有几个容易被忽略的细节。resp.encoding resp.apparent_encoding这一步很关键。中文小说网站经常使用不同的编码格式如果不处理编码解析出来的文本很可能是乱码。apparent_encoding是 Requests 根据字节内容推测出来的编码适合应对页面没有显式声明编码的情况。解析时使用if item.select_one(.author) else 这种写法是为了避免字段缺失时程序抛出NoneType异常。真实页面中不一定每个条目都有作者、评分、字数这些空值需要在清洗阶段做处理而不是让解析器中断。time.sleep(random.uniform(1, 3))表示每抓一页休息 1 到 3 秒同时加入随机数避免形成太规律的请求节奏。这是为了避免给目标网站带来过大压力。运行爬虫python spider/novel_spider.py控制台会打印每一页解析到的数据条数。如果看到类似“第 1 页解析到 0 条数据”优先检查选择器是否写错而不是检查请求是否失败。请求失败会进入异常分支不会进入解析函数。所以“进程结束码为 0 但没有输出数据”的排查思路是确认页面内容是否真的返回。打印一小段 HTML确认要解析的节点存在。用解析器在本地调试单一页面。再跑全量循环。最终生成的novels.json是一个 JSON 数组每个元素一个字段。为了让后续存入 HDFS 更符合 MapReduce 的输入习惯可以使用 pandas 把 JSON 转为 CSV# 文件路径spider/convert_to_csv.py import pandas as pd df pd.read_json(novels.json) df.to_csv(novels.csv, indexFalse, encodingutf-8)输出文件会带表头。在写 Mapper 时要注意首行是字段名跳过表头或者在代码中单独处理。这里的字段顺序是novel_id,title,author,category,word_count,status,score。5. 数据上传 HDFS 并执行 MapReduce 统计爬虫输出只是第一步接着要把数据送到 Hadoop 文件系统。5.1 HDFS 目录设计与上传HDFS 和 Linux 目录类似但它是分布式文件系统。对目录做好规划会让后续工作更清晰。可以按照原始数据、清洗数据、输出结果来分层/novel/raw /novel/clean /novel/output创建目录并上传文件hdfs dfs -mkdir -p /novel/raw hdfs dfs -put novels.csv /novel/raw/查看上传结果hdfs dfs -ls /novel/raw/如果文件比较大可以查看它的块信息hdfs fsck /novel/raw/novels.csv -files -blocks -locations这个命令适合在论文中展示 Hadoop 分布式存储的工作效果说明一个文件被拆分成了哪些块、存放在哪些节点。在伪分布式模式下只有单个节点但依然能看到分块逻辑。5.2 使用 Hadoop Streaming 编写 MapReduceMapReduce 使用 Java 编写会比较繁琐。Hadoop 提供了 Streaming 模式允许用 Python、Shell 等其他语言编写 Mapper 和 Reducer。它只是把 stdin 作为输入流把 stdout 作为输出流因此适合演示统计逻辑。下面实现一个最常见的统计需求统计各分类下的小说数量。原始数据列顺序是第 3 列为category也就是列索引从 0 开始算的第 2 列。第一行是表头。Mapper 的作用是把每条记录加工成key, value对中间用\t分隔。# 文件路径hadoop_jobs/mapper.py #!/usr/bin/env python import sys first_line True for line in sys.stdin: line line.strip() if not line: continue if first_line: first_line False continue fields line.split(,) if len(fields) 3: continue category fields[2].strip() if not category: continue print(f{category}\t1)Reducer 会把 Mapper 输出中相同 key 的数据汇总起来。# 文件路径hadoop_jobs/reducer.py #!/usr/bin/env python import sys current_category None current_count 0 for line in sys.stdin: line line.strip() if not line: continue category, count_str line.split(\t, 1) try: count int(count_str) except ValueError: continue if current_category category: current_count count else: if current_category is not None: print(f{current_category}\t{current_count}) current_category category current_count count if current_category is not None: print(f{current_category}\t{current_count})这里的 Reducer 代码只适用于已经按 key 排好序的输入。Hadoop Streaming 在 Map 和 Reduce 之间会对 key 做一次排序所以相同类别的数据会连续到达。如果你的处理逻辑对顺序敏感需要在 Reducer 内部额外维护一个字典而不是假设数据一定有序。这个项目里简单计数不需要额外排序。运行时需要先找到 Hadoop Streaming 的 jar 文件。很多同学在运行时报“jar does not exist or is not a normal file”原因通常是把 jar 路径写成了固定路径但本地 Hadoop 目录结构或版本和网上教程不一致。更稳妥的方式是自动查找STREAMING_JAR$(find $HADOOP_HOME -name hadoop-streaming*.jar 2/dev/null | head -1) echo $STREAMING_JAR如果HADOOP_HOME没有设置可以手动指向 Hadoop 安装目录再查找HADOOP_HOME/usr/local/hadoop STREAMING_JAR$(find $HADOOP_HOME -name hadoop-streaming*.jar 2/dev/null | head -1)然后执行hadoop jar $STREAMING_JAR \ -input /novel/raw/novels.csv \ -output /novel/output/category_count \ -mapper python mapper.py \ -reducer python reducer.py \ -file mapper.py \ -file reducer.py注意-file mapper.py不是可选的。它的作用是把本地脚本上传到 Hadoop 集群中所有需要执行任务的节点。如果没有-file即使你的机器能跑脚本集群节点也可能找不到脚本文件。运行结束后查看结果hdfs dfs -ls /novel/output/category_count/ hdfs dfs -cat /novel/output/category_count/part-00000结果文件的内容应该类似都市 42 玄幻 56 历史 13 悬疑 18这里的数字取决于爬虫采集到的数据量。为了验证统计结果是否正确最简单的方法是在本地用 pandas 统计一次和 HDFS 输出比对。import pandas as pd df pd.read_csv(novels.csv) print(df[category].value_counts())如果本地统计结果与 HDFS 输出一致说明数据链路已经打通。这一步是整篇文章中最重要的验收点因为后面任何可视化图表、推荐输入都依赖数据能可靠流动。6. 推荐算法设计与实现网络小说推荐是整个系统里最有“算法感”的模块。这里要用到协同过滤。什么叫协同过滤简单说就是利用一群人过去的行为来推测某个用户可能喜欢什么物品。例如读者 A 喜欢玄幻类小说找到一群和 A 行为相似的用户再看看这群用户还喜欢哪些 A 没读过的小说把结果推荐给 A。在这个毕业设计项目里如果缺少真实用户点击数据可以先构造模拟评分数据例如用户对小说的打分、点击次数映射成 1 到 5 分、收藏行为视为高分。必须在论文中说明这些是实验模拟数据而不是真实采集的用户隐私数据。本章用“基于物品的协同过滤”作为讲解重点因为它的推荐结果更容易解释“你看过的小说 A 和相关小说 B 相似所以推荐 B”。6.1 数据准备假设系统中有用户行为数据每条记录包含 user、novel、score 三个字段。score 可以来自显式评分也可以通过阅读时长、收藏和评论等行为综合得到。为了跑通代码先准备一个很小的数据集合# 文件路径recommend/rating_data.py SAMPLE_RATINGS [ (u001, n001, 5), (u001, n002, 4), (u001, n003, 3), (u002, n001, 4), (u002, n002, 5), (u002, n004, 2), (u003, n003, 5), (u003, n004, 4), ]6.2 构建物品相似度矩阵基于物品的协同过滤关键是计算物品之间的相似度。可以把每一本小说映射到一个用户评分向量上。两个小说如果有越多相同的用户打分并且打分方向接近它们就越相似。下面使用余弦相似度计算两个物品的相似度。公式的含义是衡量两个向量夹角的余弦值值越接近 1说明两个向量方向越一致。# 文件路径recommend/item_cf.py import math from collections import defaultdict def build_item_similarity(ratings): item_users defaultdict(dict) user_items defaultdict(dict) for user, item, score in ratings: item_users[item][user] score user_items[user][item] score items list(item_users.keys()) sim_matrix defaultdict(dict) for i in range(len(items)): for j in range(i 1, len(items)): item_a items[i] item_b items[j] common_users set(item_users[item_a].keys()) set(item_users[item_b].keys()) if not common_users: continue dot sum(item_users[item_a][u] * item_users[item_b][u] for u in common_users) norm_a math.sqrt(sum(v * v for v in item_users[item_a].values())) norm_b math.sqrt(sum(v * v for v in item_users[item_b].values())) if norm_a 0 or norm_b 0: continue sim dot / (norm_a * norm_b) if sim 0: sim_matrix[item_a][item_b] sim sim_matrix[item_b][item_a] sim return sim_matrix, user_items, item_users这段代码没有使用 sklearn目的是让推荐逻辑清晰可见。在毕业设计演示中你甚至可以把相似度计算过程截图放进论文附录。6.3 生成 TopN 推荐当用户浏览某本小说而不是打分时系统要给他推荐类似的其它小说。推荐逻辑是对用户已经产生过行为的小说找到所有相似小说累加“用户对已读小说的评分 × 相似度”然后取出分数最高的前几本。# 文件路径recommend/item_cf.py续 def recommend_for_user(user_id, ratings, top_n5): sim_matrix, user_items, _ build_item_similarity(ratings) if user_id not in user_items: return [] liked_items user_items[user_id] scores defaultdict(float) for liked_item, rating in liked_items.items(): similar_items sim_matrix.get(liked_item, {}) for candidate_item, sim in similar_items.items(): if candidate_item in liked_items: continue scores[candidate_item] sim * rating ranked sorted(scores.items(), keylambda x: x[1], reverseTrue) return ranked[:top_n]运行推荐# 文件路径recommend/run_recommend.py from rating_data import SAMPLE_RATINGS from item_cf import recommend_for_user result recommend_for_user(u001, SAMPLE_RATINGS, top_n3) print(推荐结果) for novel_id, score in result: print(f{novel_id}: {score:.4f})对 u001 来说他看过 n001、n002、n003因此推荐结果中不会出现这些小说只会返回他未看过的小说的预测分。输出结果可能类似建议为用户 u001 推荐 n004: 计算得到的加权分数推荐模块在真实项目中不会每次请求都重新计算相似度矩阵而是通过离线任务定期计算把结果缓存。毕业设计阶段可以做一次离线训练然后保存到本地文件或 MySQLWeb 接口直接读取。这种设计虽然没有线上实时计算那么“炫”但逻辑更清晰也更容易在答辩中解释。这里也要提一个常见误区很多同学觉得推荐算法越复杂越好。实际上基于协同过滤的短板是冷启动问题比如新书没有用户行为数据新用户没有历史行为算法就难以推荐。对毕业设计而言能在演示中主动指出这个局限并说明可以用基于内容的推荐做补充面试官或老师通常会给你加分。7. Web 服务与可视化展示模块在 HDFS 和推荐模块都跑通之后需要一个让用户能直观看到的界面。这里用 Flask 搭建后端用 ECharts 绘制前端图表不涉及重型前端工程。7.1 Flask 提供统计数据接口后端接口的作用是让前端能够按需请求数据。后端可以从 HDFS 导出的结果文件读取分类统计结果也可以从 MySQL 查询。为了方便演示先以内存数据为例再替换成文件读取。# 文件路径web/app.py from flask import Flask, jsonify from flask_cors import CORS app Flask(__name__) CORS(app) def load_category_count(): # 生产环境中建议改为从 HDFS 输出文件或 MySQL 表读取 # 这里仅演示返回数据结构 return [ {category: 玄幻, count: 56}, {category: 都市, count: 42}, {category: 历史, count: 13}, {category: 悬疑, count: 18}, ] app.route(/api/category/count, methods[GET]) def category_count(): data load_category_count() return jsonify({code: 0, message: success, data: data}) app.route(/api/novel/recommend/user_id, methods[GET]) def recommend(user_id): # 真实项目中推荐结果由推荐模块离线生成这里返回模拟数据结构 data [ {novel_id: n004, title: 示例小说, reason: 基于内容相似推荐} ] return jsonify({code: 0, message: success, data: data}) if __name__ __main__: app.run(host0.0.0.0, port5000, debugTrue)如果从 HDFS 直接读取结果常见做法是先用hdfs dfs -getmerge把输出目录合并到本地再让 Flask 读取。尽量不要让 Web 服务器频繁调用 HDFS 客户端否则容易出现权限、网络等额外问题。hdfs dfs -getmerge /novel/output/category_count category_count.tsv读取本地文件的逻辑可以放在load_category_count()中。使用 TSV 文件时按\t分隔字段def load_category_count(): result [] with open(category_count.tsv, r, encodingutf-8) as f: for line in f: line line.strip() if not line: continue category, count line.split(\t) result.append({category: category, count: int(count)}) return result7.2 前端页面与 ECharts 渲染前端可以是一个非常简洁的 HTML 页面通过 Fetch 请求 Flask 的接口再用 ECharts 绘制柱状图。这样做的最大好处是前后端分离但又不引入 Node 工程。!DOCTYPE html html langzh-CN head meta charsetUTF-8 title网络小说数据分析系统/title script srchttps://cdn.jsdelivr.net/npm/echarts5.4.3/dist/echarts.min.js/script /head body h2网络小说分类数量统计/h2 div idmain styleheight: 400px;/div script fetch(http://127.0.0.1:5000/api/category/count) .then(function (resp) { return resp.json(); }) .then(function (res) { if (res.code ! 0) { console.error(接口异常, res.message); return; } var chart echarts.init(document.getElementById(main)); var categories []; var counts []; res.data.forEach(function (item) { categories.push(item.category); counts.push(item.count); }); chart.setOption({ title: { text: 小说分类数量分布 }, tooltip: {}, xAxis: { type: category, data: categories }, yAxis: { type: value }, series: [ { name: 小说数量, type: bar, data: counts, itemStyle: { color: #4f81bd } } ] }); }) .catch(function (err) { console.error(请求失败, err); }); /script /body /html这个页面先发起接口请求收到数据后调用echarts.init初始化图表容器然后通过setOption填充数据和配置。如果页面展示不出来不要急着改图表配置先打开浏览器控制台确认接口返回的数据结构是否正确。7.3 更多可视化展示思路除了分类柱状图还可以从下面这些维度做可视化让系统看起来更完整图表类型数据分析维度数据来源词云小说简介高频词jieba 分词 wordcloud折线图热门小说推荐票增长趋势时间序列数据饼图小说状态占比例如连载中与已完结MapReduce 统计排行榜作者小说数量排行HDFS 聚合结果散点图或热力图字数与评分关系数据分析结果词云部分用 Python 处理中文文本时需要使用 jieba 分词。核心代码非常简单# 文件路径analysis/word_cloud.py import jieba import pandas as pd from collections import Counter df pd.read_csv(novels.csv) text .join(df[introduction].dropna().astype(str).tolist()) words jieba.cut(text) words [w for w in words if len(w) 1 and w not in {小说, 主角, 内容}] counter Counter(words) top_words counter.most_common(30) print(top_words)这一步生成的关键词列表可以输出成 JSON 接口再由 ECharts 的 wordCloud 插件渲染。如果没有接入词云需要的插件也可以先用柱状图展示 Top 30 关键词答辩效果同样不差。8. 常见问题与排查方法在毕业论文写“遇到的问题与解决方案”时下面的表格可以直接作为素材。这些问题是 HDFS Python 爬虫 可视化项目中最高频出现的坑。问题现象可能原因排查方式解决方案爬虫运行结束码为 0但没有任何数据输出选择器没匹配到元素循环里items为空打印某页 HTML确认节点结构修改解析规则对单页做调试页面文本出现大量乱码没有处理编码或编码声明错误查看响应 Content-Type使用resp.apparent_encoding或指定页面 charsetHDFS 上传文件时报权限异常当前用户不是 Hadoop 集群授权用户查看日志中用户名称和路径权限在测试环境调整目录权限或使用授权用户运行 hadoop jar 时报 jar does not exist or is not a normal fileHadoop Streaming jar 路径不对检查HADOOP_HOME和find查找结果不要在脚本里写死 jar 的版本路径Map 执行成功但 Reduce 输出为空Mapper 输出的 key/value 分隔符不是\t或输入文件带表头未跳过在本地构造小文件模拟执行统一使用\t分隔处理表头NameNode 启动失败日志显示目录冲突格式化或数据目录配置不干净检查dfs.namenode.name.dir指向的目录备份文件后清空元数据目录重新格式化测试环境第一次格式化后jps缺少 DataNode格式化后 DataNode 进程启动失败查看 dfs data 目录写权限保证数据目录存在且用户有写权限Flask 接口能返回 JSON前端却只显示空白跨域问题或接口请求路径不对查看浏览器 Console 和 Network后端启用 CORS前端确认 URLECharts 柱状图没有柱子数据格式不是数组或 x 轴数据为空Console 打印res.data规范后端返回 JSON 结构小文件过多HDFS 里全是几十 KB 的文件爬虫每次写入不同文件对文件排序和合并定期合并 CSV 文件或先落 MySQL 再导出推荐接口返回结果总是为空用户没有任何历史行为或所有候选都已经被看过打印用户已读和候选集合增加冷启动规则例如按热度推荐关于“小文件”问题需要特别重视。HDFS 最不适合存储大量小文件因为每个文件的元数据都要占用 NameNode 内存。如果爬虫每天抓一次就生成一个小 JSON长期积累会出现性能问题。解决方案是在进入 HDFS 之前把多天的数据合并成一个或少数几个大文件这是很值得写进论文的工程优化点。9. 系统完整性检查与效果验证一个多模块系统不能等全部写完再开始测试。建议按照下面的顺序分步验收。第一步验证爬虫模块。单独运行novel_spider.py检查 JSON 文件是否包含目标字段条数是否符合预期。第二步验证 HDFS 存储。查看文件块信息和文件大小确认上传成功。第三步验证 MapReduce 统计。使用本地 pandas 统计结果与 HDFS 输出对比保证数值一致。第四步验证推荐模块。构造一个只有几条评分数据的小集合人工推算一位用户的推荐结果再和代码输出对照。如果代码按余弦相似度计算可以先人工选择两本相似小说确认相似度数值合理。第五步验证 Flask 接口。直接在浏览器访问http://127.0.0.1:5000/api/category/count先确认 JSON 能正常显示。第六步验证前端图表。打开index.html页面刷新后能看到柱状图。如果图表空白先打开浏览器开发者工具看 Network 面板中请求是否返回 200再看 Console 是否有 JavaScript 报错。整个链条里最隐蔽的问题是编码。Windows 上文件编码可能是gbk而 HDFS 上默认按字节处理如果后面用 Hive 或 Spark 读取很容易出现中文乱码。建议在导出 CSV 时统一使用 UTF-8 编码并在后续所有脚本里显式指定encodingutf-8。df.to_csv(novels.csv, indexFalse, encodingutf-8)如果从 CSV 中读取数据后打印到控制台乱码可能只是终端编码问题不代表文件本身有问题可以先用 Python 打开文件写出到一个新的文本文件检查。10. 最佳实践与毕业答辩准备最后这部分讲的是“怎么把这个项目做得更像工程而不像课程作业”。第一数据文件统一放在 data 目录下脚本统一放在各自模块目录下。例如spider/、hadoop_jobs/、recommend/、