简介:这份Python中文文本分析期末大作业压缩包面向高校学生及文本挖掘初学者,以《三国演义》为分析对象,完整演示了中文分词、词频统计、词云绘制、人物出场排序、社交网络关系建模与章回字数统计等典型任务。包内共13个文件,涵盖Python源码、可执行程序、可视化图表、报告文档及原始文本,其中main.py与main.exe方便直接运行,html与png展示词云、关系网络等结果,docx和pdf则为课程报告与说明材料,整体约406.57MB,结构清晰、开箱可用。资源已有6645人学习,内容兼顾编程实践与数据分析思路,适合期末参考、课设复现或个人自学。通过研读源码和报告,可掌握jieba分词、wordcloud词云、networkx关系图等关键技术,并理解从文本清洗到可视化输出的完整流程,是一份能直接启发中文文本分析项目设计的综合案例。
1. Python中文文本分析期末大作业:先别急着找代码,说清要交什么再说
“Python中文文本分析”这类期末大作业,几乎每个和数据处理沾边的专业都会碰到一次。表面看是让你写一个程序,把一批中文文本做分词、词频统计、情感分析和可视化,最后提交代码、数据、报告和一份能运行的环境清单。实际上一眼看出差距的地方,往往是“文件打不开、中文乱码、词云一片方块”这些最草根的问题。这篇笔记按一个能答辩的方案去拆,讲清楚选型理由和具体参数,再把踩坑记录一并列出来。无论是零基础第一次做文本分析,还是想给已有课程设计补分,这套流程都能直接对齐。
2. 中文文本分析为什么不能照搬英文方案:分词、编码与停用词三座山
文本分析如果只处理英文,一个字符串的 split() 就能基本解决问题。中文不一样,一句话几十个字连在一起,没有空格作为天然边界;同一个词在不同语境里还可能被切成不同长度。所以中文文本分析的第一个技术决策不是模型,而是分词器怎么选。分词、编码、停用词,我习惯把这称为三座山,任何一座没处理好,后面的词频、词云和情感分析全是在垃圾数据上做验收。
2.1 没有天然空格:jieba 分词模式与三个关键参数
最常用的开源分词工具是 jieba。它不是靠固定词典硬切,而是在词典匹配的基础上,用 HMM 模型识别词典之外的新词。日常文本分析的精度够用,安装简单,API 稳定,所以课程作业和快速原型基本都是它。先用一句话区分三种模式,再看表格。
| 模式 | 调用方式 | 返回特点 | 适合场景 |
|---|---|---|---|
| 精确模式 | jieba.lcut(text) | 把句子切成一串最小且不重叠的词 | 词频统计、关键词提取,作业默认选它 |
| 全模式 | jieba.lcut(text, cut_all=True) | 把所有可能词都扫一遍,产生重叠 | 调试词典覆盖,不适合直接统计 |
| 搜索引擎模式 | jieba.lcut_for_search(text) | 在全模式基础上补齐长词 | 搜索场景,作业里偶尔用于对比展示 |
import jieba text = "Python中文文本分析期末大作业" words = jieba.lcut(text) print(words) all_mode = jieba.lcut(text, cut_all=True) print(all_mode) search_mode = jieba.lcut_for_search(text) print(search_mode)这三个打印结果放在一起看,能直观理解精确模式为什么是默认选择。lcut 返回的是 list,比原来的 cut 返回生成器更方便,后续直接接 Counter 做统计也不会搞错对象。cut_all 参数默认是 False,也就是精确模式;HMM 参数一般不需要手动改,保持默认即可。
实际作业里,记住精确模式就够。全模式跑出来的词频统计往往把同一个内容重复算好几遍,反而让结果失真,答辩时老师一问就露馅。
2.2 字符编码:黑匣子一样的 GBK 和 UTF-8 混战
中文文本分析的第二个坑发生在数据读入之前。编码不是 Python 的问题,但足够让 Python 程序“莫名”崩溃。常见场景是 Windows 下用记事本保存文件,可能被存成 ANSI/GBK;从网页 API 或 Linux 服务器拿到的文件通常是 UTF-8。两者混用就会遇到“明明数据没问题,一读就读不出来”的情况。
import chardet # 读前 10000 字节去猜编码,猜错也比瞎赌好 with open("data.txt", "rb") as f: raw = f.read(10000) encoding = chardet.detect(raw)["encoding"] print(encoding)chardet 返回的结果不一定精确,尤其对短文本,所以它更适合用来做初步判断。更稳的做法是拿到文件后先统一转成 UTF-8 再进 pandas,我实际用得最多的还是指定 encoding 参数去读,而不是依赖系统默认。
提示:Windows 记事本另存为时,“UTF-8”和“UTF-8 BOM”是两回事。pandas 用 encoding="utf-8" 读带 BOM 的文件,第一列经常出现一个看不见的 BOM 字符,处理时要用 encoding="utf-8-sig" 读取。
2.3 停用词表:词频排行第一的永远是“的”,这不是分析结果
中文里有一批高频但没有信息量的词,比如“的、了、是、在、和、与”。如果不剔除,它们会占满高频词前二十,真正的主题词被挤到后面,整个词频统计失去解释力。公开的中文停用词表有不少,常见的是哈工大停用词表和百度停用词表,大小从几百词到两千词不等。
但领域词必须自己补。新闻语料里要加“记者、编辑、来源”,评论语料里要加“哈哈、真的、感觉、觉得”。这份 stopwords.txt 建议自己动手维护,同一份文件对多批语料反复使用,这也是期末作业里特别能体现细节的地方。
后面所有词频统计都默认先过停用词。处理顺序一般是:正则清洗、分词、过滤停用词、统计,顺序不要颠倒。先统计再过滤会导致计数多算一次,而且某些词会被标点和前后缀污染,清洗效果大打折扣。
3. 搭一套能交差的流程:从语料到词云的最小可执行方案
原理部分讲完,下面进入能跑的阶段。我倾向于按“环境、数据、分词、可视化”四步走,每一步的产物都是下一步的输入。这套流程同样适用于自建项目,区别只是数据源不同。对期末大作业来说,能把这一步跑通就已经拿到了大部分基础分。
3.1 环境准备:用 venv 和这 6 个库避免版本翻车
第一个建议是不要把包直接装进系统 Python。零基础入门教程通常推荐 Anaconda,但做期末大作业我反而建议用 venv 或 conda 建独立环境。原因很实际:交作业时通常要写依赖清单,独立环境能精确导出 requirements.txt,不会把系统里无关的包带进去。另一个常见翻车点是版本。Python 3.13 发布后,部分依赖编译的库还没有对应 wheel,建议锁定 3.9 到 3.12,其中 3.10、3.11 最稳。
python -m venv nlp_homework # Windows 激活 nlp_homework\Scripts\activate # macOS / Linux 激活 source nlp_homework/bin/activate pip install jieba pandas wordcloud matplotlib snownlp chardetpython 安装教程里常说要加镜像源,如果 pip 下载慢,可以在命令末尾加-i https://pypi.tuna.tsinghua.edu.cn/simple,这不影响最终代码运行。激活虚拟环境后,命令行里的 python 指向的是项目内解释器,和全局环境隔离。
如果用 VSCode 写代码,装完依赖后还要在解释器选择器里指到nlp_homework\Scripts\python.exe,否则会出现“命令行激活了、编辑器里还是全局解释器”的诡异情况。这个是 python 环境配置里最常见的跳坑点,我见过不止一个人在这上面浪费一晚上。
3.2 数据准备:CSV 还是 TXT,先解决一列文本的清洗问题
语料怎么来,取决于作业给的是什么。常见的是新闻标题、商品评论、影评、问卷开放性题目。有一个干净的短文本列表,后面流程会顺很多。如果作业不限定数据源,我一般建议把数据存成 CSV,至少两列:一列 id,一列 text,一列可选的 label。这个结构够用,也能支持后面做类别对比分析。
import pandas as pd # 读 CSV;如果报 UnicodeDecodeError,先试着改成 encoding="gbk" df = pd.read_csv("data.csv", encoding="utf-8") df = df.dropna(subset=["text"]) print(df.head()) # 没有现成文件时,用这个最小语料也能跑通全流程 sample_texts = [ "期末考试临近,大家都在准备Python数据分析与可视化作业。", "中文文本分析需要用分词工具,最常用的是jieba。", "停用词表对词频统计影响很大,不过滤全是虚词。", "词云图需要指定中文字体,否则看到的都是方框。", "情感分析可以告诉老师这条评论偏正面还是负面。" ] texts = df["text"].tolist() if "text" in df else sample_textsdropna(subset=["text"]) 会把正文为空的行直接丢掉,避免后面分词时对 NaN 操作报错。"text" in df是判断列名是否存在,如果字段名不对会自动走示例语料,不至于一运行就中断。真实作业里建议把这段改成明确的列名判断,省得示例数据混进正式结果。
3.3 分词与停用词过滤:把句子切碎并且去掉噪音
分词函数是整个流程里最需要稳定的部分。思路是先只保留中文、英文、数字,再用精确模式切词,最后过滤停用词和单字。正则里的[^\w\u4e00-\u9fa5]在 Python3 下已经是“保留单词字符和中文”的写法,因为 \w 本身会匹配中文,多写 \u4e00-\u9fa5 只是把意图写得更明确。
import jieba import re # 自定义词典,一行一个词,格式:词 词频 词性 # jieba.load_userdict("userdict.txt") stopwords = set() with open("stopwords.txt", "r", encoding="utf-8") as f: for line in f: word = line.strip() if word: stopwords.add(word) def clean_and_cut(text): text = re.sub(r"[^\w\u4e00-\u9fa5]", " ", str(text)) words = jieba.lcut(text) result = [] for w in words: if w.strip() and w not in stopwords and len(w.strip()) > 1: result.append(w) return result all_words = [] for t in texts[:2000]: all_words.extend(clean_and_cut(t))开发阶段先用texts[:2000]切片跑通,逻辑没问题再放开全量,这是一个避免反复浪费时间的原则。停用词表用 set 而不是 list,成员判断 O(1),几万条文本跑下来差距明显。len(w.strip()) > 1会把单字直接过滤,因为词频统计里单字通常只是噪音,除非你是做汉字级别的分析。
停用词表里没有的词,比如“作业”这种领域词,如果不想进停用词,也不用管。真正要处理的是希望被识别成整体但被拆开的词,那个用 load_userdict 或 add_word 解决,后面避坑章节会专门讲。
3.4 词频统计与可视化:词云加柱状图,成果图就有了
词频统计用 Counter 一行就能排好。柱状图展示 Top30,词云展示 Top100,两张图放进报告,代码量并不大,但视觉效果立刻完整。注意 matplotlib 默认字体不含中文,不设置字体的话,坐标轴标签全是方块。
from collections import Counter counter = Counter(all_words) top30 = counter.most_common(30) print(top30[:10]) import matplotlib.pyplot as plt plt.rcParams["font.sans-serif"] = ["SimHei"] plt.rcParams["axes.unicode_minus"] = False words = [w for w, _ in top30] freqs = [f for _, f in top30] fig, ax = plt.subplots(figsize=(12, 6)) ax.bar(range(len(words)), freqs, color="#4C72B0") ax.set_xticks(range(len(words))) ax.set_xticklabels(words, rotation=45, ha="right") ax.set_ylabel("词频") plt.tight_layout() plt.savefig("top30.png", dpi=150) plt.show()plt.rcParams 两行是全局设置,把默认字体切到黑体,同时把负号显示修好,否则图表里出现减号会变方块。rotation=45 和 ha="right" 是让中文标签斜着排,避免横轴重叠。tight_layout 自动收缩留白,savefig 的 dpi=150 足够放进报告打印。
词云部分同样要注意字体,WordCloud 默认的字体对中文不友好,必须手动指定 font_path。
from wordcloud import WordCloud wc = WordCloud( font_path="C:/Windows/Fonts/simsun.ttc", width=800, height=600, max_words=100, background_color="white", colormap="viridis", max_font_size=120, random_state=42 ) wc.generate_from_frequencies(dict(counter.most_common(100))) plt.figure(figsize=(10, 6)) plt.imshow(wc, interpolation="bilinear") plt.axis("off") plt.savefig("wordcloud.png", dpi=150, bbox_inches="tight") plt.show()font_path 指向系统里的一个中文字体文件,Windows 常见宋体是 simsun.ttc,黑体是 simhei.ttf;macOS 放在 /System/Library/Fonts/PingFang.ttc。max_words 控制词云里最多显示多少词,词太多会糊成一片。colormap 控制配色,viridis 是默认里不容易翻车的方案。random_state=42 固定随机种子,保证每次运行生成的词云形状一致,写报告和答辩演示时不跳变。
4. 期末大作业常见问题排查:六个跑不通的坑和对应解法
能跑的流程已经有了,下面这些坑是我见过最多的,几乎每个小组作业都会踩中至少一条。每条按现象、原因、解决列出来,方便对照排查。
4.1 读 CSV 报 UnicodeDecodeError 乱码
现象:pd.read_csv("data.csv")一运行就报错,提示'gbk' codec can't decode byte 0x...。原因:文件是 UTF-8 编码,但 Windows 下 pandas 默认用 GBK 去解码,或者反过来文件是 GBK 而代码写了 utf-8。解决:先确定文件编码再指定。
try: df = pd.read_csv("data.csv", encoding="utf-8") except UnicodeDecodeError: df = pd.read_csv("data.csv", encoding="gbk")这个 try except 拦截是实用做法,但不完美,如果两种解码都失败,说明文件本身混入了特殊字节。这个时候用记事本打开文件,另存为 UTF-8 再跑一次,比在代码里绕来绕去更省时间。
4.2 词云全是方块或者直接报字体异常
现象:词云生成了,但每个字都是一个空心小方块,或者运行时报cannot load font。原因:WordCloud 默认的字体不包含中文字形,系统里找不到匹配的字体文件就变成占位符。解决:在 WordCloud 里显式指定中文字体路径。Windows 用C:/Windows/Fonts/simhei.ttf,macOS 用/System/Library/Fonts/PingFang.ttc,Linux 可以检查 /usr/share/fonts 下有没有中文字体。代码里先判断文件存在再传进去,比直接写死路径更稳。
4.3 分词结果和预期差一大截,比如“文本分析”被切开
现象:高频词结果里有“文本”也有“分析”,但就是没有“文本分析”这个整体词。原因:jieba 的默认词典里对这个词的词频权重不够,或者中英混排让切分走了另一条路。解决:自定义干预。
import jieba jieba.add_word("文本分析", freq=50, tag="n") words = jieba.lcut("中文文本分析方法") print(words)add_word 适合临时加一个词。如果词表很长,用 load_userdict 加载文本文件更合适,文件每行一个词,格式是“词 词频 词性”。注意自定义词必须在 lcut 之前加入,切分是即时计算的结果。
4.4 全量跑几万条文本,卡到怀疑人生
现象:代码在分词阶段跑了很久没反应,内存占用持续上涨。原因:中文分词本身是逐句处理,不像矩阵运算可以批量加速,正则加集合过滤在高数据量下也会堆积耗时。解决:开发阶段先用texts[:2000]跑通,确认效果后再全量运行。全量跑时,可以在循环里每 5000 条打印一次进度,不然只能干瞪眼。
for i, t in enumerate(texts): all_words.extend(clean_and_cut(t)) if (i + 1) % 5000 == 0: print(f"处理进度: {i + 1}/{len(texts)}")词云和柱状图不必每次都从全量数据重跑,先用抽样结果把图调好看,最后跑一次全量做正式数据,这是很实用的节奏。
4.5 报告只有一张词云,老师说不够深入
现象:代码全对,图也美,但报告交上去被打回,评语写着“分析性不足”。原因:词云只是词频统计的可视化,缺少问题定义和分析结论。老师要看到的是从问题出发、用数据回答问题的过程,不是程序运行截图。解决:至少补三类内容,高频词排序柱状图、按类别或时间对比词频、情感分布结果。情感和关键词扩展在下一章展开。
5. 从合格到优秀的三个加分方向:情感分析、关键词提取与报告结构
词频和词云是基础分,要把档次提上去,最快的是加情感分布和关键词权重分析。这两项代码量不大,但能明显让报告有“分析”的骨架。更重要的是,答辩时老师问“你的分析说明什么”,你手里有具体数字可以回答。
5.1 用 SnowNLP 做情感分布
如果语料带主观性,比如微博、评论、影评,情感分析是最好实现的加分项。SnowNLP 是基于朴素贝叶斯的中文情感分析库,调用很简单。但要知道训练语料偏电商购物评论,对新闻类文本的结果会比较偏中性,使用时要在报告里说明。
from snownlp import SnowNLP texts_sample = texts[:500] scores = [] for t in texts_sample: s = SnowNLP(t) scores.append(s.sentiments) pos = sum(1 for s in scores if s > 0.6) neg = sum(1 for s in scores if s < 0.4) mid = len(scores) - pos - neg print(f"正面 {pos},中性 {mid},负面 {neg}") labels = ["positive", "neutral", "negative"] values = [pos, mid, neg] plt.pie(values, labels=labels, autopct="%1.1f%%") plt.savefig("sentiment.png", dpi=150) plt.show()sentiments 取值在 0 到 1 之间,越接近 1 越正面。阈值 0.6 和 0.4 是根据语料情况调整的经验值,在报告中写明“本文以 0.6 和 0.4 为阈值划分三分类,并经过抽样校验”,这句话本身就是加分项。如果语料是新闻标题,正负占比通常会集中在中性附近,这很正常,说明数据特性要单独讨论,不要硬套结论。
5.2 用 TF-IDF 关键词提取,比词频统计更有说服力
词频统计体现的是“出现得多”,TF-IDF 体现的是“在这份语料里相对重要”。一个词如果每篇文章都出现,它的 IDF 会很低;只有集中在部分文章中出现的词,权重才会凸显。用 jieba 的 analyse 模块可以直接算。
import jieba.analyse full_text = " ".join(texts[:500]) keywords_tfidf = jieba.analyse.extract_tags( full_text, topK=20, withWeight=True ) for word, weight in keywords_tfidf: print(word, weight) keywords_tr = jieba.analyse.textrank( full_text, topK=20, withWeight=True, allowPOS=("ns", "n", "vn", "v") ) for word, weight in keywords_tr: print(word, weight)extract_tags 默认算法就是 TF-IDF。topK 控制返回数量,withWeight=True 会同时输出权重值,这是画对比表的基础。textrank 是另一种算法,基于词图网络计算重要性,allowPOS 限制了参与计算的词性,通常只保留名词和动词,效果会比全词性更干净。
作业里把词频榜、TF-IDF 榜、TextRank 榜放到一个表格里做对比,并说明为什么“出现次数多”和“重要”不是一回事,这就是很扎实的分析内容。
5.3 报告结构参考:背景、数据、方法、结果、不足五段
期末大作业报告不需要长篇大论,但要有完整逻辑链。我见过很多报告把大量篇幅花在介绍 Python 是什么上,这没有意义。真正该写的是下面这五块。
| 章节 | 内容要点 | 常见错误 |
|---|---|---|
| 背景 | 想解决什么问题,为什么用文本分析 | 从 Python 发展史写起 |
| 数据 | 来源、条数、预处理方案 | 不写编码处理和清洗 |
| 方法 | 分词器选型、停用词表、统计方案 | 只写库名不给参数 |
| 结果 | 词云、柱状图、情感分布、对比表 | 只有一张词云图 |
| 不足 | 数据量、模型偏差、人工标注缺失 | 不写不足,怕暴露问题 |
答辩问题通常不在代码本身,而在“你这个词频说明了什么”这类问题上。情感分布给了你一个相对客观的结论,TF-IDF 给了你一组可以解释的权重,两者都能支撑起一段像样的讨论。这也是我强烈建议把扩展分析写进报告的原因。
6. 给词云换一张形状:掩膜图片的完整步骤与参数
6.1 用 PIL 读取一张黑白剪影作为词云轮廓
词云默认是一个矩形,但如果想让视觉呈现更有记忆点,比如把词云做成爱心、头像、项目 Logo 的形状,只需要准备一张黑白剪影图,在生成时把图片传给 mask 参数。这个技巧对最终展示效果提升非常明显,操作也不复杂。
from PIL import Image import numpy as np mask = np.array(Image.open("mask.png")) # 处理掉可能存在的灰度,保留纯黑和纯白两个值 mask = np.where(mask > 128, 255, 0).astype(np.uint8) wc = WordCloud( font_path="C:/Windows/Fonts/simhei.ttf", mask=mask, width=800, height=600, background_color="white", contour_width=2, contour_color="steelblue", max_words=80, random_state=42 ) wc.generate_from_frequencies(dict(counter.most_common(80))) plt.figure(figsize=(10, 8)) plt.imshow(wc) plt.axis("off") plt.savefig("wordcloud_mask.png", dpi=150) plt.show()mask 里白色部分代表留白,黑色部分是词云填充的区域。如果发现文字跑到背景里,说明图片的颜色反了,用mask = 255 - mask反转一次就能解决。contour_width 和 contour_color 会给形状轮廓加一条描边,视觉上更干净。生成的剪影图用常见图形软件导出 PNG 即可,注意尺寸不要太小,建议不低于 800x600,不然词云里的文字会糊。
最后说一个我自己踩过的细节。期末展示那次,我的词云背景是灰扑扑的色块,老师随口问了一句“是不是字体加载失败了”。当时我检查了很久才发现是掩膜图片本身带了反锯齿,边缘有一圈半透明灰度,程序处理时没有把它二值化干净。后来在代码里加了np.where(mask > 128, 255, 0)这一步,问题一次性解决。这个习惯沿用到现在:凡是涉及图像生成的环节,先确认数据只有黑和白两个值,再去调字体和配色。希望帮到你。
本文还有配套的精品资源,点击获取