用字典给词频计数,找出一篇文章里最重要的10个词
一、一个真实的场景
你写了一篇文章(或者拿到领导的一篇讲话稿),想知道:
- 这篇文章里哪些词出现得最多?
- 作者嘴上说的重点,和他真正反复强调的,是不是一回事?
手工数?一篇文章几千字,数到明年。
这件事程序最擅长:读取文件 → 把文字拆成词 → 数每个词出现几次 → 排序 → 打印前10名。全程1秒不到。
你可能觉得这是”高级文本分析”。其实不是——用到的全是学过的东西:文件读取(第12篇)、字典(第八篇)、循环、字符串split、排序。
这个小工具也是所有”关键词提取””文本分析”功能的雏形。搜索引擎、舆情监控,底层逻辑都是这个:数词频。
二、先搞懂核心思路
数词频就三步:
- 拆:把一整篇文章拆成一个个词(用
split(),按空格切开) - 数:用字典记录每个词出现了几次(词是key,次数是value)
- 排:按次数从高到低排序,取前几名
第1篇我们看过一个”数字版”的雏形(统计每个字出现几次)。今天升级成:从文件读取、按词统计、排序输出。
三、准备一篇文章
先创建一个测试文章article.txt,随便写点内容(或者粘贴一篇你喜欢的文章):
人工智能 正在 改变 世界。人工智能 可以 帮助 医生 诊断 疾病,人工智能 可以 帮助 老师 批改 作业。
很多人 担心 人工智能 会 抢走 工作,但 也有人 认为 人工智能 会 创造 新的 工作。
无论如何,学习 人工智能 已经 成为 一种 趋势。学习 编程,学习 人工智能,从现在开始。
保存到和你的Python文件同一个文件夹里。
注意:我故意在词和词之间留了空格。 现实中的中文文章是没有空格的(”人工智能正在改变世界”连在一起),按空格切分会把一整行当成一个”词”。中文分词需要专门的库(比如jieba),那是以后的事。今天我们用这种”已分好词”的文本,先把词频统计的原理搞懂——英文文章天生有空格,拿来就能直接用。
四、先跟AI聊
你的提示词:
用Python写一个单词统计器:读取当前目录下的article.txt文件,把内容按空格拆分成词语,用字典统计每个词出现的次数,最后按次数从高到低排序,打印出现最多的前10个词和它们的次数。加上详细的中文注释。
AI会给你一段代码,大概长这样:
# 打开文件并读取内容
with open("article.txt", "r", encoding="utf-8") as f:
text = f.read()
# 把文章拆成一个个词
words = text.split()
# 用字典统计每个词的出现次数
count = {}
for word in words:
if word in count:
count[word] = count[word] + 1
else:
count[word] = 1
# 把字典转成列表,方便排序
items = list(count.items())
# 按出现次数从高到低排序
items.sort(key=lambda x: x[1], reverse=True)
# 打印前10名
print("出现最多的10个词:")
for word, times in items[:10]:
print(f"{word}: {times}次")
五、逐行理解
第2-3行:读文件
with open("article.txt", "r", encoding="utf-8") as f:
text = f.read()
第12篇学过这个套路,复习一下:
open("article.txt", "r"):打开文件,r是read(读)模式encoding="utf-8":告诉Python用UTF-8编码读(读中文必备,忘了它中文会乱码)with ... as f:安全打开,读完自动关上f.read():把整个文件内容一次性读出来,存进text变量
现在text就是整篇文章的字符串。
第6行:split() —— 拆词神器
words = text.split()
split()是字符串的方法:按空格把字符串切成一堆小字符串。
比如"今天 天气 真好".split()得到['今天', '天气', '真好']。
不传参数时,它按”任何空白”(空格、换行、Tab)切分,而且多个空格算一个——很省心。
一个小坑:中文没有空格分隔词,所以split()对连着的中文(如”人工智能正在”)会整段当成一个”词”。这就是为什么我们准备的测试文章里用了标点和空格来分隔。真实项目里中文分词需要专门的库(比如jieba),但那是以后的事——今天我们先用最朴素的办法把原理搞懂。
第9-13行:字典计数(第1篇的老朋友)
count = {}
for word in words:
if word in count:
count[word] = count[word] + 1
else:
count[word] = 1
这段逻辑你在第1篇见过(数字版)。翻译成人话:
count = {}:准备一个空记事本- 遍历每个词:如果记事本上已经有这个词了,就在它名字后面画”正”字(次数+1);如果是第一次见,写上这个词,记1次
循环结束后,count长这样:{'人工智能': 5, '学习': 3, '可以': 2, ...}
第16行:字典 → 列表
items = list(count.items())
字典不能直接排序,但列表可以。所以先把字典转成列表。
count.items()把字典拆成一对一对的:[('人工智能', 5), ('学习', 3), ...]——每对是一个元组,前面是词,后面是次数。
list(...)把这些对转成真正的列表。现在items是一个”元组列表”。
第19行:排序(今天最有含金量的一行)
items.sort(key=lambda x: x[1], reverse=True)
这行有点复杂,拆开看:
items.sort(...):给列表排序(原地排序,排完items自己就变了)reverse=True:倒着排——从大到小。不加这个参数是从小到大key=lambda x: x[1]:按什么排。x[1]是元组的第二个元素(次数),x[0]是词。所以这句的意思是”按次数排,不是按词排”
lambda x: x[1]叫匿名函数——没起名字的迷你函数,意思是”给我一个x,我返回x[1]”。如果这个写法让你头疼,很正常,第一次见都头疼。问AI:”用大白话解释sort的key=lambda参数”。
其实今天你不需要完全吃透lambda,只要知道:这行代码=按次数从大到小排序。
第22-24行:切片取前10
print("出现最多的10个词:")
for word, times in items[:10]:
print(f"{word}: {times}次")
两个知识点:
切片:items[:10]取列表前10个元素(第八篇学过切片)。如果列表不足10个,有多少取多少,不会报错。
元组解包:for word, times in items[:10] —— 每个元素是('人工智能', 5)这样的元组,直接拆成两个变量word和times。比写for item in items: print(item[0], item[1])清爽多了。
六、运行试试
python word_count.py
用上面的测试文章,输出:
出现最多的10个词:
人工智能: 4次
可以: 2次
帮助: 2次
会: 2次
正在: 1次
改变: 1次
世界。人工智能: 1次
医生: 1次
诊断: 1次
疾病,人工智能: 1次
一眼就看出:这篇文章嘴上讲了很多,其实核心就是”人工智能”和”学习”。
但有两个明显的问题:
- “世界。人工智能”被当成一个词——因为句号后面没空格,
split()认不出来那是两个词 - “可以””会”这种废话词混在里面——它们出现次数不少,但没有分析价值
这两个瑕疵马上修。
七、第一个改进:过滤停用词
输出里除了真正的关键词,还混着大量”废话词”——”的、了、是、可以、帮助”这种。语言学上叫停用词:出现频率高但没实际含义。
解决办法:准备一个停用词列表,统计时跳过它们。
顺便把标点清掉(用replace()把标点替换成空格):
# 打开文件并读取内容
with open("article.txt", "r", encoding="utf-8") as f:
text = f.read()
# 把标点替换成空格(这样split切出来的就是干净的词)
punctuation = ",。!?、;:""''()《》……——nt"
for p in punctuation:
text = text.replace(p, " ")
# 停用词列表:这些词没分析价值
stop_words = ["的", "了", "是", "在", "和", "也", "就", "都", "而", "及", "与",
"可以", "一个", "我们", "你", "我", "他", "这", "那", "有", "要"]
# 拆词
words = text.split()
# 统计(跳过停用词)
count = {}
for word in words:
if word in stop_words:
continue # 是停用词,跳过不统计
if word in count:
count[word] = count[word] + 1
else:
count[word] = 1
# 排序
items = list(count.items())
items.sort(key=lambda x: x[1], reverse=True)
# 打印前10
print("出现最多的10个词:")
for word, times in items[:10]:
print(f"{word}: {times}次")
清标点:replace() + 循环
punctuation = ",。!?、;:""''()《》……——nt"
for p in punctuation:
text = text.replace(p, " ")
replace(旧, 新)是字符串方法:把”人工智能。”变成”人工智能 “(句号换空格)。一个replace()只能换一种标点,所以套个循环,把标点字符串里的每个字符挨个替换。
清完标点,split()切出来的就是干净的词了——”世界。”变成”世界”。
跳过停用词:continue
if word in stop_words:
continue
停用词列表就是一个普通列表,in操作检查当前词在不在里面。在的话,continue跳过本次循环(第13篇学过)。
stop_words你可以随时往里加——看统计结果里还有什么废话词,加进去重新跑。
改进后的输出
出现最多的10个词:
人工智能: 7次
学习: 3次
帮助: 2次
会: 2次
工作: 2次
正在: 1次
改变: 1次
世界: 1次
医生: 1次
诊断: 1次
干净多了。”人工智能7次、学习3次”——这篇文章的骨架完全暴露。”世界。人工智能”也被正确拆开了,标点清理生效。
八、第二个改进:显示Top 10(带名次和进度条)
前10已经在打印了?不,这次我们把它做得好看:带名次、带次数、还用字符画个简单的”条形图”:
def show_top(items, n=10):
"""显示前n名,带名次和简单条形图"""
print(f"n🏆 出现最多的{n}个词:")
print("-" * 40)
for i, (word, times) in enumerate(items[:n]):
# 用重复字符画条形图(1次=2个方块)
bar = "█" * (times * 2)
print(f"第{i + 1:>2}名 {word:3}次 {bar}")
等等,新东西有点多。别慌,逐个看。
函数化:把”显示”单独打包
显示逻辑独立成show_top()函数。它只管显示,不管统计——一块代码只干一件事(第14篇刚讲过)。
enumerate() —— 带编号的循环
for i, (word, times) in enumerate(items[:n]):
普通的for word in 列表拿不到”这是第几个”。enumerate()给每个元素配上编号:生成(0, 元组), (1, 元组), (2, 元组)…
所以i是名次(从0开始,打印时+1),(word, times)还是元组解包。
enumerate()是Python内置的,不用import。问AI:”enumerate和直接for循环有什么区别?”——这是个好问题。
"█" * (times * 2) —— 字符串乘法
字符串乘以数字=重复!"█" * 5就是"█████"。出现5次的词,后面就跟10个方块,视觉上一目了然。
这是字符串的一个隐藏技巧,很多教程不讲,但画进度条、画分隔线时特别好用。比如"-" * 40就是一条40字符的分隔线。
f-string的格式化魔法
print(f"第{i + 1:>2}名 {word:3}次 {bar}")
{i+1:>2}、{word:<8}、{times:>3}是什么?
:>2:右对齐,占2个字符宽:<8:左对齐,占8个字符宽:>3:右对齐,占3个字符宽
对齐了输出才整齐,不然”人工智能”和”学习”长短不一,后面的数据参差不齐:
第 1名 人工智能 7次 ██████████████
第 2名 学习 3次 ██████
看不懂就运行一下,然后把:<8改成:<20再跑一次,观察变化。改参数观察输出,是最快的学法。
把它接进主程序
# 完整调用
items = list(count.items())
items.sort(key=lambda x: x[1], reverse=True)
show_top(items, 10)
九、第三个改进:支持命令行参数
现在文件名写死为article.txt。想统计别的文件,得改代码。
用sys.argv(命令行参数),让程序变成通用工具:
import sys
def load_words(filename):
"""读取文件,清理标点,返回词语列表"""
with open(filename, "r", encoding="utf-8") as f:
text = f.read()
punctuation = ",。!?、;:""''()《》……——nt"
for p in punctuation:
text = text.replace(p, " ")
return text.split()
def count_words(words):
"""统计词频,返回排序好的元组列表"""
stop_words = ["的", "了", "是", "在", "和", "也", "就", "都", "而", "及", "与",
"可以", "一个", "我们", "你", "我", "他", "这", "那", "有", "要"]
count = {}
for word in words:
if word in stop_words:
continue
if word in count:
count[word] = count[word] + 1
else:
count[word] = 1
items = list(count.items())
items.sort(key=lambda x: x[1], reverse=True)
return items
def show_top(items, n=10):
"""显示前n名"""
print(f"n🏆 出现最多的{n}个词:")
print("-" * 40)
for i, (word, times) in enumerate(items[:n]):
bar = "█" * (times * 2)
print(f"第{i + 1:>2}名 {word:3}次 {bar}")
# === 主程序 ===
# 从命令行参数获取文件名
if len(sys.argv) > 1:
filename = sys.argv[1]
else:
filename = "article.txt"
print(f"正在分析:{filename}")
try:
words = load_words(filename)
items = count_words(words)
print(f"共读取 {len(words)} 个词,其中不重复的词 {len(items)} 个")
show_top(items, 10)
except FileNotFoundError:
print(f"找不到文件 {filename},请检查文件名和路径!")
sys.argv —— 命令行传参
import sys后,sys.argv是一个列表,装着”运行命令的各个部分”:
python word_count.py 新闻.txt
这时sys.argv = ['word_count.py', '新闻.txt']。sys.argv[0]是脚本自己的名字,sys.argv[1]是第一个参数。
if len(sys.argv) > 1:
filename = sys.argv[1]
else:
filename = "article.txt"
有参数就用参数,没有就用默认的article.txt。len(sys.argv) > 1是在防呆:用户没传参数时,sys.argv[1]不存在,直接取会报错。
运行方式:
python word_count.py article.txt
python word_count.py 我的日记.txt
python word_count.py
同一个程序,分析任意文件。这就是”工具”和”一次性代码”的区别。
三个函数的分工
load_words(filename):读文件 + 清理 → 返回词列表count_words(words):统计 + 排序 → 返回元组列表show_top(items, n):格式化显示
主程序只有几行:拿参数 → 依次调用三个函数 → 处理文件不存在的错误。
这个结构叫”分层”:每层只管自己的事,出了问题一眼知道去哪层找。你以后写更大的程序,这个思路依然适用。
错误处理
except FileNotFoundError:
print(f"找不到文件 {filename},请检查文件名和路径!")
文件不存在会抛FileNotFoundError(第12篇提过,第13篇用过try/except)。现在程序不会崩溃,而是友好提示。
试试故意输错文件名,看程序的反应——故意搞破坏,是测试程序健壮性的好习惯。
十、回顾一下用到了什么
| 知识点 | 在哪里用的 |
|---|
|——–|———–|
| 文件读取 | with open + f.read() |
|---|---|
| 字符串split | 把文章拆成词 |
| 字符串replace | 清理标点 |
| 字典 | 词→次数的映射 |
| 字典计数套路 | if in: +1 else: =1 |
| items() | 字典转元组列表 |
| sort + key + reverse | 按次数从大到小排 |
| 切片 | items[:10] 取前10 |
| 元组解包 | for word, times in … |
| continue | 跳过停用词 |
| 函数 | 三个函数分工 |
| enumerate | 带名次的循环 |
| 字符串乘法 | “█” * n 画条形图 |
| sys.argv | 命令行参数 |
| try/except | 文件不存在的提示 |
15个知识点,全部来自前13篇。 其中lambda你可能还半懂不懂——没关系,能用就行,理解会随着使用次数自然加深。
十一、今日小练习
试着自己加一个功能(可以问AI帮忙):
功能:导出统计结果到文件
把前20名的统计结果写入result.txt,格式像这样:
人工智能 5
学习 3
帮助 2
提示:你需要第12篇学的文件写入——open("result.txt", "w", encoding="utf-8")和f.write()。注意循环里每次write后要加换行符n。
想想你会写在哪个函数里?自己加一个save_to_file()函数?还是塞进show_top()里?(想想”一块代码只干一件事”的原则。)
下一篇:简易计算器 — 不是手机上那种计算器,而是一个能连续计算、记录历史、还能算(3+5)×2这种带括号式子的命令行工具。字典里存函数,这个技巧会让你大开眼界。
发表回复