Pyecharts词云图实战:中文分词、停用词与参数调优

发布时间:2026/10/31 18:24:26
Pyecharts词云图实战:中文分词、停用词与参数调优

做内容运营和数据分析这行的朋友,多半都遇到过这样的场景:手里攥着几千条用户反馈、几百页会议记录、一整年的社群聊天文本,领导要你"十分钟内告诉我大家到底在聊什么"。表格列不出来,饼图表达不了语义,这时候一张词云图往大屏上一放,谁的词最大谁就是重点,沟通成本瞬间降到最低。我用 Pyecharts 做词云图做了不下一百次,从最早只会抄官方示例、动不动渲染出一片方块字,到后来能把形状、字号区间、旋转角度、配色、输出方式全部拿捏住,中间踩的坑够写一篇长文。这篇就围绕 Pyecharts 的词云图模块,把参数一个一个拆开讲清楚,把文本预处理这条容易被忽略的链路补全,最后给一份我自己在用的、改改就能跑的完整脚本。不管你是刚接触数据可视化、只会画柱状图的新手,还是已经在用 ECharts 生态做看板的老手,下面这些内容都能直接拿去用。

1. 词云图项目的整体设计与方案选型

1.1 词云图到底解决什么问题,什么时候不该用它

先把定位说清楚。词云图的本质是词频的视觉编码:词的面积大小映射出现次数或权重,位置和颜色是次要装饰。它擅长的是"一眼看出高频主题",不擅长的是"精确比较两个词的差距"。你让读者看"退款 120 次"和"包装 98 次"哪个多,词云图是比不过柱状图的。所以我的判断标准很粗暴:如果目的是汇报开场、大屏展示、报告封面、快速探路,词云图非常合适;如果目的是精确排名、要做决策依据,那词云图只配当配图,主力还得是条形图或者表格。

在实际项目里,我通常把词云图放在分析链路的最前端,当成"探索性工具"用。拿到一批陌生文本,先分词统计,画一张词云,扫一眼就知道里面有没有脏数据、有没有把无意义的高频词(比如"然后""就是说""这个")顶到最前面。这一步做完,才轮到后面的分类、情感分析、主题聚类。很多新手直接跳过这一步,把原始文本一股脑喂给词云,结果出来的图里"的""了""是"三个字占满屏幕,然后就开始怀疑是库有问题——其实问题在数据,不在工具。

至于为什么选 Pyecharts 而不是别的方案,理由很实际:它输出的 HTML 是可交互的,鼠标悬停能看到每个词的具体数值,这在给业务方演示的时候体验差距巨大;它天然跟 ECharts 生态打通,你后面要往看板里嵌、要跟其他图表联动,都不用重写;Python 侧写代码,跟分词、统计、清洗这些步骤在同一个脚本里,不用来回导数据。代价也有,就是它对静态图片输出的支持不如一些纯 Python 绘图库直接,想导 PNG 要额外折腾,这点后面会专门讲。

1.2 主流词云方案的横向对比

我把常见的几种做法列个表,方便你按自己的场景挑。这张表不是要分出高下,而是说明每种方案最适合的边界在哪。

方案交互能力中文字体处理形状遮罩输出形式适合场景
Pyecharts强,悬停提示、点击事件需显式指定字体名支持内置形状与自定义图片HTML、可导出图片网页看板、演示汇报、需要交互
纯 Python 绘图库方案弱,基本是静态图需指定字体文件路径支持,靠遮罩图实现PNG、JPG、SVG论文配图、静态报告、印刷
前端图表库直写强由浏览器字体决定支持浏览器渲染前端主导的项目
在线生成工具中通常已内置支持,但受限下载图片临时出图、非技术同事应急

从表里能看出一个关键差异:Pyecharts 的词云图最终是在浏览器里用 canvas 画出来的,这意味着字体的可用性取决于浏览器能加载到什么字体,而不是 Python 环境里装了什么字体。这个认知非常关键,第 2 节和第二部分的字体坑全部源于此。反过来,纯 Python 绘图方案是在服务端用字体文件渲染,字体问题在手上一把抓,但交互性基本为零。

我自己的取舍是:内部探索、临时分析用纯 Python 方案出图快;对外汇报、嵌看板一律用 Pyecharts。两条腿走路,不纠结。

1.3 从原始文本到可交互 HTML 的完整链路

