☰
东野圭吾文本挖掘大作业:从语料清洗到情感分析的完整实战
2026/10/2 3:24:15 网站建设 项目流程

简介:面向需要完成数据挖掘实践任务的在校生,这是一套以东野圭吾小说集为分析对象的Python文本挖掘项目,涵盖期末大作业、课程设计与毕业设计等典型应用场景,可直接作为高分项目参考。项目包含完整源码与文档说明,代码附有详细注释,新手也能快速上手部署;系统功能完善、界面直观,且经过严格调试确保可运行,具有较高的实际应用价值。压缩包整体约25.78MB,以源码和说明文档为核心,便于直接下载使用。目前已有518人学习/下载,得到导师认可的高分评价,可帮助读者快速搭建文本挖掘分析流程,理解分词、词频统计、可视化等关键环节,并可直接作为课程作业或毕设的基础框架加以扩展,有效节省从零搭建的时间。

1. 东野圭吾文本挖掘大作业:从语料到报告的一次完整闭环

每年数据挖掘课程结课的时候,总有一批人卡在同一个问题上:数据集是现成的,模型是调包的,但报告交上去,老师一句"分析深度不够"就打回来了。东野圭吾小说集文本挖掘这个题目之所以常被选作大作业,是因为它自带三个天然优势:语料容易获取、文本长度足够做统计、作者风格有明显变化可以挖出故事。但绝大多数人把作业做成了一次词频统计——画几张词云、贴一个 TF-IDF 热点词表就收工,这撑不起高分。这篇笔记要讲的,是把文本挖掘从"交差"做到"能讲出道理"的完整落地路径:从语料清洗、分词调参,到主题建模和情感走向,每一步都给出可直接复制的代码和参数,也把容易翻车的地方提前指出来。适合正在做数据挖掘大作业、期末项目,或者想把手头一堆小说文本真正挖出点东西的读者。

2. 语料准备与清洗:构建能复现的东野圭吾文本文档集

2.1 语料从哪来:公开来源、自备文件和版权边界

做中文文本挖掘大作业,第一步不是写代码,是先解决"有没有干净语料"的问题。常见做法有三种,按优先级排序:

  • 使用课程提供的样例语料或教材附带数据,能省掉大量清洗时间,但缺点是作品不全,做风格对比时样本不够。
  • 从公开文本库或 GitHub 上的语料仓库获取,这类语料往往是别人整理过的,格式比较规整,但要留意编码是不是 UTF-8,很多老仓库给的是 GBK。
  • 自备电子书自行抽取,这是自由度最高的方式,可以按自己需求选作品、定篇章范围,但清洗工作量也最大,需要处理目录、注释、空白页等噪声。

版权方面提醒一句:大作业是学习用途,语料不建议大规模分发,也不要把爬虫写得像在扫站,控制请求频率,只取自己需要的少量作品。课程答辩时老师看的是方法和结论,不是语料库多大。

2.2 建立统一语料目录:按作品存储的目录约定

不管语料从哪来,我一般先统一落成这样的目录结构,后面每一步都不用来回改路径:

corpus/ ├── 白夜行.txt ├── 嫌疑人X的献身.txt ├── 解忧杂货店.txt ├── 放学后.txt └── 恶意.txt

每部作品一个 TXT 文件,文件名即作品名。选 5 到 8 部作品比较合适:太少做不了对比,太多清洗和标注的时间会失控。大作业的时间预算下,6 部左右是最舒服的。

2.3 清洗脚本:去掉目录、空行和乱码字符

拿到 TXT 后第一件事就是清洗。很多网上下载的小说文本带有章节目录、页眉页脚、全角空格和不可见字符,这些残留会直接污染后续的分词统计,尤其是高频词表里出现一堆"第一章""目录"之类的噪声词,答辩时很尴尬。

清洗脚本按规则逐行过滤:

import re def clean_novel(text): # 删除常见垃圾行:目录、章节标记、纯数字页眉页脚 lines = text.split("\n") cleaned = [] for line in lines: line = line.strip() if not line: continue # 跳过目录/章节行 if re.match(r"^[((]?第[0-9一二三四五六七八九十百千]+[章节回卷].*", line): continue if re.match(r"^\d+$", line): # 纯数字页码 continue if len(line) < 2: # 单字行大概率是噪声 continue cleaned.append(line) return "\n".join(cleaned) with open("corpus/白夜行.txt", "r", encoding="utf-8") as f: raw = f.read() cleaned = clean_novel(raw) with open("corpus_clean/白夜行.txt", "w", encoding="utf-8") as f: f.write(cleaned)

这段逻辑的关键在正则那一行:第[0-9一二三四五六七八九十百千]+[章节回卷]能覆盖大部分中文小说的章节标题格式。注意有些电子书的目录不是"第X章"格式,而是直接用"XX事件""XX人物"做章名,那种情况可以改用长度阈值加人工抽查。编码统一用 UTF-8 保存,后面读入时不用再猜编码。清洗后人工抽查 5 个文件的开头 50 行,确认没有整段丢失即可。

3. 中文分词与特征提取:jieba 加 TF-IDF 的默认搭配

3.1 jieba 分词参数:精确模式、自定义词典和停用词三层设置

中文文本挖掘的第一步是分词。没有分词,后面的词频统计、主题模型全部无法进行。常用方案是 jieba,速度快、安装简单、中文支持度好,对作业项目足够用。

import jieba # 加载自定义词典,提高人名、专有名词的切分准确率 jieba.load_userdict("dict/novel_dict.txt") # 精确模式分词,返回 list words = jieba.lcut(cleaned_text)

自定义词典是这里最值得花时间的环节。以东野圭吾作品为例,如果不加词典,"汤川学"很可能被切成"汤川/学","加贺恭一郎"会被切得乱七八糟。词典格式很简单,每行一个词,可以带词频和词性:

汤川学 10 nr 加贺恭一郎 10 nr 东野圭吾 10 nr 白夜行 10 n 解忧杂货店 10 n

词频数字建议填 10 左右,太高会导致这个词被强制保留,反而干扰统计;词性标注可写可不写,大作业层面写了没坏处。词典内容根据你的语料范围来定,选的作品不同,人物名词表也不同。一个省事的做法是把所有作品里出现超过 20 次的专有名词列出来,人工筛选后加入词典。

分词之后还差一步——去停用词。停用词表直接决定高频词的质量。"的""了""是""在"这类虚词如果不删,词频统计前十全是它们,词云和图谱都没有意义。建议使用通用的中文停用词表,再针对小说文本手动增加一些词,比如"说道""看见""知道""时候""因为""所以"这类叙事高频但无分析价值的词。

stopwords = set() with open("dict/stopwords.txt", "r", encoding="utf-8") as f: for line in f: stopwords.add(line.strip()) # 保留长度 > 1 的词,过滤停用词 final_words = [w for w in words if len(w) > 1 and w not in stopwords]

3.2 TF-IDF 特征提取:用 sklearn 做作品间对比

分词完成了,下一步是特征化。大作业里最常被要求的特征提取方法是 TF-IDF,它解决的问题很直接:某些词在单篇作品里出现频率高,但在全部作品里只有这篇出现,说明它是这部作品的标识性词汇。TF-IDF 计算综合了词频和逆文档频率,适合拿来做作品风格对比和关键词提取。

from sklearn.feature_extraction.text import TfidfVectorizer # 语料列表:每个元素是一部作品清洗+分词后的文本,空格连接 corpus_texts = [ " ".join(tokenize_one_file("corpus_clean/白夜行.txt")), " ".join(tokenize_one_file("corpus_clean/嫌疑人X的献身.txt")), # ... 依此类推 ] vectorizer = TfidfVectorizer(max_features=3000, ngram_range=(1, 2)) tfidf_matrix = vectorizer.fit_transform(corpus_texts) feature_names = vectorizer.get_feature_names_out()

