用 Python 从单个文本中提取关键字的四种超棒的方法
今天就聊点实在的:用 Python 从单个文本里提关键字,四种不太花哨、但真能落地的办法。我都用中文例子,代码也尽量写得接地气一点。
方法一:最朴素,但很好用的“词频统计”
先来最原始的:谁出现得多,谁就是关键词。 别嫌土,这个在很多场景下够用了,比如评论分析、简单的文章标签。
大致步骤就三步: 1)分词 → 2)过滤掉“的、了、是”这种废话 → 3)统计次数,按频率排序。
我们用 jieba 来做分词,用 collections.Counter 计数。
import jieba
from collections import Counter# 一段示例文本
text = """
Python 提取关键字的方法有很多,比如基于词频、TF-IDF、TextRank,
还有最近比较流行的基于预训练模型的方法。
在实际项目中,你可以根据数据量、准确率要求、性能需求来选择不同方案。
"""
# 简单停用词列表,真实项目建议用更全的
stopwords = {"的", "了", "是", "和", "有", "在", "也", "就", "对", "中", "你", "我"}
defkeyword_by_freq(text, top_k=10):
# 精确模式分词
words = jieba.lcut(text)
# 过滤掉停用词、长度为1的词(比如标点、单个字)
words = [w for w in words if w.strip() and w notin stopwords and len(w) > 1]
counter = Counter(words)
# most_common 返回 (词, 词频) 的列表
return counter.most_common(top_k)
if __name__ == "__main__":
for word, freq in keyword_by_freq(text, top_k=5):
print(word, freq)
这种方法的特点你可以简单记一下:
优点:实现简单、依赖少、速度快,对短文本也挺友好。 缺点:只看“出现次数”,完全不管“是不是很普通的词”。比如“方法”“项目”这种行业高频词也会排得很前面。
如果你只是想做个快速的小工具、脚本,这个方案已经能撑起一片天了。
方法二:TF-IDF —— 让“特别”的词排得更前
词频只是看谁出现得多,但有些词很多文章都有,比如“系统”“功能”“数据”。 TF-IDF 做的事就是:出现得多是加分项,但如果“大家都在说你”,那就没那么特别了。
公式那些就不展开背了,你可以这么想:
TF(Term Frequency):在这篇文本里出现得多 → 说明对这篇文本很重要; IDF(Inverse Document Frequency):在整个语料库里很少见 → 说明它“有个性”。
问题是:我们现在只有“单个文本”,哪来的“语料库”算 IDF 呢?
两个常见的小技巧:
把这篇长文本拆成多段(比如按段落、句子),当成一个小语料; 或者,准备一些同类型的历史文案,当成背景语料(推荐)。
下面用 sklearn 的 TfidfVectorizer 做个简单示例,把一整篇文本按“句子”拆开:
import re
import jieba
from sklearn.feature_extraction.text import TfidfVectorizertext = """
Python 提取关键字的方法有很多,比如基于词频的、基于 TF-IDF 的、基于 TextRank 的,
还有最近比较流行的基于预训练模型的方法。
如果你只是做一个小工具,词频统计就够用了;
如果想让“更有信息量”的词排在前面,可以试试 TF-IDF。
"""
# 简单按标点切句子
defsplit_to_sentences(text: str):
# 这里只是简化写法,生产环境可以用更成熟的分句工具
sentences = re.split(r'[。!?;\n]', text)
return [s.strip() for s in sentences if s.strip()]
defjieba_tokenizer(text: str):
return [w for w in jieba.lcut(text) if w.strip()]
defkeyword_by_tfidf(text: str, top_k=10):
sentences = split_to_sentences(text)
ifnot sentences:
return []
# TfidfVectorizer 接受一个“把文本转成 token 列表”的函数
vectorizer = TfidfVectorizer(
tokenizer=jieba_tokenizer,
stop_words=None, # 中文停用词建议自己传一份
max_df=0.8, # 太常见的词丢掉
min_df=1# 单文档这里就别设太大
)
tfidf_matrix = vectorizer.fit_transform(sentences)
# 求整篇文本里每个词的最大 TF-IDF 分数
# axis=0 表示对行(句子)取最大值
max_scores = tfidf_matrix.max(axis=0).toarray().ravel()
vocab = vectorizer.get_feature_names_out()
word_score_pairs = list(zip(vocab, max_scores))
# 按分数从高到低排序
word_score_pairs.sort(key=lambda x: x[1], reverse=True)
return word_score_pairs[:top_k]
if __name__ == "__main__":
for word, score in keyword_by_tfidf(text, top_k=8):
print(word, round(float(score), 4))
这个方案更适合一类场景:你有一堆同类型文本(比如很多篇技术文章、很多条用户评论),想统一打标签、做搜索权重,用 TF-IDF 会比纯词频靠谱不少。
方法三:TextRank —— 把词连成“关系网”再选头部
前面两个方法,都是看“这个词本身”的统计信息。 TextRank 这类算法有点不一样,它会看“词和词之间的关系”。
思路简单说就是:
把文本分词; 以一个固定长度的窗口(比如 2~4 个词)滑过去,窗口里出现的词两两连边; 于是我们得到一个“词共现图”,两个词一起出现次数越多,这条边就越“重”; 在这个图上跑 PageRank(对,就是搜索引擎那个 PageRank 的思想),得到每个词的重要度。
听起来有点抽象,用 jieba.analyse 直接调就好了:
import jieba
import jieba.analysetext = """
Python 提取关键字有很多思路,TextRank 算法算是比较有代表性的一个。
它会把词之间的共现关系看成一个图,然后用类似 PageRank 的方法给每个词打分。
适合用在没有太多历史语料、但是单篇文本比较完整的场景。
"""
defkeyword_by_textrank(text: str, top_k=10):
# withWeight=True 可以拿到 (词, 权重)
tags = jieba.analyse.textrank(
text,
topK=top_k,
withWeight=True,
allowPOS=('ns', 'n', 'vn', 'v') # 只保留名词、动词等
)
return tags
if __name__ == "__main__":
for word, weight in keyword_by_textrank(text, top_k=8):
print(word, round(weight, 4))
这个方法相对于 TF-IDF 有几个比较实用的点:
不太依赖外部语料,只用当前这篇文本也能跑; 对“结构比较完整”的文章会有优势,比如一篇讲故事、讲原理的长文; 会把那些在重要位置成串出现的词抬得更高。
当然它也不是万能的,比如文档特别短、文本本身就比较乱,效果就一般般。
方法四:借力预训练模型,玩一点“语义级”的关键词
前面三种方法,本质上都是基于词面和统计:谁出现多、谁和谁一起出现,之类的。 现在很多场景下你会希望:关键词不是简单的单词,而是一小段短语,而且能更贴“语义”。
举个例子:
文本: “我们用 Python 写了一个小工具,自动从公众号文章中提取标题级别的关键词,用于推荐系统打标签。”
传统方法给出来的可能是:Python、小工具、文章、关键词、推荐系统但你可能更想要的是:Python 小工具、公众号文章、推荐系统、关键词提取 这种短语。
这一类思路可以借鉴 KeyBERT 的做法:
用预训练语言模型(比如 Sentence-BERT)把整段文本编码成一个向量; 再把所有候选词/短语也编码成向量; 计算每个候选和整段文本的余弦相似度,相似度越高越说明“跟这段话最贴”。
简单示例(需要先安装依赖):
pip install keybert sentence-transformers
然后代码:
from keybert import KeyBERTtext = """
我们用 Python 写了一个小工具,自动从公众号文章中提取标题级别的关键词,
用在推荐系统的内容打标签环节。这样用户历史点击过的文章,就可以通过标签去做相似内容推荐。
"""
# 模型可以选一个多语言或者中文效果比较好的
# 第一次加载会比较慢,会自动下载权重
kw_model = KeyBERT(model='distiluse-base-multilingual-cased-v1')
defkeyword_by_bert(text: str, top_k=10):
# use_maxsum / use_mmr 可以控制多样性,这里先用一个比较常用的 MMR
keywords = kw_model.extract_keywords(
text,
keyphrase_ngram_range=(1, 3), # 1~3 个词的短语
stop_words=None, # 真正用时建议自己传停用词
top_n=top_k,
use_mmr=True, # 增加结果多样性
diversity=0.5
)
return keywords
if __name__ == "__main__":
for phrase, score in keyword_by_bert(text, top_k=8):
print(phrase, round(score, 4))
这个方案的优点就比较明显了:
能直接给出短语级别的关键词,更接近人写标签的感觉; 对“语义相近但字面不同”的情况比较友好,比如“推荐系统”和“内容推荐”,模型知道它们很像; 如果用在多语言环境里,换个多语言模型就行。
代价也很实在: 模型大、加载慢、依赖多,放在离线任务、定时任务里比较舒服,要是每次接口都临时起一个就有点折腾服务器了。
你可以先看自己场景是哪一挂的,大致这么想:
脚本级小工具 / Demo / 文本很短→ 用方法一:词频统计,简单粗暴,bug 最少。
有不少同类型文本,需要统一打标签、做搜索权重→ 方法二:TF-IDF,配合自己的语料,效果会稳定很多。
单篇文本比较长、结构完整,但没有额外语料→ 方法三:TextRank,非常适合“就这一篇”的情况。
想要“人类味”更强一点的短语级关键词,有一定算力预算→ 方法四:预训练模型(KeyBERT 思路),尤其适合内容推荐、检索等场景的离线加工。
实际项目里,你完全可以把这些方法混着用,比如:
先用 TextRank 或 TF-IDF 选 50 个候选词; 再用预训练模型从这 50 个里面精挑 10 个短语出来,这样既省算力又兼顾语义效果。
先聊到这儿。你如果手上刚好有一段文本,可以直接把上面四段代码随便拿一段跑一下,肉眼对比下结果,很快就能感觉到: 不同方法“偏好”的关键词是真的不一样。
-END-
我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html
虎哥作为一名老码农,整理了全网最全《python高级架构师资料合集》,总量高达650GB