很多人以为做词云就是"调一个 add 函数",实际上完整链路有六步,每一步都会影响最终效果:

  1. 文本采集与清洗:去掉 HTML 标签、URL、表情符号、重复行、无意义的客服话术模板。
  2. 中文分词:用 jieba 之类的分词工具切词,这一步决定了"用户体验"会不会被切成"用户"和"体验"。
  3. 停用词过滤:把"的、了、是、我们、这个、然后"这类词剔掉,同时要针对业务补一份领域停用词,比如电商场景要屏蔽"亲""宝贝""下单"以外的无意义寒暄。
  4. 词频统计与权重归一化:统计每个词的次数,必要时做 TF-IDF 或者对数压缩,避免头部词一家独大。
  5. 参数配置与渲染:把词频字典喂给词云组件,配置形状、字号区间、旋转、配色。
  6. 输出与分发:渲染成 HTML,或者截图成 PNG 嵌进 PPT。

这六步里,我的经验是第 2、3 步决定了成图质量的 70%,第 5 步只占 20%,剩下的 10% 是配色审美。但绝大多数教程一上来就讲第 5 步,把前两步一笔带过,所以出来的一堆丑图。这篇文章的结构,我会先把工具链和环境讲透,再讲参数,最后回头把预处理这块补齐,因为这个顺序才是真正上手时不返工的顺序。

2. 环境准备与中文字体这个绕不过去的坑

2.1 依赖安装与版本对齐

依赖不多,两个就够起步:

pip install pyecharts jieba

但版本这件事必须说清楚。Pyecharts 在 1.x 和 2.x 之间做过一轮不小的调整,词云组件的部分能力(比如自定义图片遮罩)是在 1.x 后期才补齐的,网上大量教程写于 1.5 之前的年代,你照着抄很可能报unexpected keyword argument。我现在统一用 2.x,安装后确认一下版本:

pip show pyecharts | grep Version

另外,词云图在 Pyecharts 里是扩展包的一部分,早期版本需要额外pip install pyecharts-jupyter-installer或者手动引入扩展,2.x 里这个组件已经内置,直接from pyecharts.charts import WordCloud就能用。如果你跑出ModuleNotFoundError,八成是版本太老,升级一下就好。

还有一个坑值得提前说:在 Jupyter 里渲染和生成独立 HTML 文件,表现可能不一样。Jupyter 里是 inline 渲染,容器宽度受限,你设的width="1200px"可能被压缩;生成 HTML 后打开是完整画布。我调试的时候习惯直接render成 HTML 用浏览器看,因为那才是最终交付给别人的样子。

注意:如果你的运行环境是无图形界面的服务器,直接 render 成 HTML 完全没问题,不需要任何浏览器;只有想导出 PNG 时才需要浏览器内核,这一点后面单独讲。

2.2 中文字体配置的三种做法与取舍

这是新手翻车率最高的地方,症状很统一:图是出来了,但所有中文都是方框、问号,或者干脆整张图空白。

原因:词云图由 ECharts 在浏览器端用 canvas 绘制,font_family传的是标准的 CSS 字体族名。如果这个字体名在当前浏览器里找不到,就会回退到默认字体,而不支持中文的默认字体就画不出字来。这跟你在 Python 里装没装字体库没有直接关系。

三种处理方式,按可靠性从低到高排:

第一种,直接用系统通用字体名。比如传font_family="SimHei"或者font_family="Microsoft YaHei"。大部分 Windows 浏览器都能命中,但一旦换了 Mac、Linux,或者用户在系统里没有这个字体,就废了。临时调试可以用,正式项目别用。

第二种,用字体族回退列表。CSS 支持逗号分隔的字体族,命不中第一个就往下找。传font_family="SimHei, Heiti SC, Source Han Sans SC, sans-serif",覆盖面和稳定性比单一字体名好得多,这是我在大多数项目里的默认做法。

第三种,页面里内嵌字体。把字体文件转成 base64 写进 HTML,或者给页面加@font-face。这种做法最稳,因为字体跟着页面走,但也最折腾,会让 HTML 体积从几百 KB 涨到几 MB。只有在交付对象完全不可控(比如要发给一堆外部客户看)的时候才值得这么做。

实操心得:判断字体到底有没有生效,有个笨但有效的办法——先把字号区间调大,比如[40, 120],只放五六个词,渲染出来看。字号大了之后字体渲染细节更明显,有没有回退一眼就能看出来。别在几十个词的小字里找问题。

