简介这份PDF教程面向希望掌握文本挖掘与数据可视化的Python初学者和中级用户系统讲解使用wordcloud与matplotlib生成个性化词云的完整流程。内容覆盖词云概念、作用、预处理、词频与TF-IDF权重、展示保存等基础环节并通过7个案例由浅入深演示最简调用、中文不分词、中文分词、蒙版形状、自定义配色及关键词权重提取等做法兼顾项目报告、数据分析、社交媒体监控和教学演示场景。资源包共1个PDF文件约2.07MB便于集中阅读与按案例检索。已有108人学习读者可据此理解词云生成原理积累可直接迁移的代码片段与参数配置思路并掌握从文本清洗到成图输出的实践链路。1. python课程资源里最实用的一页几万字文本压成一张词云图拿到一份三万字的用户反馈 txt直接从头读是效率最低的做法。比较省事的路径是先跑一张词云扫一眼哪些词最粗最显眼再决定深读哪几段。这份 python 课程资源里讲的正是这件事用wordcloudmatplotlib把文本按词频压成一张图7 个案例从最简单的三行调用一路做到蒙版形状、按图取色、TF-IDF 加权。环境写得很明确Python 3.7、wordcloud 1.9.3、matplotlib 3.5.3案例代码可以直接抄。它适合两类人一类是刚学完 Python 语法、想找个能出成果的小项目练手的另一类是要在报告、周会、数据看板里塞一张“大家都看得懂”的图的开发者。词云本身不复杂但中文场景下的字体、分词、蒙版这几处坑比想象中多。2. 工程骨架目录结构、依赖锁定与最小可跑脚本这套教程的案例是分散的独立脚本直接一个文件一个文件跑没问题但想改起来舒服最好先把目录拉成一个能复用的工程。原教程里案例用的是../xyj.txt、../Hiragino.ttf这种上级目录相对路径本质上是把脚本放进了子目录、数据和字体放外面。这种写法在 IDE 里点运行没问题一换到命令行就经常找不到文件因为它依赖的是“当前工作目录”而不是“脚本所在目录”。2.1 目录与样例文件清单我一般会把数据、字体、脚本、输出四类东西分开输出单独一个目录方便加完参数批量重跑时只清 output 不碰别的。按这个思路整理出来的结构大概是这样路径作用说明data/english_sample.txt英文样本文本用空格分词案例 01 直接读它data/novel.txt中文长文本需要先 jieba 分词案例 02 起用data/mask_shape.png形状蒙版纯白底 深色形状白色区域不排字data/color_mask.png彩色蒙版既当形状又当取色源案例 05、06、07 用fonts/cn_font.ttf中文字体必须是支持中文的 ttf/otf路径写绝对或项目相对cases/case01_simple.py…case07_keywords.py7 个案例一案例一文件便于对照output/图片输出目录跑之前先mkdir -p output注意mask的规则wordcloud 把蒙版图里白色#FFFFFF区域当成不排字的空白其余像素区域才填词。所以做形状蒙版时形状本体要填成黑或深色背景留纯白。如果拿一张白底照片直接当 mask跑出来会是一整块矩形看着像没生效。2.2 依赖安装与版本对齐教程锁的版本是 Python 3.7 wordcloud 1.9.3 matplotlib 3.5.3这个组合是有原因的matplotlib 从 3.6 起最低要求 Python 3.8在 3.7 上直接pip install matplotlib会拉到不兼容的版本然后报一堆语法错。想复现教程效果就这么钉住版本# 建独立环境避免污染系统 Python python -m venv .venv source .venv/bin/activate # Windows: .venv\Scripts\activate # 钉住教程验证过的版本 pip install wordcloud1.9.3 matplotlib3.5.3 jieba pillow numpy # 生成锁文件后续换机器复现 pip freeze requirements.txtwordcloud是带 C 扩展的包源码安装需要本地有编译工具链。Windows 上如果卡在 “Microsoft Visual C 14.0 is required”别硬装编译环境先python -m pip install --upgrade pip再装新版 pip 会优先拿预编译 wheel还不行就按 Python 版本去下对应 wheel 本地安装。jieba是纯 Python装上就能用pillow是读蒙版图必需的numpy用来把图片转成数组喂给mask。2.3 一个可复用的最小骨架案例 01 是十几行的平铺脚本作为演示够了但七个案例都是这个结构抄七遍就是在给自己制造维护成本。我习惯先把“读文本 → 生成 → 保存 → 显示”抽成一个函数后面所有案例只改变传入的参数# -*- coding: utf-8 -*- from wordcloud import WordCloud import matplotlib.pyplot as plt def make_wordcloud(text, out_path, showTrue, **kwargs): 根据文本生成词云并落盘返回 WordCloud 对象便于二次处理 wc WordCloud(**kwargs).generate(text) # 关键generate 接收的是已分好词的字符串 wc.to_file(out_path) # 先存文件避免 show 阻塞导致图没保存 if show: plt.imshow(wc, interpolationbilinear) # 插值只为观感不影响像素数据 plt.axis(off) # 关掉坐标轴否则边上带刻度很难看 plt.show() return wc if __name__ __main__: with open(data/english_sample.txt, encodingutf-8) as f: text f.read() make_wordcloud( text, output/demo.png, width1200, # 画布宽像素 height800, # 画布高像素 background_colorwhite, max_words200, # 最多排多少个词多了会挤 random_state42, # 固定随机种子保证每次布局一致 )几个参数值得先记住width/height决定画布尺寸默认 400×200 出来会糊max_words默认 200中文语料词汇量大时可以拉到 500random_state是复现的关键不设的话每次布局都不一样拿去写报告会被人问“你上次那张图呢”background_color设成None配合modeRGBA才是真透明光设None不加 RGBA 出来是黑底。把to_file放在plt.show()之前也是个习惯展示窗口一弹出来脚本就挂在那儿忘了关就没保存上白跑一次。3. 七个案例逐级拆解generate() 到 generate_from_frequencies() 的调用链七个案例不是七份互不相干的代码而是一条逐步加参数的递进路线01 建立最小调用02 和 03 解决中文04 到 06 解决形状和颜色07 换成自己算权重。把这四步串起来看就知道WordCloud这个类到底在哪几个环节留了口子。3.1 案例 01默认参数下 generate() 干了什么最简调用只有四行核心代码但generate(text)内部做了不少事先按空白字符切词统计词频默认还会把相邻的两个词组成二元组一起统计collocationsTrue然后按词频映射字号用随机布局算法把词一个个塞进画布塞不下就跳过。所以同一份文本两次运行出来的图长得不一样这是布局算法的随机性不是 bug。# -*- coding: utf-8 -*- from wordcloud import WordCloud import matplotlib.pyplot as plt with open(data/english_sample.txt, encodingutf-8) as f: text f.read() wc WordCloud( collocationsFalse, # 关掉二元组只保留单个词词表更干净 random_state42, ).generate(text) plt.imshow(wc, interpolationbilinear) plt.axis(off) plt.show() wc.to_file(output/case01.png)collocations这个参数很多人不知道开着的时候 “data science” 这种词组会以一个整体的形式出现字号按词组频次算英文场景有时反而更好看但做中文且已经用 jieba 分好词时二元组会把的 数据、数据 的这种无意义组合也拼进去一般建议关掉。interpolationbilinear只影响imshow的显示平滑度和导出的 png 无关别指望它提升图片质量。plt.axis(off)必须加否则图周围一圈坐标刻度看着像个半成品。3.2 案例 02 与 03中文不分词到底差在哪案例 02 特意演示了“中文不分词”的效果这个反面教材很有价值。中文句子词与词之间没有空格WordCloud的默认切分逻辑拿不到切分点整段文本会被当成极少数几个“超长词”结果要么几乎排不出字要么一个巨大的长串横在画布中间。案例 03 加了三行 jieba 就解决了# -*- coding: utf-8 -*- import jieba from wordcloud import WordCloud with open(data/novel.txt, encodingutf-8) as f: text f.read() # jieba.cut 返回生成器用空格 join 后 WordCloud 才能按空白切词 words jieba.cut(text, cut_allFalse) # 精确模式最常用 text_seg .join(words) print(text_seg[:100]) # 先打印前 100 字肉眼确认分词结果对不对 wc WordCloud( font_pathfonts/cn_font.ttf, # 中文字体不传就是一堆方块 width1600, height1000, modeRGBA, background_colorNone, # 透明背景方便贴进 PPT random_state42, ).generate(text_seg) wc.to_file(output/case03.png)jieba.cut的第二个参数是模式开关cut_allFalse是精确模式切得最准日常首选cut_allTrue是全模式会把长词里所有可能的短词都扫出来召回高但噪音大还有jieba.cut_for_search是搜索引擎模式在精确模式基础上把长词再切细做检索索引时用。中文词云九成场景用精确模式就够。text_seg[:100]这一行打印不是可有可无的调试残留中文分词出错时最典型的症状就是词云里全是“的”“了”这种单字先看前 100 字的分词结果问题立刻暴露。font_path指向的字体文件必须真实存在且支持中文这是中文词云翻车率最高的一处下一章单独展开。3.3 案例 04 到 06mask 蒙版与三种上色路线这三个案例解决的是“形状”和“颜色”逻辑是叠进的。案例 04 只加形状案例 05 在形状基础上按图取色案例 06 换成完全自定义的颜色函数。蒙版本身就是一个 numpy 数组所以要先过一遍 PIL 和 numpy# -*- coding: utf-8 -*- import numpy as np from PIL import Image import jieba from wordcloud import WordCloud, ImageColorGenerator with open(data/novel.txt, encodingutf-8) as f: text .join(jieba.cut(f.read())) # 读蒙版白色区域不排字彩色/深色区域排字 mask np.array(Image.open(data/color_mask.png)) wc WordCloud( maskmask, # 形状来源 font_pathfonts/cn_font.ttf, modeRGBA, background_colorNone, max_words300, max_font_size120, # 显式限高避免某个高频词撑爆画布 random_state42, ).generate(text) # 路线 A按蒙版像素取色词落在哪个色块就用哪个色块的颜色 image_colors ImageColorGenerator(mask) wc.recolor(color_funcimage_colors) wc.to_file(output/case05.png)ImageColorGenerator的取色逻辑是“按位置取样”某个词排在画布的 (x, y) 附近就去蒙版图同位置取色。所以它要求蒙版本身就是彩色的且色块分布和你想让词呈现的颜色对得上拿一张灰阶图它会给你一片灰。案例 06 换成了自定义颜色函数控制力最强import random def random_color(word, font_size, position, orientation, random_stateNone, **kwargs): 返回 HSL 颜色字符串hue 随机才能出彩色固定 0 只会是红色系 hue random.randint(0, 360) return hsl(%d, %d%%, %d%%) % (hue, random.randint(60, 80), random.randint(60, 80)) wc WordCloud( color_funcrandom_color, masknp.array(Image.open(data/color_mask.png)), font_pathfonts/cn_font.ttf, modeRGBA, background_colorNone, ).generate(text)函数签名这块有个细节教程里写的是显式列出font_path而 wordcloud 实际是以关键字参数调用的稳妥写法是用**kwargs兜住多余参数不然版本一变多传一个参数就直接 TypeError。返回值只要是hsl(...)、#rrggbb或英文颜色名都能被识别。另外这个函数会被调用几百次里面千万别留print教程原代码里有一行打印跑一次刷屏几百行把真正的日志全冲没了。写自定义颜色时的血泪经验是hue固定成 0 得到的是清一色红想配色统一又不单调可以按font_size分档大字一个色系、小字一个色系视觉层次立刻出来。3.4 案例 07extract_tags 与 generate_from_frequencies最后一个案例换了输入口径不再把整段文本丢给generate让它自己数词频而是先用 jieba 的 TF-IDF 接口算出关键词和权重再喂给generate_from_frequencies。这两条路径的区别在于“谁来决定哪个词重要”。# -*- coding: utf-8 -*- import jieba.analyse import numpy as np from PIL import Image from wordcloud import WordCloud, ImageColorGenerator with open(data/novel.txt, encodingutf-8) as f: text f.read() # topK 取前 200 个关键词withWeight 让返回值带上 TF-IDF 权重 pairs jieba.analyse.extract_tags(text, topK200, withWeightTrue) print(pairs[:20]) # 先看前 20 个确认关键词靠不靠谱 freq {w: float(weight) for w, weight in pairs if weight 0} mask np.array(Image.open(data/color_mask.png)) wc WordCloud( maskmask, font_pathfonts/cn_font.ttf, modeRGBA, background_colorNone, max_font_size140, random_state42, ).generate_from_frequencies(freq) wc.recolor(color_funcImageColorGenerator(mask)) wc.to_file(output/case07.png)extract_tags返回的是[(词, 权重), ...]列表generate_from_frequencies要的是{词: 权重}字典中间必须转一次。权重是浮点数float()转一下更保险过滤掉weight 0的项因为权重为 0 的词会被直接丢掉留着只是噪音。这条路径最大的好处是可控——你可以把业务词表塞进freq手动提权也可以把敏感词权重压到 0 实现过滤等于绕开了generate那套基于原始词频的黑匣子。代价是max_font_size必须显式给TF-IDF 权重分布比词频平缓得多不给上限的话最高频那个词和最低频的差距拉不开图会显得“每个词都差不多大”白瞎了词云的信息密度。4. 中文词云的三道关font_path、jieba 分词与停用词表英文词云的坑基本只有路径和参数中文要过的关多得多。跑不出图、出来是方块、出来全是“的了吗呢”这三类问题占了中文词云故障的绝大多数而且都能在写代码之前就规避掉。4.1 font_path不指定中文字体就是一堆方块WordCloud默认加载的是内置的英文字体中文字符在字库里找不到对应字形matplotlib 渲染时就会画成空心方块俗称豆腐块。这不是编码问题文件读进来是对的字符也在纯粹是字体缺字形。所以中文场景下font_path是必填项不是可选项。字体选择上有几个考虑一是必须支持中文常见的黑体、思源系、系统自带的微软雅黑/苹方都行二是体积别太大十几 MB 的字体加载会明显拖慢生成速度做服务端批量生成时更要把 WordCloud 对象复用起来别每个请求都重新加载字体三是注意授权商用项目里别随手把系统字体打进镜像。路径写法建议统一走项目相对路径并且用os.path拼避免在 Windows 上\和/混用踩坑import os FONT_PATH os.path.join(os.path.dirname(__file__), fonts, cn_font.ttf) assert os.path.exists(FONT_PATH), 字体文件不存在: %s % FONT_PATH这个assert看着多余实际能省掉大量排查时间。字体路径写错时 wordcloud 抛的异常信息是OSError: cannot open resource完全没提字体两个字新手很容易误判成图片读取失败。4.2 jieba 分词模式、自定义词典与词性过滤分词质量直接决定词云质量。默认词典对通用文本够用但项目里一旦出现产品名、人名、行业术语默认词典大概率切错比如把一个三字产品名切成“两字 单字”那张图上就永远看不到这个产品名。解决办法是加载自定义词典import jieba import jieba.analyse jieba.load_userdict(data/userdict.txt) # 每行一个词可带词频和词性 jieba.suggest_freq((数据, 看板), tuneTrue) # 强制成词tuneTrue 动态调整 # 只保留名词、动词、形容词过滤掉虚词和标点 pairs jieba.analyse.extract_tags(text, topK300, withWeightTrue, allowPOS(n, vn, v, a))load_userdict的词典文件格式是词语 [词频] [词性]词频和词性可省略suggest_freq用来强行让某几个字组合成一个词或者反过来拆开一个总被误合并的词。extract_tags的allowPOS参数是按词性白名单过滤这一步比堆停用词表更有效——中文里最高频的往往是“的、了、是、在”这类虚词和标点词性过滤能一次性干掉一大片剩下的停用词表只用来处理业务噪音比如“公司”“项目”“进行”这种谁都有、看不出信息的词。停用词表就是普通文本文件一行一个词读进 set 传给stopwords参数with open(data/stopwords.txt, encodingutf-8) as f: stopwords {line.strip() for line in f if line.strip()} wc WordCloud( font_pathFONT_PATH, stopwordsstopwords, # 这些词不参与排版 collocationsFalse, random_state42, )注意stopwords的匹配发生在切词之后是精确匹配所以表里写“的”能过滤单字“的”但对“的话”无效。另外传进去的必须是 set 或类似容器传 list 也能跑但查找是线性的几十万词规模下会有可感知的差异。4.3 透明背景、尺寸与输出的几个实用参数做 PPT 或看板时最常要的是透明背景 PNG参数组合是modeRGBA加background_colorNone两个都要缺一个就出不来透明效果。输出尺寸建议按最终用途定贴网页或文档 1200×800 够用要印刷或大屏就上 2400×1600同时把max_words和字号一起放大否则词虽然大了但数量少图会显得空。还有几个调观感的参数值得知道min_font_size控制最小字号默认 4词多的时候小字会挤成一团糊掉调到 8 到 10 会清爽很多prefer_horizontal控制横排词比例默认 0.9调到 0.5 竖排词变多密集感更强但可读性下降relative_scaling默认 0.5决定字号和词频的相关强度设成 0 就纯粹按排名给字号设成 1 则严格按频次比例中文长尾明显的时候调到 0.3 到 0.4 更均衡。这些参数没有标准答案靠试但试之前先把random_state固定住不然你改一个参数看到的差异里混着随机布局的干扰等于在玄学调参。5. 避坑与排查词云跑不出图时的 5 个高频故障下面这几条基本覆盖了第一次跑这份教程会撞上的问题每条都按“看到什么 → 为什么 → 怎么改”来写遇到时对着现象找就行。5.1 图里全是方块或者中文一个都不显示现象脚本正常跑完图片也生成了但画面里全是空心方块或者干脆只有零星几个英文词中文全不见了。原因没传font_path或者传的字体不含中文字形。wordcloud 用内置默认字体渲染中文字符找不到 glyph就画成方块某些情况下布局阶段直接把无法渲染的词丢弃于是中文整体消失。解决显式指定一个确认支持中文的 ttf/otf用os.path.exists断言在加载前校验一次跨平台部署时把字体文件放进项目一起分发别依赖系统字体路径。改完先跑一份只有十来个中文词的测试文本确认字体生效再上真实语料。5.2 OSError: cannot open resource现象启动就抛OSError: cannot open resource堆栈指向 PIL 的字体加载报错信息里没有任何“字体”字样。原因font_path或Image.open()的路径是相对路径而当前工作目录不是你以为的那个。在 IDE 里点运行工作目录通常是项目根在终端cd cases python case03.py工作目录就变成了 cases../xyj.txt能对上fonts/cn_font.ttf就对不上了。解决所有资源路径统一以脚本文件位置为基准拼用os.path.join(os.path.dirname(os.path.abspath(__file__)), ...)或者干脆在入口脚本里os.chdir到项目根。这个坑我踩过不止一次本质是把“相对路径”当成了“相对于脚本”实际它永远相对于进程的工作目录。5.3 generate_from_frequencies 报错或输出一片空白现象案例 07 里generate_from_frequencies(freq)抛ValueError或者跑完得到一张几乎空白的图只有角落几个词。原因三种可能。传进去的不是 dict 而是 list比如忘了把extract_tags的结果转成字典字典里所有 value 都是 0 或负数被全部过滤或者词太多但max_font_size没设上限权重分布太平导致每个词都被压到极小的字号。解决先print(len(freq), list(freq.items())[:5])确认数据类型和数值范围过滤掉非正权重显式给max_font_size同时把width/height调大给字号留空间。另外generate_from_frequencies不会做分词传进去的 key 必须已经是干净词条带空格或标点的 key 会原样排上去。5.4 透明背景导出后变成黑底现象代码里设了background_colorNoneplt.show()里看着是透明的保存出来的 png 却是黑底或白底。原因只设了background_colorNone但没设modeRGBA图像模式还是 RGB透明通道不存在None被当成黑色处理另一种情况是用plt.savefig保存而不是wc.to_filematplotlib 的 figure 默认有不透明 facecolor会盖掉透明通道。解决两个参数成对出现modeRGBAbackground_colorNone并且保存一定走wc.to_file()而不是截图式保存。存完用图片查看器或Image.open(...).mode检查一下返回RGBA才算真透明。5.5 蒙版形状不生效出来还是一整块矩形现象传了mask参数生成的图却还是铺满整个画布形状完全没体现。原因wordcloud 的规则是蒙版里白色区域不排字其余区域排字。如果蒙版是白底白形状、或者形状本身颜色太浅接近纯白等于整张图都是“不排字区域”布局算法只能在边缘找到零星空间看起来就是没形状或者干脆空白。解决把形状本体涂成深色黑或高饱和色背景保持纯白用np.array(img).shape确认是三通道彩色图而不是带 alpha 的四通道图四通道的透明区域有时会被当成有效区域。蒙版尺寸也别太大直接拿一张 4000×3000 的图当 mask布局计算量会成倍上涨缩到 800 到 1200 宽再喂进去效果一样但快得多。6. 二次开发把 7 个 demo 收敛成一个可复用的词云生成器七个案例跑通之后真正的活是把它们收成一个能反复调用的模块。我的做法是抽一个WordCloudBuilder把“中文字体、分词、停用词、蒙版、配色”这五件事固化成默认值业务侧只传文本和输出路径。这样后面接接口、接定时任务、接批量跑批都只是换个调用入口。# -*- coding: utf-8 -*- import os import jieba import jieba.analyse import numpy as np from PIL import Image from wordcloud import WordCloud, ImageColorGenerator BASE os.path.dirname(os.path.abspath(__file__)) class WordCloudBuilder: def __init__(self, font_pathNone, stopwordsNone, mask_pathNone): self.font_path font_path or os.path.join(BASE, fonts, cn_font.ttf) self.stopwords stopwords or set() self.mask np.array(Image.open(mask_path)) if mask_path else None def _cut(self, text, modetfidf, topk300): if mode tfidf: pairs jieba.analyse.extract_tags(text, topKtopk, withWeightTrue, allowPOS(n, vn, v, a)) return {w: float(v) for w, v in pairs if v 0} return .join(jieba.cut(text)) def build(self, text, out_path, modetfidf, **overrides): params dict(font_pathself.font_path, stopwordsself.stopwords, maskself.mask, modeRGBA, background_colorNone, max_words300, max_font_size140, random_state42) params.update(overrides) # 业务侧按需覆盖 wc WordCloud(**params) data self._cut(text, modemode) wc wc.generate_from_frequencies(data) if isinstance(data, dict) else wc.generate(data) if self.mask is not None: wc.recolor(color_funcImageColorGenerator(self.mask)) wc.to_file(out_path) return out_path这段的关键设计是把“分词口径”做成开关modetfidf走关键词加权适合报告和摘要图modefreq走原始词频适合看整体语料分布。overrides用dict.update兜住业务侧想改字号、改random_state、改透明背景都不用动类本身。验证方式我一般分三步先用十来个词的假文本跑一遍确认字体和蒙版生效再用真实语料跑一遍把print出来的前 20 个关键词肉眼过一遍最后固定random_state连跑两次比对输出文件的 md5两次不一致就说明有随机源没锁住。改造点改之前改之后调用方式七份脚本各跑各的一个类build(text, out)中文字体每个案例手写路径构造时固定支持覆盖分词口径分散在各脚本mode参数二选一输出控制脚本内写死文件名由调用方传路径便于批量还要提醒一点批量生成时别在循环里反复WordCloud()字体加载和布局初始化都不便宜一个 builder 实例复用着跑几百份文本耗时会差出一个量级。从那以后我每次往生产环境塞词云逻辑都强制先用固定random_state跑一遍对比测试确认“同样的输入出同样的图”才敢上线——这东西看着是装饰性功能但报告里的图前后不一致被人截图对比时就很难解释。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?