简介这份资源面向Python初学者、NLP入门者及需要完成课程设计的学生系统讲解中文文本关键词抽取的三种经典方法TF-IDF、TextRank与Word2Vec词向量聚类。内容从原理、流程到代码实现逐层展开并附课程论文、项目源码及运行截图帮助读者理解各方法的适用场景与差异。压缩包共31个文件约1.78MB包含4个py脚本分别对应三种抽取方法及词向量训练14个csv数据文件与结果文件、8张png截图、2个txt词表与停用词文件、1个docx论文、1个md说明及license结构清晰便于对照学习。目前已有2116人学习下载。读者可获得完整可运行的代码与样例数据直接复现关键词抽取流程同时论文中讨论了语料扩充、标题权重、聚类数设置、高频词过滤等改进方向为后续优化提供思路适合作为课程设计或NLP实践的参考模板。1. 中文关键词抽取三种路线从一份课程设计源码包说起做中文文本处理的人迟早会撞上同一个需求给一篇没有标签的文章自动挑出它到底在讲什么。搜索、推荐、舆情监控、论文查重底层都绕不开关键词抽取。我手上这份基于Python实现中文文本关键词抽取的三种方法.zip就是把这个需求拆成三条可跑的路线——TF-IDF、TextRank、Word2Vec 词向量聚类配了课程论文、源码、样例数据和停用词表。它不是工业级框架而是一套能让你把三种方法的输入输出、参数影响、失败边界都亲手摸一遍的实验台。适合正在做课程设计的学生、需要快速搭关键词抽取原型的工程师以及想搞清楚「为什么同一篇文章三种方法给出的词不一样」的从业者。下面我按自己拆包复现的顺序把每条路线的原理、代码、参数和坑讲清楚。2. TF-IDF 与 TextRank两条无监督路线的代码落地2.1 TF-IDF 的统计逻辑与keyextract_tfidf.py拆解TF-IDF 的核心假设很朴素一个词在当前文档里出现得越多、在整个语料里出现得越少它就越能代表这篇文档。词频 TF 衡量局部重要性逆文档频率 IDF 惩罚那些到处都有的通用词。这份源码里keyextract_tfidf.py走的是「分词 → 构建词频矩阵 → 算 TF-IDF 权重 → 排序取 TopN」的流程依赖 jieba 做中文分词用 scikit-learn 的TfidfVectorizer或手写统计都能实现。我一般会先确认分词和停用词这两步因为它们直接决定后面权重的质量。源码里stopWord.txt是独立文件data目录放原始语料result目录存输出。下面是我按源码逻辑重写的一段可独立运行的最小实现方便你对照理解每一步在干什么import jieba from sklearn.feature_extraction.text import TfidfVectorizer # 1. 读取停用词中文停用词表通常包含的、了、在、是这类高频虚词 def load_stopwords(path): with open(path, r, encodingutf-8) as f: return set(line.strip() for line in f if line.strip()) # 2. 自定义分词器jieba 切词后过滤停用词和单字 def tokenizer(text, stopwords): words jieba.lcut(text) return [w for w in words if w not in stopwords and len(w) 1] stopwords load_stopwords(stopWord.txt) docs [ 中文文本关键词抽取是自然语言处理的基础任务, TF-IDF 通过词频和逆文档频率衡量词语重要性, TextRank 借助图模型对词语进行排序 ] # 3. 构建 TF-IDF 矩阵tokenizer 接收单个文档返回词列表 vectorizer TfidfVectorizer(tokenizerlambda t: tokenizer(t, stopwords)) tfidf_matrix vectorizer.fit_transform(docs) # 4. 取第一篇文档的 TopN 关键词 feature_names vectorizer.get_feature_names_out() scores tfidf_matrix[0].toarray()[0] topn sorted(zip(feature_names, scores), keylambda x: x[1], reverseTrue)[:5] print(topn)逻辑上分四段停用词加载、分词过滤、矩阵构建、排序输出。参数上最需要动的是len(w) 1这个单字过滤阈值——中文里「人」「事」这类单字有时是关键词但多数场景过滤掉能减少噪声TfidfVectorizer的tokenizer参数必须接收「单个文档字符串」返回词列表如果你直接把整个语料传进去会报错。源码里keys_TFIDF.csv就是这套流程的输出字段是词语加权重方便你直接比对。注意TF-IDF 对短文本很敏感。文档只有一两句话时IDF 几乎失效因为语料太小、每个词都只出现在少数文档里权重区分度会崩掉。这也是为什么源码的sample_data.csv要放多篇文档而不是单篇。2.2 TextRank 的图排序与keyextract_textrank.py实现TextRank 借的是 PageRank 的思路把词当节点共现关系当边谁被重要节点连得多、连得紧谁就重要。它不依赖外部语料单篇文档就能跑这是它相对 TF-IDF 的最大优势。源码里keyextract_textrank.py的流程是「分词 → 滑动窗口建共现图 → 迭代计算节点权重 → 排序取 TopN」。共现窗口大小是这里最关键的参数。窗口设 2只有相邻词算共现设 5一句话里隔几个词也算关联。窗口越大图越稠密通用词越容易互相抬权重结果反而变糊。我一般从 2 到 3 起步看结果再调。下面是对应实现import jieba import networkx as nx def build_textrank(text, stopwords, window3, topn5): words [w for w in jieba.lcut(text) if w not in stopwords and len(w) 1] graph nx.Graph() # 滑动窗口建边窗口内任意两词之间加一条边 for i, word in enumerate(words): for j in range(i 1, min(i window, len(words))): if graph.has_edge(words[i], words[j]): graph[words[i]][words[j]][weight] 1 else: graph.add_edge(words[i], words[j], weight1) # pagerank 迭代weight 作为边权影响传递 scores nx.pagerank(graph, weightweight) return sorted(scores.items(), keylambda x: x[1], reverseTrue)[:topn] stopwords set(open(stopWord.txt, encodingutf-8).read().split()) text 中文文本关键词抽取是自然语言处理的基础任务TextRank 借助图模型对词语进行排序 print(build_textrank(text, stopwords))window控制共现范围nx.pagerank的weight参数让重复共现的词对权重更高。源码输出keys_TextRank.csv你可以拿同一篇文档分别跑 TF-IDF 和 TextRank对比两个 CSV 的重合度——重合高的词通常是真正稳定的关键词差异大的词往往暴露了方法本身的偏好。提示TextRank 在长文档上表现更稳短句上容易退化成「谁出现次数多谁赢」因为图太小、迭代收敛快排序信息不足。3. Word2Vec 词向量聚类从语义相似度到关键词簇3.1keyextract_word2vec_1.py与_2.py的分工这份资源里 Word2Vec 路线有两个脚本keyextract_word2vec_1.py和keyextract_word2vec_2.py从命名和vecs目录看一个负责训练或加载词向量另一个负责聚类和关键词输出。Word2Vec 的思路和前两种完全不同它不统计词频而是把词映射成稠密向量语义相近的词在向量空间里距离近然后对向量做聚类从每个簇里挑代表词作为关键词。这条路线的价值在于能抓到「字面不同但语义相关」的词。比如「汽车」和「轿车」在 TF-IDF 里是两个独立词在 Word2Vec 空间里距离很近聚类后可能归到同一簇。代价是你需要一个像样的训练语料否则词向量质量差聚类结果就是玄学。3.2 词向量训练与 KMeans 聚类的参数设置源码的vecs目录存的是训练好的向量keyextract_word2vec_2.py里应该包含 KMeans 聚类。下面是我按这条路线复现的核心代码用 gensim 训练词向量再用 KMeans 对文档中的词向量聚类import jieba import numpy as np from gensim.models import Word2Vec from sklearn.cluster import KMeans # 1. 准备语料每篇文档分词后作为一行 corpus [ 中文 文本 关键词 抽取 自然语言 处理.split(), 词向量 语义 相似度 聚类 关键词.split(), TF-IDF TextRank 图模型 统计 方法.split() ] # 2. 训练 Word2Vecvector_size 是向量维度window 是上下文窗口 model Word2Vec(corpus, vector_size100, window5, min_count1, workers4) # 3. 取目标文档中的词映射成向量 target_words [w for w in jieba.lcut(中文文本关键词抽取与词向量聚类) if len(w) 1] vectors [model.wv[w] for w in target_words if w in model.wv] # 4. KMeans 聚类n_clusters 决定聚成几类 if len(vectors) 2: kmeans KMeans(n_clusters2, n_init10, random_state42) labels kmeans.fit_predict(np.array(vectors)) for word, label in zip(target_words, labels): print(label, word)vector_size一般设 100 到 300语料小就设小一点否则每个词都过拟合window是上下文窗口中文一般 5 左右min_count1是因为样例语料太小实际项目里通常设 2 到 5 过滤低频词。n_clusters是最需要根据数据调的参数——摘要里也提到如果测试集有多个分类n_clusters应该设成分类个数。设错了聚类结果要么全挤一簇要么碎成单点。注意Word2Vec 训练语料如果只有几篇文档词向量基本没有语义区分度聚类结果和随机差不多。源码里vecs目录预存了向量说明作者也意识到现场训练不现实直接加载更稳。4. 避坑与排查三种方法跑不通时先看这几处4.1 分词和停用词没对齐结果全是噪声现象跑出来的关键词里混着「的」「了」「我们」这类词或者全是单字。原因通常是停用词表没加载成功或者分词后没做长度过滤。stopWord.txt的编码如果是 GBK用 UTF-8 读会乱码过滤就失效。解决先打印停用词集合的长度和前几个词确认加载正确再检查len(w) 1这类过滤条件是否生效。4.2 TF-IDF 在单篇文档上权重全为 0现象keys_TFIDF.csv里所有词权重一样或者为空。原因是 IDF 计算依赖多篇文档只有一篇时TfidfVectorizer的 IDF 退化成常数甚至报空词汇表。解决确保sample_data.csv里有多篇文档或者改用「词频 位置权重」的简化方案。这也是为什么源码要配一个样例数据集而不是让你随便丢一段文本进去。4.3 TextRank 图太大导致迭代慢或内存爆现象长文档跑 TextRank 时卡住或者内存占用飙升。原因是共现窗口设得太大词与词之间边数呈平方级增长。解决把window降到 2 到 3或者在建图前先过滤掉低频词只保留出现次数超过阈值的词作为节点。源码里词性标注参考.txt其实提示了一个方向——只保留名词、动词、形容词这类实词建图能大幅减小图规模。4.4 Word2Vec 加载预存向量时报维度不匹配现象keyextract_word2vec_2.py加载vecs目录下的向量时抛KeyError或维度错误。原因是训练时的vector_size和加载时不一致或者目标词不在词表里。解决加载后先打印model.wv.vector_size和model.wv.index_to_key[:10]确认对不在词表的词做跳过处理不要直接索引。4.5 三种方法结果差异大不知道该信哪个现象同一篇文档TF-IDF 给出「文本、抽取」TextRank 给出「关键词、方法」Word2Vec 给出「语义、聚类」几乎没有交集。原因不是代码错了而是三种方法的假设不同TF-IDF 看统计稀有性TextRank 看共现结构Word2Vec 看语义分布。解决把三份 CSV 做交集和并集交集词作为高置信关键词并集词作为候选再人工或规则筛一遍。这也是这份课程设计最有价值的地方——它让你亲眼看到方法差异而不是背概念。5. 进阶技巧把三种结果融合成一份可用的关键词表三种方法各跑一遍之后真正能落地的是融合策略。我一般会做加权投票TF-IDF 权重归一化后占 0.4TextRank 占 0.3Word2Vec 聚类代表词占 0.3同一词在多路出现就累加分数最后取 TopN。这样既保留了统计上的稀有词也兼顾了结构重要词和语义代表词。源码里的result目录已经分好了三份 CSV你只需要写一个合并脚本import pandas as pd # 三份结果各自读入假设列名为 word, score tfidf pd.read_csv(result/keys_TFIDF.csv).rename(columns{score: tfidf}) textrank pd.read_csv(result/keys_TextRank.csv).rename(columns{score: textrank}) w2v pd.read_csv(result/keys_word2vec.csv).rename(columns{score: w2v}) # 按 word 外连接合并缺失值填 0 merged tfidf[[word, tfidf]].merge(textrank[[word, textrank]], onword, howouter) merged merged.merge(w2v[[word, w2v]], onword, howouter).fillna(0) # 各自归一化后加权求和 for col in [tfidf, textrank, w2v]: if merged[col].max() 0: merged[col] merged[col] / merged[col].max() merged[final] merged[tfidf] * 0.4 merged[textrank] * 0.3 merged[w2v] * 0.3 print(merged.sort_values(final, ascendingFalse).head(10))参数上三个权重不是固定的如果你的语料领域性强、通用词多可以提高 TF-IDF 权重如果文档结构清晰、句子长TextRank 权重可以调高如果语料本身语义丰富、同义词多Word2Vec 权重值得加大。验证方法也简单拿几篇你人工标过关键词的文档看融合结果和人工标注的重合率比单跑任何一种方法都高就说明权重方向对了。还有一个容易被忽略的技巧来自摘要里的提示标题文本往往包含文档的重要信息可以对标题中出现的词给一个初始权重加成。具体做法是在分词阶段记录词是否出现在标题里融合时给这些词乘一个 1.2 到 1.5 的系数。这个改动很小但在新闻和论文类文档上提升明显。另外如果某些词在所有文档里都高频出现比如「研究」「方法」可以在融合前直接按文档频率阈值剔除避免它们靠 TF-IDF 的 IDF 惩罚不够而混进结果。我从第一次跑这份源码到现在养成了一个习惯任何关键词抽取任务先跑三种方法看交集再决定用哪种融合权重绝不单信一种。这份资源最大的价值不是代码多精妙而是它把三条路线的输入输出都摆在你面前让你自己踩一遍坑。希望帮到你。本文还有配套的精品资源点击获取