max_features=3000控制只保留 TF-IDF 值最高的 3000 个特征,避免维度爆炸;ngram_range=(1, 2)同时保留单词和二元词组,像"不在场证明""本格推理"这类双字词组成的短语可以保留下来。需要注意,tokenize_one_file需要自己封装,内部做读取、清洗、分词、去停用词四步。

拿到 TF-IDF 矩阵后,可以打印每部作品里 TF-IDF 值最高的 15 个词,这就是答辩时的关键素材:

import numpy as np for i, title in enumerate(titles): row = np.asarray(tfidf_matrix[i].todense()).flatten() top_idx = row.argsort()[-15:][::-1] top_words = [feature_names[j] for j in top_idx] print(f"{title}: {', '.join(top_words)}")

这段输出的价值在于,你会明显看到《白夜行》和《解忧杂货店》的关键词几乎不重叠——前者偏暗色调词汇,后者偏温暖治愈类词汇。用 TF-IDF 数值作为"风格指纹"做对比,是文本挖掘大作业里最稳的一个亮点。

3.3 词云制作:让中文不显示成方块的必要设置

词云几乎是每份大作业的刚需可视化,但中文词云有个必经的坑:默认字体不支持中文。直接用 wordcloud 包默认字体会得到一堆方块,需要显式指定中文字体路径。

from wordcloud import WordCloud import matplotlib.pyplot as plt font_path = "C:/Windows/Fonts/simhei.ttf" # Windows 黑体,Mac 用 /System/Library/Fonts/PingFang.ttc wc = WordCloud( font_path=font_path, width=1600, height=900, background_color="white", max_words=200, colormap="Dark2", random_state=42 # 固定随机种子,保证结果可复现 ) wc.generate(" ".join(final_words)) wc.to_file("output/wordcloud.png")

max_words=200控制词云里显示前多少个高频词,想过图干净就调到 150 以下。random_state=42必须设,不设的话每次运行词云布局不同,报告里的图没法复现,答辩时如果老师问"怎么保证结果一致性",这就是一个减分项。词云本身信息量不大,建议放置在报告的分析章节开头作为视觉概览,重点分析放在 TF-IDF 和后面的主题模型上。

4. 主题建模与情感分析:拉开分数差距的两个分析

4.1 LDA 主题建模:从困惑度到落地解释的完整做法

词频统计和 TF-IDF 只能回答"每部作品里什么词多",但回答不了"作品在讲什么主题"。主题建模是对整个语料库进行主题推断的常用手段,LDA 是最经典的一种。它的基本假设是:每篇文档是多个主题的混合,每个主题是多个词的分布。用 LDA 能看到,比如"侦探推理""家庭伦理""情感救赎"这些主题在各作品里各占多少比例。

from sklearn.decomposition import LatentDirichletAllocation from sklearn.feature_extraction.text import CountVectorizer # 用词频矩阵作为 LDA 输入 vectorizer = CountVectorizer(max_features=5000, token_pattern=r"(?u)\b\w+\b") count_matrix = vectorizer.fit_transform(corpus_texts) lda = LatentDirichletAllocation( n_components=5, random_state=42, learning_method="batch", max_iter=25 ) lda.fit(count_matrix) # 打印每个主题的 top 词 feature_names = vectorizer.get_feature_names_out() for topic_idx, topic in enumerate(lda.components_): top_words = [feature_names[i] for i in topic.argsort()[:-11:-1]] print(f"Topic {topic_idx}: {' '.join(top_words)}")

n_components=5是先入为主的主题数,初学者建议直接用 5 到 8 试跑,然后看每个主题的词列表能不能被"一句话命名"。learning_method="batch"适合语料量小的场景,比在线学习更稳定;max_iter=25是迭代次数的保守值,语料大时可调到 50。