另外补一个细节:font_weight参数在词云里影响很大。中文笔画多,不加粗的话小字号会糊成一团。我一般设font_weight="bold",代价是字形变粗后词与词之间容易挤,需要把word_gap适当调大配平。

2.3 项目目录与数据准备的建议结构

顺手说一句工程习惯,这不算技术点但很省事。我一般这么组织:

wordcloud_demo/ ├── data/ │ ├── raw_text.txt # 原始文本 │ └── stopwords.txt # 停用词表 ├── output/ │ └── wordcloud.html # 渲染结果 └── build_wordcloud.py # 主脚本

停用词表可以自己攒,也可以直接找一份公开的通用中文停用词表打底,然后在项目里追加业务词。我强烈建议把停用词表落成文件而不是写死在代码里,原因很简单:业务方每次看完图都会说"这个词能不能去掉",写死在代码里改一次跑一次,落成文件只需要往里面加一行,改完直接重跑,不用碰代码逻辑。

3. 词云组件核心参数逐项拆解

这一节是重点。我按"影响最大、最常调"的顺序来排,每个参数都说明它到底改了什么、什么时候该动它。

3.1 形状、尺寸与边界类参数

shape决定词云的整体轮廓。内置的几档取值记忆成本不高:circle(圆)、cardioid(心形)、diamond(菱形)、triangle-forward(正三角)、triangle(倒三角)、pentagon(五边形)、star(星形)。默认是circle。形状选择上没有技术门槛,纯粹看场景:做年度总结用star有仪式感,做健康类内容用cardioid讨巧,做中性数据汇报就老老实实circle,因为圆形边界最规整,跟周围的图表卡片最容易对齐。

width和height决定画布尺寸,默认是800px和500px。这里有个实操经验:画布尺寸和字号区间必须一起调,不能单独动。你如果只把字号从[12, 60]提到[30, 100],画布还是 800×500,结果就是词根本排不下,组件会自动丢弃一部分词,最后图里只剩十几个大字,你还会以为数据出了问题。反过来,画布很大但字号很小,图会显得空旷稀疏。我常用的组合是1000×600配[14, 70],或者1200×700配[16, 80],视觉密度比较舒服。

word_gap是词与词之间的最小间隙,默认 20。这个参数调小的效果是"更挤更满",调大是"更透气但可能放不下"。当词数量在 200 个以上时,我会把它压到 8~12,让画面更饱满;词少的时候(30 个以内)反而要加大到 30 左右,避免几个大字孤零零地飘着。

pos_left、pos_top、pos_right、pos_bottom是画布内的定位偏移,支持像素和百分比。用途主要是微调,比如嵌进卡片时留白不对称,用pos_top="5%"挪一下。这几个参数一旦设了,画布尺寸的表现可能跟你想的不一样,建议先不设,默认居中,需要的时候再补。

is_draw_out_of_bound这个参数容易被忽略,它控制是否允许词画出画布边界,默认false。除非你在做某种刻意的出血效果,否则保持默认,开了之后边缘的词会被裁掉一半,很难看。

3.2 词频权重与视觉映射参数

data_pair是数据入口,格式是[(词, 权重), ...]的列表,或者等价的字典转列表。这里有个必须说清的点:权重不一定是词频。你可以传原始次数,也可以传 TF-IDF 值,还可以传经过对数压缩的数值。传什么,直接决定视觉上谁最突出。

我遇到过一种典型情况:头部一个词出现 3000 次,第二名只有 400 次,直接传原始值的结果是第一名大到撑满屏幕,其余几十个词全缩成蚂蚁。解决办法有两个,一是做对数压缩math.log(count + 1),二是做截断min(count, 800)。对数压缩更平滑,我一般用这个:

import math weighted = [(word, round(math.log(cnt + 1) * 10, 1)) for word, cnt in counter.most_common(300)]

乘 10 是为了把数值拉回一个好看的量级,不然算出来的都是 2.0、3.5 这种,视觉上区分度不够。这只是个手感系数,你可以按数据自己调。

word_size_range是字号区间,格式[min, max],默认[12, 60]。它是按权重线性映射到区间的,所以刚才说的权重归一化直接决定最终的观感。:这个参数的两个极端要避开:最小值低于 10 时,中文小字基本糊成黑点,看不清还占地方;最大值超过画布高度的 40% 时,最大的那个词会把整张图撑变形。我一般控制max在画布高度的 12%~15%。

