AI编程(十五):单词统计器 — 让程序读完一整篇文章

作者:

用字典给词频计数,找出一篇文章里最重要的10个词

一、一个真实的场景

你写了一篇文章(或者拿到领导的一篇讲话稿),想知道:

  • 这篇文章里哪些词出现得最多
  • 作者嘴上说的重点,和他真正反复强调的,是不是一回事?

手工数?一篇文章几千字,数到明年。

这件事程序最擅长:读取文件 → 把文字拆成词 → 数每个词出现几次 → 排序 → 打印前10名。全程1秒不到。

你可能觉得这是”高级文本分析”。其实不是——用到的全是学过的东西:文件读取(第12篇)、字典(第八篇)、循环、字符串split、排序

这个小工具也是所有”关键词提取””文本分析”功能的雏形。搜索引擎、舆情监控,底层逻辑都是这个:数词频。

二、先搞懂核心思路

数词频就三步:

  1. :把一整篇文章拆成一个个词(用split(),按空格切开)
  2. :用字典记录每个词出现了几次(词是key,次数是value)
  3. :按次数从高到低排序,取前几名

第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)这样的元组,直接拆成两个变量wordtimes。比写for item in items: print(item[0], item[1])清爽多了。

六、运行试试

python word_count.py

用上面的测试文章,输出:

出现最多的10个词:
人工智能: 4次
可以: 2次
帮助: 2次
会: 2次
正在: 1次
改变: 1次
世界。人工智能: 1次
医生: 1次
诊断: 1次
疾病,人工智能: 1次

一眼就看出:这篇文章嘴上讲了很多,其实核心就是”人工智能”和”学习”。

但有两个明显的问题:

  1. “世界。人工智能”被当成一个词——因为句号后面没空格,split()认不出来那是两个词
  2. “可以””会”这种废话词混在里面——它们出现次数不少,但没有分析价值

这两个瑕疵马上修。

七、第一个改进:过滤停用词

输出里除了真正的关键词,还混着大量”废话词”——”的、了、是、可以、帮助”这种。语言学上叫停用词:出现频率高但没实际含义。

解决办法:准备一个停用词列表,统计时跳过它们。

顺便把标点清掉(用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.txtlen(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这种带括号式子的命令行工具。字典里存函数,这个技巧会让你大开眼界。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注