关于主题数怎么选,大作业层面不需要太复杂。常见做法是分别跑 n_components=4、5、6、7,人工对比每个主题的 top 词可解释性。如果某个主题的 top 词是"一个""这个""就是"这种泛化词,说明主题数不合理或分词环节有问题。答辩时能解释清楚"我选了哪些主题数,比较后留下可解释性最强的",这个深度足够拿到不错的评价。

LDA 的结果建议再可视化一层。pyLDAvis 是业界标准的交互式主题可视化工具,它能展示主题间距离和每个主题的 top 词分布,放进报告截图,视觉说服力很强。

import pyLDAvis.sklearn panel = pyLDAvis.sklearn.prepare(lda, count_matrix, vectorizer) pyLDAvis.save_html(panel, "output/lda_visualization.html")

4.2 情感分析:SnowNLP 的中文适配与局限

情感分析是给大作业加分的第二个分析维度。东野圭吾的作品跨度很大,从《白夜行》的压抑到《解忧杂货店》的温暖,用情感倾向做作品对比非常直观。

from snownlp import SnowNLP import matplotlib.pyplot as plt sentiments = [] for text in whole_texts: # whole_texts 是每个作品清洗后的全文 s = SnowNLP(text) sentiments.append(s.sentiments) # 返回 0~1 的情感倾向值,0.5 为中性

SnowNLP 的情感值输出范围是 0 到 1,越接近 1 越正向。但注意,直接对整本小说做情感分析会有均值回归问题——一本 20 万字的书,无论多黑暗,平均情感值也会落在 0.4 到 0.6 之间,区分度不高。更有效的做法是分段计算,按章节或按固定长度切分,画出整本书的情感曲线,对比《白夜行》和《解忧杂货店》的曲线形态差异。

import re def split_by_chunks(text, chunk_len=2000): return [text[i:i + chunk_len] for i in range(0, len(text), chunk_len)] chunks = split_by_chunks(whole_texts[0]) chunk_scores = [SnowNLP(c).sentiments for c in chunks] plt.figure(figsize=(12, 4)) plt.plot(chunk_scores, linewidth=1.2) plt.xlabel("文本分块序号") plt.ylabel("情感倾向值") plt.title("情感走向曲线") plt.savefig("output/sentiment_curve.png", dpi=200)

需要明确的是,SnowNLP 的情感模型基于电商评论语料训练,对文学文本的判断存在偏差,比如文学性反讽、暗黑叙事可能被误判为中性或正向。所以这部分更适合展示"相对差异",不要过分依赖绝对值。报告中建议写一句"情感分析作为辅助维度,结果供参考,与主题模型结论互证",老师挑不出毛病。

4.3 人物共现网络:把文本挖掘升级成关系挖掘

东野圭吾的作品非常适合做人物共现分析,因为侦探小说人物关系是核心。共现网络的思路是:如果两个人物出现在同一段落或同一章节里,就认为一次共现,统计共现次数,再用 NetworkX 画关系图。这能让大作业从"文本统计"上升到"结构挖掘",是拿高分的一个重要加分项。

from collections import Counter, defaultdict import networkx as nx # char_dict: 自定义词典里的人物名列表 co_occur = defaultdict(int) # 按段落处理,每段内统计人物共现 for paragraph in paragraphs: chars_in_para = [c for c in char_dict if c in paragraph] chars_in_para = list(set(chars_in_para)) for i in range(len(chars_in_para)): for j in range(i + 1, len(chars_in_para)): pair = tuple(sorted([chars_in_para[i], chars_in_para[j]])) co_occur[pair] += 1 G = nx.Graph() for (c1, c2), weight in co_occur.items(): if weight >= 5: # 只保留共现次数 >= 5 的边,去噪 G.add_edge(c1, c2, weight=weight)

阈值设为 5 可以过滤一大批随机共现的噪声边,实际项目中 3 到 8 之间都可以试。画图时用节点大小代表人物的总出现次数,用连边粗细代表共现强度,这样一眼能看出谁是核心人物。答辩时这个图放在 TF-IDF 热词表旁边,形成"词与关系"两个层面的对照,分析深度就出来了。