rotate_step控制旋转角度的步长,默认 45,意味着可选角度是-90、-45、0、45、90。设成 90 就只剩横竖两个方向,设成 0 就是全部水平。这里有个审美经验:中文词云建议控制在两个方向以内。中文是方块字,斜着排的时候阅读成本极高,斜词一多整张图就有"乱码感"。我现在基本都设rotate_step=90,保留横竖交错,既有节奏又不难读。如果是英文词云,45反而更好看,因为拉丁字母本身的朝向感弱。

3.3 样式、提示与交互类参数

textstyle_opts用opts.TextStyleOpts(...)构造,能设font_family、font_size、font_weight、color、font_style。这里有个巨坑:一旦你在textstyle_opts里显式设了color,所有词就变成同一个颜色,ECharts 默认的随机配色会被覆盖。想保留多彩效果,就不要在textstyle_opts里设color,而是通过color参数传一个颜色列表让组件自己去分配:

color="#5470c6" # 这种写法在某些版本是单色

准确的做法是用列表形式给一组颜色,让组件循环取用。我的常用配色是一组低饱和度的蓝绿橙紫,避免大红大绿,因为词云颜色一花,可读性断崖下跌。

tooltip_opts控制悬停提示。默认已经有了,显示词和数值。我一般会加一个格式化模板,把数值加上单位,比如"显示次数:{c}",业务方看起来更有信息量。这个细节不起眼,但汇报的时候能省掉一堆"这个数字是啥意思"的解释。

emphasis_textstyle_opts是悬停高亮样式,可以设高亮时的文字颜色或者阴影。我一般只改颜色,加阴影会让画面变脏。

还有两个跟交互相关的点:word_click事件(点某个词触发回调)在纯 HTML 输出里用不上,需要配合前端代码;is_selected这类选中态参数在词云里作用有限。新手不用在这上面花时间。

我把所有参数整理成一张速查表:

参数默认值作用我的常用值调整信号
shapecircle整体轮廓形状circle / star按汇报调性选
width / height800 / 500画布尺寸1000 / 600词多就加大
word_gap20词间最小间隙8~12(词多)/ 30(词少)画面太挤或太空
word_size_range[12, 60]字号映射区间[14, 70]配合画布一起调
rotate_step45旋转角度步长90中文避免斜排
is_draw_out_of_boundfalse是否越界绘制false一般不动
textstyle_opts无字体、字重、颜色字体族 + bold中文必设字体
tooltip_opts无悬停提示格式加单位文案面向非技术读者

4. 文本预处理:成图质量的分水岭

参数调得再花,数据脏了就是白搭。这一节讲的是我在每个词云项目里都要重做一遍的流程,也是新手最容易跳过、跳过后最容易被业务方怼回来的部分。

4.1 中文分词与停用词的两层设计

中文不像英文有天然的空格分隔,必须分词。常用的是 jieba,jieba.lcut(text)返回列表。它的三种模式各有用途:精确模式适合通用文本,全模式会把所有可能的词都切出来(适合做词典挖掘,不适合做词云),搜索引擎模式会在精确模式基础上对长词再切一刀,适合短文本。

分词之后必然要过滤。我把停用词分成两层:

第一层是通用停用词,包括虚词、代词、连词、语气词,就是那种在任何文本里都高频但毫无信息量的词。这一层可以找一份公开的通用表打底,几百上千个词,一次准备好长期复用。

第二层是领域停用词,这一层才是真功夫。不同业务里"看起来是实词但没意义"的词完全不同。比如做社群运营的聊天记录分析,你会发现"哈哈""哈哈哈""收到""好的""在吗"能占掉前三名;做产品反馈分析,"用户""功能""感觉""觉得"这类词会顶上来;做客服工单分析,"咨询""反馈""问题""处理"全是没有区分度的模板词。这一层必须一个项目一个项目地攒,攒下来的东西就是你自己的资产。

我的做法是在脚本里维护一个EXTRA_STOPWORDS集合,边看图边往里加,第一版图出来之后至少迭代两轮。这个过程听起来笨,但效率其实很高,因为你看图就知道该删什么。

再说一个容易被忽略的过滤维度:长度过滤。单字词在中文里绝大多数没有独立语义("的""了""是"是虚词,"车""房""钱"这种实词虽然是单字但有意义)。我的做法是单字词只保留白名单里的实词,其余直接丢。双字词和三字词是主力,超过六个字的短语考虑截断或者丢弃,因为太长的词在词云里字号一大就撑爆版面。

