news 2026/10/17 5:16:24

使用Python实现文本词频分析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
使用Python实现文本词频分析

前言


文本词频分析(word frequency analysis)要解决的问题很直接:给定一段文本,统计其中每个词出现了多少次,并按次数从多到少排序。它是关键词提取、日志统计、舆情监控、文本分类特征工程的第一步,也是最基础的一步。


新手最常见的两个误解。第一个是以为"统计词频"就是"对每个词调用一次list.count()"——这样写的复杂度是 O(n²),文本一变长就慢得没法用。第二个是以为中文文本可以直接调用str.split()得到词,实际上split()只按空白字符切分,而中文句子内部没有空格,切出来往往是整句。


本文用纯标准库(re、collections)搭一套可以直接复制进.py文件运行的英文词频分析流程,并说清中文分词为什么绕不开第三方库,以及不装第三方库时的退路是什么。文中所有示例按 Python 3.8+ 编写。


一、词频分析的完整流程


一个能用的词频分析,通常要经过四步,缺一步结果就会走样:



  1. 读取文本。从字符串、文件或网页拿到底层文本。

  2. 归一化(normalization)。统一大小写、去掉标点、处理数字。

  3. 切词(tokenization)。把长文本切成一个个词元(token)。

  4. 计数与排序。统计频次,取 Top-N。


很多"我自己写了个词频统计"的代码之所以结果奇怪,就出在第 2、3 步:"Python"和"python"被算成两个词,"word,"带标点和"word"也被算成两个词。


计数这一步不要手写循环累加,标准库已经给了专门的数据结构。




做法复杂度推荐度



对每个词调用list.count()O(n²)不推荐

手写dict累加d[w] = d.get(w, 0) + 1O(n)可用

collections.CounterO(n)推荐



二、切词:英文用正则,中文要走分词


英文的词与词之间有空格,但直接用split()还是不够:"word,"会把逗号一起切进来。更稳的写法是用正则表达式re.findall(),只匹配"连续的字母",把标点当作分隔符扔掉。


# 适用于 Python 3.8+
import re

text = "The quick brown fox. The quick dog! The Fox."
words = re.findall(r"[A-Za-z']+", text.lower())
print(words)
# ['the', 'quick', 'brown', 'fox', 'the', 'quick', 'dog', 'the', 'fox']

注意这里先.lower()做归一化,所以Fox和fox合并成了一个词,这是绝大多数场景想要的结果。如果你要区分大小写,去掉.lower()即可。


中文的情况完全不同。中文句子"今天天气很好"内部没有空格,re.findall(r"[A-Za-z']+", ...)什么都匹配不到,split()只会返回整句一个元素。中文正确切词的唯一实用办法是分词库,比如结巴分词(jieba)。jieba 是第三方库,需要单独安装,本机没有安装它,下面的调用请以 jieba 官方文档为准,我这里只写用法思路:


# 适用于 Python 3.8+;需要安装第三方库 jieba,此处仅示意用法
import jieba

words = list(jieba.cut("今天天气很好,我们出去走走"))
# 大致切分为 ['今天', '天气', '很好', '我们', '出去', '走走']

如果不允许引入第三方库,可以用两种"降级"方案:一是按单字统计(把每个汉字当一个词,统计字频),二是按二元组(bigram)统计,也就是把相邻的两个字拼成一个词元。后者的写法是:


# 适用于 Python 3.8+:中文二元组切词,无需第三方库
import re

def bigrams(text):
chars = re.findall(r"[一-鿿]", text)
return [chars[i] + chars[i + 1] for i in range(len(chars) - 1)]

print(bigrams("今天天气很好"))
# ['今天', '天天', '天气', '气很', '很好']

这里的一到鿿是常用汉字在 Unicode 中的码点区间。二元组切法噪声较大,但不需要任何依赖,适合做粗粒度统计。


三、用 Counter 计数