5. 避坑清单:文本挖掘大作业的编码、词典和可视化重灾区

文本挖掘大作业的翻车点高度集中,以下五条是我见过最多的踩坑现场,每条都按"现象→原因→解决"写清楚。

5.1 读文件时 UnicodeDecodeError:编码不统一

现象:开着 "utf-8" 读文件,报错UnicodeDecodeError: 'utf-8' codec can't decode byte。

原因:语料文件是 GBK 或 GB18030 编码,常见于从老网站或论坛下载的 TXT。Windows 简体中文环境下,很多文本编辑器默认用 ANSI(即 GBK)保存,这段经历可称为玄学现场。

解决:读取时先尝试 GB18030 解码。

def read_text_safe(filepath): for enc in ["utf-8", "gb18030", "gbk"]: try: with open(filepath, "r", encoding=enc) as f: return f.read() except UnicodeDecodeError: continue raise ValueError(f"无法解码文件: {filepath}")

统一规范是:清洗后全部转存为 UTF-8,后续代码固定用 UTF-8 读写,不再逐次猜编码。

5.2 高频词全是人称代词:停用词表太薄

现象:词频统计 top 20 里全是"他""她""的""了""在",分析无从下手。

原因:通用停用词表只覆盖了常见的虚词,小说文本里大量叙事功能词没有被过滤。尤其是"他/她"这类代词,在小说里频率极高,但它们不携带风格信息。

解决:在通用停用词表基础上,针对小说文本增量补充一个停用词集合,包含"说道""知道""看见""时候""已经""过来""什么""没有""这样""那样""一下"等词。更彻底的做法是用词性过滤,只保留名词、动词和形容词,用 jieba.posseg 做词性标注后按词性筛选。

import jieba.posseg as pseg allowed_flags = {"n", "nr", "ns", "v", "a", "vn"} words = [w.word for w in pseg.cut(text) if w.flag in allowed_flags and len(w.word) > 1]

注意区分两种方案的适用场景:词性过滤会丢掉一些值得分析的双字词,比如"当然""或许"这类语气副词,它们在风格分析里有价值。如果重点看词义关键词,用词性过滤;如果做风格对比,保留副词更好。

5.3 词云中文全是方框:缺少字体路径

现象:词云生成成功,图里所有中文都是空心方块。

原因:wordcloud 默认字体不支持中文,没有指定 font_path 时系统找不到中文字体。

解决:在 Windows 上指定C:/Windows/Fonts/simhei.ttf(黑体)或simsun.ttc(宋体);macOS 上指定/System/Library/Fonts/PingFang.ttc,Linux 系统先安装文泉驿或思源黑体。一个稳妥办法是把字体文件复制到项目目录下的assets/文件夹,用相对路径引用,换机器跑不依赖系统字体。

wc = WordCloud(font_path="assets/fonts/simhei.ttf", ...)

5.4 LDA 主题词无法命名:没先做充分清洗

现象:LDA 输出的每个主题 top 词都是"这个""怎么""一个""里",没有任何可解释性。

原因:去停用词不彻底,虚词和泛化动词占了词频优势,LDA 抓不到语义特征。这与 5.2 的问题同源,但表现更严重。

解决:重点检查自定义停用词表是否覆盖了叙事通用词,以及是否做了词性筛选。另外一个常见原因是语料长度太短,很多大作业只收集了每部作品的开头章节,样本量不足导致 LDA 的主题推断不稳定。建议每个作品至少保留全文的三分之一以上篇幅。

5.5 Matplotlib 中文乱码或警告:全局字体未设置

现象:绘图标题和图例中文显示为黑色方块,控制台输出Glyph missing from current font警告。

原因:matplotlib 默认字体不含中文字形,需要设置字体家族。

解决:在绘图脚本开头统一设置:

plt.rcParams["font.sans-serif"] = ["SimHei"] # 或用 ["PingFang SC"] plt.rcParams["axes.unicode_minus"] = False # 解决负号显示为方块