4.2 词频统计、权重压缩与词数控制

统计用collections.Counter最省事,Counter(words).most_common(n)直接拿到排序好的前 n 个。

这里的关键决策是保留多少个词。给几个我实测的参考:

画布尺寸字号区间建议词数说明
800×500[12, 60]60~100超过 120 会明显拥挤
1000×600[14, 70]100~180常用配置,平衡度最好
1200×700[16, 80]150~250大屏展示用
1600×900[20, 100]250~400大屏或海报,小字号要慎用

有个反直觉的现象:词数不是越多越好。词数超过一定量之后,新增的词只能挤在边角用最小字号呈现,对读者来说等于噪音,还会拉低整体的信息浓度。我见过不少人为了"显得数据量大"硬塞五百个词,结果图上除了头部十个词之外全是看不清的小字,反而比只放一百个词显得粗糙。

权重压缩前面提了对数方案,这里补一个替代思路:分级映射。把词按频率分成三档(头部、腰部、尾部),分别映射到不同的字号区间,比如头部[50, 80]、腰部[25, 49]、尾部[14, 24]。这种做法的好处是可控性强,缺点是分档阈值要靠经验。数据分布特别长尾的时候我更倾向用这个,因为对数压缩还是会让头部词偏大。

4.3 从原始文本到词频字典的完整代码

把上面几步串起来:

import re import math from collections import Counter import jieba # 1. 读原始文本 with open("data/raw_text.txt", encoding="utf-8") as f: raw = f.read() # 2. 基础清洗:去 URL、去英文、去多余符号与空白 text = re.sub(r"https?://\S+", " ", raw) text = re.sub(r"[a-zA-Z]+", " ", text) text = re.sub(r"[\s\d\p{P}]+", " ", text) if False else re.sub(r"[^\u4e00-\u9fa5]+", " ", text) text = re.sub(r"\s+", " ", text).strip() # 3. 分词 words = jieba.lcut(text) # 4. 停用词双层过滤 with open("data/stopwords.txt", encoding="utf-8") as f: base_stop = {line.strip() for line in f if line.strip()} EXTRA_STOPWORDS = { "哈哈", "哈哈哈", "收到", "好的", "在吗", "用户", "功能", "感觉", "觉得", "问题", "咨询", "反馈", "处理", "这个", "然后", } SINGLE_CHAR_WHITELIST = {"码", "价", "货", "店", "券", "单"} cleaned = [] for w in words: w = w.strip() if not w or w in base_stop or w in EXTRA_STOPWORDS: continue if len(w) == 1 and w not in SINGLE_CHAR_WHITELIST: continue if len(w) > 8: continue cleaned.append(w) # 5. 统计 + 对数压缩 + 取前 N counter = Counter(cleaned) TOP_N = 150 data_pair = [ (word, round(math.log(cnt + 1) * 10, 1)) for word, cnt in counter.most_common(TOP_N) ] print(f"有效词数: {len(cleaned)}, 去重后: {len(counter)}, 入图: {len(data_pair)}") print(data_pair[:10])

这段代码可以直接跑,改文件路径就行。注意里面那行text = re.sub(...) if False else ...是我故意留的痕迹,实际用的时候删掉前半段,只保留re.sub(r"[^\u4e00-\u9fa5]+", " ", text)这一句,意思是只保留中文字符,其他全部转空格。这个正则很好用,中文字符的 Unicode 区间就是\u4e00-\u9fa5,一句话把半角符号、数字、英文、表情全部清掉。

注意事项:清洗阶段不要过度清洗。比如做技术社区文本分析,你如果只保留中文,那"API""SDK""Bug"这些高频关键术语全没了,图就没意义了。这时候要按业务调整正则,保留字母和数字。清洗规则永远是为分析目标服务的,没有通用答案。

5. 完整实战:一份可复用的词云生成脚本

5.1 脚本主体与关键配置说明

下面是我自己常用的模板,配置分成"常改"和"少改"两块,方便复用:

from pyecharts import options as opts from pyecharts.charts import WordCloud def build_wordcloud(data_pair, title="高频词云", shape="circle", width="1000px", height="600px", size_range=(14, 70), gap=10): wc = ( WordCloud(init_opts=opts.InitOpts(width=width, height=height)) .add( series_name="词频", data_pair=data_pair, shape=shape, word_gap=gap, word_size_range=list(size_range), rotate_step=90, is_draw_out_of_bound=False, textstyle_opts=opts.TextStyleOpts( font_family="SimHei, Heiti SC, Source Han Sans SC, sans-serif", font_weight="bold", ), tooltip_opts=opts.TooltipOpts( is_show=True, formatter="{b}:权重 {c}", ), emphasis_textstyle_opts=opts.TextStyleOpts( color="#d94e5d", ), ) .set_global_opts( title_opts=opts.TitleOpts( title=title, pos_left="center", pos_top="2%", title_textstyle_opts=opts.TextStyleOpts(font_size=18), ), legend_opts=opts.LegendOpts(is_show=False), ) ) return wc if __name__ == "__main__": wc = build_wordcloud( data_pair=data_pair, title="某社群一个月的发言高频词", shape="circle", width="1100px", height="650px", size_range=(14, 72), gap=10, ) wc.render("output/wordcloud.html") print("已输出 output/wordcloud.html")

几个设计上的考虑解释一下:

把配置封装成函数而不是写一串链式调用,理由是同一份数据你往往要出好几版图(不同形状、不同字号),封装之后改参数就行,不用复制粘贴。我最早的时候每改一版就复制一段代码,后来文件里堆了七八份几乎一样的调用,改一个错误要改八遍,血的教训。

width和height用的是带 px 的字符串,InitOpts里也可以传width=1100这种整数,但显式带单位更保险,避免某些版本解析歧义。

legend_opts关掉,因为单系列词云不需要图例,留着反而占位置,还会跟标题挤在一起。

rotate_step=90,中文场景的默认选择,理由前面讲过。

5.2 输出 HTML 与导出图片的现实方案

render()出来的是完整 HTML 文件,双击就能在浏览器看,可以直接丢进任何网页项目里用iframe嵌,也可以把里面的配置项抠出来塞进前端工程。

如果你需要 PNG,情况就复杂一点。Pyecharts 早期有一套快照机制,后来独立成包,需要额外装依赖并且依赖浏览器内核(无头浏览器)。流程大致是:

pip install snapshot-selenium
from pyecharts.render import make_snapshot from snapshot_selenium import snapshot make_snapshot(snapshot, wc.render(), "output/wordcloud.png")

这条路能走通,但对环境有要求:需要有浏览器驱动,服务器上没有图形界面时还要配无头模式。我在本地开发机跑得很顺,部署到容器里踩过好几次坑,主要是驱动版本和浏览器版本对不上。

所以我的实际建议是:如果只是偶尔要一张图,直接在浏览器里打开 HTML,用开发者工具把画布右键导出,或者截图,五分钟搞定,别折腾自动化。如果是批量出图上百张,再考虑走无头浏览器的方案,并且把版本锁死。

实操心得:截图前把浏览器窗口调到和画布尺寸一致,缩放调到 100%,不然截出来的图会被浏览器缩放插值糊掉。这个细节我在第一次给客户交付的时候栽过,图糊得像打了马赛克,被要求重出。

6. 常见问题与排查速查

这一节把我在实操中反复遇到的现象整理成表,按"症状 → 可能原因 → 处理方式"组织,遇到问题直接查表。

6.1 渲染与显示类问题

症状可能原因处理方式
中文全是方块或问号浏览器找不到指定字体换成字体族回退列表;或调大字号验证
整张图空白数据为空,或全部被停用词过滤掉了打印清洗后的词数,确认非空
只显示几个词词数少、字号过大、画布太小降字号下限、加画布、给足词数
词挤成一团word_gap太小、画布偏小加大 gap 或画布
HTML 打开后是源码用了render之外的错误方法确认用的是render或render_notebook
形状设了没效果同时设了图片遮罩,遮罩优先级更高二者只留一个

关于空白图,我有个特别好用的排查顺序:先打印len(data_pair),如果是 0,问题在预处理;如果大于 0 但图还是空,问题在渲染。这一步能把排查范围直接砍掉一半。

再补一个隐蔽的坑:文字里有不可见字符。从网页复制的文本经常带着零宽字符、全角空格、不换行空格,这些字符出现在词里,画出来就是空白或者错位。清洗的时候加一句把\u200b、\u00a0、\u3000这类字符替换掉,能省掉很多莫名其妙的调试时间。