collections.Counter是dict的子类,专门用来数东西。它可以接收一个可迭代对象,直接把每个元素的出现次数统计好:Counter(words)之后,counter["the"]就是"the"的次数,取不存在的键返回0而不是抛KeyError。


# 适用于 Python 3.8+
from collections import Counter

words = ['the', 'quick', 'brown', 'fox', 'the', 'quick', 'the']
counter = Counter(words)

print(counter['the']) # 3
print(counter['missing']) # 0,不报错

# 取出现次数最多的 2 个
print(counter.most_common(2))
# [('the', 3), ('quick', 2)]

most_common(n)返回的是(元素, 次数)元组的列表,按次数降序排列。不传参数时返回全部元素。它的排序在次数相同时顺序由元素本身的顺序决定,属于实现细节,不要依赖。


几个常用方法:



  • counter.update(iterable):把新一批词加进去,次数累加。

  • counter.elements():按次数把每个元素重复展开成迭代器。

  • counter.total():所有次数之和(需要 Python 3.10 及以上)。在 3.9 及更早版本用sum(counter.values())代替。


四、过滤停用词与取 Top-N


the、a、is这类词叫停用词(stop word),它们出现频率最高但几乎没有信息量。做法是先把它们塞进一个set,再生成计数前把它们过滤掉。


# 适用于 Python 3.8+
STOP_WORDS = {"the", "a", "an", "is", "are", "of", "to", "and"}

filtered = [w for w in words if w not in STOP_WORDS and len(w) > 1]
counter = Counter(filtered)

判断"是不是停用词"用set而不是list:in操作在set上是平均 O(1),在list上是 O(n)。词表一大,差别就出来了。


完整的可运行示例:


# 适用于 Python 3.8+
import re
from collections import Counter

STOP_WORDS = {"the", "a", "an", "is", "are", "of", "to", "and", "in", "on"}

def word_freq(text, top_n=5):
tokens = re.findall(r"[A-Za-z']+", text.lower())
tokens = [t for t in tokens if t not in STOP_WORDS and len(t) > 1]
counter = Counter(tokens)
return counter.most_common(top_n)

sample = (
"Python is a programming language. Python is used for data analysis. "
"Data analysis with Python is popular. Python Python!"
)
for word, count in word_freq(sample):
print(f"{word}: {count}")

程序推演:sample先转小写,再被正则切出所有字母词,过滤掉停用词和单字母词后,python出现 4 次、data出现 2 次、analysis出现 2 次、programming、language、used、popular各 1 次。most_common(5)输出前五名。


常见坑点



  1. 在循环里用list.count()统计。这是最典型的新手写法,复杂度 O(n²),十万词的文本会卡住。


❌for w in set(words): freq[w] = words.count(w)✅freq = Counter(words),一次遍历统计完,O(n)。



  1. 忘了统一大小写和剥离标点。"Python"与"python"、"fox"与"fox."会被拆成不同的键,Top-N 结果严重失真。