这个配置要放在所有绘图代码之前,否则部分图生效部分不生效,排查起来很费时间。

6. 验证方法与进阶:拿"可复现"和"可解释"保底冲高

到大作业的最后阶段,代码能不能跑通已经不是问题了,问题变成了"怎么证明你的分析是对的"。文本挖掘的结果没有绝对正确,但有相对可靠的验证思路。这套验证方法的价值在于,它在答辩时能帮你挡住一半以上的追问。

6.1 三个验证层次

第一层是统计合理性。检查分词结果里词的个数、每部作品的平均长度、词频和 TF-IDF 的排序是否符合直觉。比如《嫌疑人 X 的献身》里"数学"应该排很前,《白夜行》里"剪刀""雪穗"应该是标识词。如果排序结果和作品标签高度吻合,说明语料清洗和分词是有效的,这套流程可以支撑你的结论。

第二层是跨方法一致性。LDA 主题词、TF-IDF 关键词和人物共现网络的结论应当互为补充而不是互相矛盾。如果 LDA 跑出"悬疑推理"主题,TF-IDF 里应该有"案件""线索""嫌疑"等词呼应。这种交叉验证是数据挖掘报告最重要的可靠性支撑。

第三层是维度稳定性。把语料分成前一半和后一半,分别做 TF-IDF,对比两部半语料的关键词重合率。重合率超过 60% 说明结果稳定,如果差异巨大,就要考虑是不是语料本身混合了多种风格或清洗不够均匀。

6.2 交付物组织建议

源码和文档说明是这个题目标注里的两个交付物。源码部分建议按以下结构组织:

project/ ├── data/raw/ # 原始语料 ├── data/clean/ # 清洗后语料 ├── code/ │ ├── 01_clean.py │ ├── 02_segment.py │ ├── 03_tfidf.py │ ├── 04_lda.py │ ├── 05_sentiment.py │ └── 06_co_occur.py ├── output/ # 图表,统一命名 ├── dict/ │ ├── novel_dict.txt │ └── stopwords.txt └── README.md

脚本按 01 到 06 编号,每步只输出中间结果,后一步读前一步的产物,这样每一步可以单独重跑,答辩演示时可以按顺序跑给老师看。输出图表统一用英文命名加日期后缀:tfidf_top_words_20250601.png。README.md 里写明运行环境(Python 3.10+、核心依赖版本)、语料来源和数据清洗规则,这是文档说明部分的基本盘。

6.3 最后一个加分技巧:建立"作品-主题-情感"三维表

把每部作品的主题分布、TF-IDF 关键词、平均情感值三组数据放到同一个表里,一页纸展示全局,这是报告里最能体现数据挖掘思维的一页。表格形式大致如下:

作品主导主题核心词 Top 5平均情感值
白夜行案侦探案/生存挣扎雪穗、亮司、桐原、剪刀、嫌疑0.43
解忧杂货店亲情/人生选择杂货店、咨询、浪矢、梦想、信0.65
恶意嫉妒/谎言野野口、日高、凶手、手记、作案0.38

这张表本身就是答辩的开场结构:从主题差异、到关键词差异、再到情感差异,三段式把东野圭吾不同作品间的风格跨度讲完整。把"为什么用这些方法"放在实验设计部分,"结果之间的内在关系"放在分析部分,报告的完整度就远超普通作业了。

文本挖掘大作业做完一遍,最有价值的收获不是代码多漂亮,而是建立了一套"从原始文本到可解释结论"的处理链路。我自己的习惯是每一次跑完都把终端截图和中间产物留存下来,答辩前回看一遍,很多细节当场就能想起来。最后提醒一句:所有图表写上生成时间、所用版本、随机种子,这在答辩时能省掉大量"结果能不能复现"的追问。希望这些经验能帮你的东野圭吾文本挖掘项目少走弯路,稳稳拿到应得的评价。

本文还有配套的精品资源,点击获取

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

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

立即咨询