6.2 数据与效果类问题

第一个典型问题:无意义高频词霸屏。处理逻辑是迭代加停用词。我一般会先出图,把前三名截图,问业务方"这几个词有意义吗",没意义就进黑名单,重跑。这个循环通常两轮就收敛。

第二个典型问题:头部词过于突出,长尾全挤没了。对数压缩或者分级映射,前面讲过。另一个偏门技巧是给头部词手动降权:某些平台名称、品牌名天然高频,但分析价值低,可以直接在统计结果里把它的计数乘 0.3。

第三个典型问题:词云里出现明显的错别字或者碎片词。这是分词粒度的问题。jieba 自定义词典能解决一部分——把业务专有名词(产品名、功能名、行业术语)加进用户词典,分词就不会把它们切碎。用法是:

jieba.load_userdict("data/userdict.txt")

文件格式是每行"词 词频 词性",词频和词性可以省略,只写词也行。这一步对专业领域文本的效果提升非常明显,值得花十分钟整理。

第四个问题:同一批数据,两次跑出来的图不一样。词云布局算法里有随机性,词的位置每次都会变,这是正常的。但如果你要求完全可复现,把数据顺序固定下来(most_common本身就是稳定的),位置差异接受就好。

6.3 性能与体量类问题

词数上千的时候,浏览器渲染会明显卡顿,尤其是切换形状、调整窗口大小的时候会重排。我的做法是入图词数永远控制在 300 以内,剩下的词用表格或者条形图补充。词云图的定位是"氛围图",不是"完整数据表",没必要把所有词都塞进去。

HTML 体积也值得留意。默认输出的 HTML 已经包含了 ECharts 的完整运行时,几百 KB 起步,词多、配置复杂的话会更大。如果要把图嵌进移动端页面,建议只导出配置项 JSON,前端按需加载,别把整个 HTML 塞过去。

还有一个细节:画布尺寸和容器尺寸不匹配。你设了1200px宽,但嵌在只有 800px 宽的卡片里,会出现横向滚动条或者被裁切。要么改画布尺寸,要么在容器上用自适应缩放,别让用户去拖滚动条看一张图。

7. 一些让我少走弯路的判断经验

做到这一步,工具层面的东西基本齐了。最后分享几个纯经验性的判断,都是踩过之后才明白的。

关于形状的克制。刚开始做词云的时候,我特别喜欢用各种形状,心形、星形换着来,觉得炫。后来发现,形状越花,可用的有效面积越小,词的排布越难做,图的信息密度越低。现在除了明确的主题场景(年度总结、节日活动),我一律用圆形。圆形没有审美风险,也最容易被其他图表包围。

关于配色的克制。ECharts 默认配色其实相当能打,我这几年改来改去,最后发现默认的调色板在可读性上就是最优解之一。真正需要定制配色的场景只有一种:要和品牌视觉体系对齐。其余情况,别折腾。

关于字体的统一。如果一个页面上有多张图,字体必须统一。我遇到过一张词云用黑体、旁边柱状图用宋体的页面,观感非常业余。定一个字体族列表,全站复用,这是最省事也最显专业的做法。

关于数据的故事性。词云图最容易犯的错是"只给图不给结论"。你把图放上去,领导看到的是"一堆词",你想表达的是"用户在集中吐槽物流"。所以要配一句话结论,比如"本月反馈中,物流相关词频环比上升明显,其中'配送''时效'首次进入前五"。图是证据,结论才是交付物。这一点跟工具无关,但决定你做的东西有没有价值。

关于复用的边界。我现在所有项目的词云脚本都是从同一个模板改出来的,改的只有三处:数据来源、停用词表、标题。参数部分几乎不动。这意味着我对这套参数的信任是经过几十次验证的。如果你的项目还在调参阶段,先别急着封装成通用组件,等参数稳定了再抽,过早抽象反而增加维护负担。

顺带说一个后续可以扩展的方向:如果词云要定期更新(比如每周一份社群周报),把整个流程做成脚本 + 定时任务就很自然,输出目录按日期分文件夹,历史版本自动留存。再往前一步,把词频变化做成时间序列,就能看出哪些词在升温哪些在降温,那已经是另一个层级的分析价值了。我在几个长期项目里做过这个,从"每周一张图"变成"每周一张图 + 一份词频变动榜",业务方的使用频率翻了好几倍。