❌words = text.split()✅words = re.findall(r"[A-Za-z']+", text.lower())



  1. 以为中文能直接split()或直接用英文正则。中文没有空格,split()返回整句,[A-Za-z']+一个都匹配不到,统计结果为空。


❌Counter(text.split())处理中文 ✅ 用 jieba 等分词库,或退而求其次按单字 / 二元组切分。



  1. 把most_common()的返回当成字典。它返回的是(元素, 次数)元组的列表,直接["word"]下标取值会报TypeError。


❌top = counter.most_common(3)然后top["python"]✅top = counter.most_common(3)后遍历for word, count in top:。



  1. 在 3.9 及更早版本调用Counter.total()。该方法是3.10 新增,旧版本会AttributeError。


❌ 在 3.8 环境写counter.total()✅ 用sum(counter.values()),它在所有 3.x 版本都可用。



  1. 统计时修改字典导致RuntimeError。边遍历counter边删键(比如想顺手删掉低频词)会抛dictionary changed size during iteration。


❌for w in counter: if counter[w] < 2: del counter[w]✅ 先收集要删的键,再删除:for w in [k for k, c in counter.items() if c < 2]: del counter[w]。



  1. 读取文件不指定encoding。在默认编码不是 UTF-8 的平台上(典型是 Windows 简体中文环境用 GBK),读含中文的文本会乱码或直接抛UnicodeDecodeError。


❌open("corpus.txt")✅open("corpus.txt", encoding="utf-8")。



  1. 把行数当词数。len(open(path).readlines())得到的是行数,不是词数,两者完全不是一回事。


❌ 用文件行数冒充词频总数 ✅ 分词后再sum(counter.values())。


总结




环节推荐做法关键点



读取open(path, encoding="utf-8")显式指定编码,避免跨平台乱码

归一化.lower()+ 正则剥标点否则同一个词被拆成多个键

英文切词re.findall(r"[A-Za-z']+", text.lower())不要用split()

中文切词jieba 等分词库;无依赖时用单字 / 二元组中文无空格,正则匹配字母无效

计数collections.CounterO(n),不要用list.count()

排序counter.most_common(n)返回元组列表,不是字典

过滤set存停用词in判定平均 O(1)



词频分析看着简单,真正的难点不在计数,而在"什么算一个词"。英文靠正则归一化,中文靠分词库;把切词这一层做对,后面的Counter和most_common几乎不会出错。反过来,切词一旦做错,再漂亮的统计代码给出的也只是一堆没有意义的字符串。




版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/17 5:15:51

计算机网络分层思想与数据封装解封装原理详解

网络的分层思想和数据封装与解封装&#xff0c;这两个词放在一起&#xff0c;基本就是计算机网络这门课里最容易被讲得云里雾里、但又最绕不开的地基。我见过太多人背得下OSI七层模型的名字&#xff0c;却说不清为什么要有分层&#xff1b;也见过有人能画出TCP/IP四层结构图&am…

作者头像 李华
网站建设 2026/10/17 5:15:40

边缘计算+AIoT智能监控:海康设备接入与算法部署实战

1. 从一次园区改造项目说起&#xff1a;为什么边缘计算成了智能监控的必选项去年下半年我接手了一个中型产业园的智能化改造项目&#xff0c;园区占地大概12万平方米&#xff0c;已经装了200多路海康威视的网络摄像头&#xff0c;后端是一台老旧的NVR在扛着。项目方的诉求听起来…

作者头像 李华
网站建设 2026/10/17 5:15:16

巡线小车进阶实战:ZTMR平台PID控制与多传感器融合解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/17 5:14:08

Qt5+MinGW下libmodbus源码编译与串口调试实战

简介&#xff1a;Windows 平台 Qt5 MinGW 环境下的 libmodbus 测试工程&#xff0c;面向需要在 Qt 项目中集成 Modbus 通信的开发者。压缩包共 21 个文件、约 39KB&#xff0c;以 h 头文件与 c 源文件为主体&#xff0c;涵盖 libmodbus 库核心接口&#xff1b;另有 cpp 源码、u…

作者头像 李华
网站建设 2026/10/17 5:13:07

高压直流输电在线监测Matlab仿真模型与GUI调参界面设计

前阵子和同行聊高压直流输电在线监测&#xff0c;有人第一句就问&#xff1a;你那个Matlab模型能不能直接用来挂现场数据&#xff1f;这个问题问得挺典型&#xff0c;说明不少人把仿真模型和真实监控系统搞混了。我这篇内容想聊的&#xff0c;是一套可复现的高压直流输电在线监…

作者头像 李华
网站建设 2026/10/17 5:13:07

云平台人脸识别1:N搜索测试:从人脸库创建到阈值调优的完整实践

1. 项目缘起与整体设计思路人脸识别这件事&#xff0c;从技术角度看早就不是新鲜词了&#xff0c;但真正落到一个具体项目里&#xff0c;尤其是做1&#xff1a;N的人脸比对&#xff0c;很多人才发现坑远比想象中多。我最近刚完成一个基于云平台人脸识别服务的测试案例&#xff…

作者头像 李华