2026/10/6 4:11:45

Matplotlib中文乱码问题全解:字体机制与跨平台配置指南

Matplotlib中文乱码问题全解:字体机制与跨平台配置指南 画了半小时图最后发现标题、坐标轴标签、图例里的中文全变成了一个个小方框这种“Matplotlib中文乱码”问题估计凡是做Python数据可视化的人都被折磨过。我最早被它坑是在一个项目里做季度销售数据汇总明明数据、逻辑都对结果跑出来的折线图里“销售额”“增长率”这几个字全部变成豆腐块当时就感觉整个人都不好了。网上搜这个问题的方案基本一抓一大把但大多数只给了两句代码根本不解释为什么这样能解决换台电脑、换个系统就失灵。这篇文章我不打算只丢个代码片段而是把字体机制、排查链路、不同系统的配置差异全部讲透适配从新手到需要部署到服务器的所有人。1. 中文乱码的根本原因不是编码是字体缺失1.1 先分清是图内乱码还是终端乱码很多人把两件事混在一起修越修越乱。图里的中文变成方框、豆腐块□□□这是Matplotlib的字体渲染问题而print(中文)在终端输出乱码、或者Jupyter里报一堆UnicodeDecodeError那是编码问题两者完全是两条路。怎么快速区分直接在Python里执行import matplotlib.pyplot as plt plt.plot([1, 2, 3], [1, 4, 9]) plt.xlabel(中文标签) plt.savefig(test.png)然后打开test.png看。如果图里文字正常那就别折腾Matplotlib了你看到的是终端或者编辑器显示编码的问题比如VSCode的terminal编码、Windows cmd的代码页。只有图片里真的出现方框、问号、乱码形状才是我们今天要解决的正文。1.2 Matplotlib默认字体列表里根本没有中文字体这是整个问题的根。Matplotlib默认使用的字体族是DejaVu Sans这是一套开源字体胜在跨平台、体积适中、覆盖拉丁字符全面但它从一开始就没包含中日韩CJK字符。你让Matplotlib用一套根本不含汉字的字体去画图它只能给每个中文字符渲染一个空方框或者notdef形状这就是乱码的真相。字体渲染的流程其实很简单Matplotlib内部有一套字体搜索机制先从font.family确定字体族再从font.sans-serif这个候选列表里挨个找可用的字体文件找到的字体被用于绘制。如果候选列表里的每一个字体都不包含中文字形最终就回退到DejaVu Sans然后产出方框。所以网上的通用解法“plt.rcParams[font.sans-serif] [SimHei]”思路是对的但有一个致命前提你的系统里必须真的装了SimHei且Matplotlib能找到它。在Linux服务器上跑这套代码大概率一点用都没有因为服务器上压根没装中文字体。1.3 乱码形态也是一条排查线索不同乱码形态对应不同原因这一点特别实用。出现整齐的方框说明字体渲染已执行但字体缺字出现问号、黑点甚至斜杠符号有可能是终端显示时按ASCII处理了出现全部文字堆成一长条或者方向颠倒则是字体方向信息不匹配还有一种是文字能显示但样式怪怪的、粗细不对这往往是把中文映射到了一个不含中文的字体。下次看到乱码先拍个照再根据形态判断能省一大半排查时间。2. 一套流程解决配置查字体、装字体、配字体2.1 先看看你的Matplotlib到底能找到哪些字体配置之前先做一次健康检查。运行下面这段代码把输出结果存下来from matplotlib import font_manager # 列出所有Matplotlib能找到的字体名 fonts sorted(set(f.name for f in font_manager.fontManager.ttflist)) for name in fonts: print(name)如果你在Windows上一般能看到SimHei、Microsoft YaHei、SimSun这些在Linux服务器上列表里往往只有DejaVu Sans、DejaVu Serif、Liberation之类的西方字体一个中文都没有。这一步能直接告诉你问题出在“系统没装字体”还是“装了但没配置”。也可以用单个字体快速验证from matplotlib import font_manager # 返回字体路径如果找不到会抛异常 print(font_manager.findfont(SimHei))如果输出/usr/share/fonts/...之类路径说明字体可用如果是警告加DejaVu Sans路径说明根本没找到后续配置SimHei就是空谈。2.2 装字体的两种情况先在系统里补齐检查完之后分情况处理。Windows和macOS本来就有大量中文字体重点不是“装字体”而是“指定字体名”。Linux服务器则经常需要手动安装。线上最省事的方式是安装Noto Sans CJK这套字体是Google和Adobe联合做的开源中文字体覆盖简繁日韩Matplotlib直接支持。Ubuntu/Debian上执行sudo apt install -y fonts-noto-cjk fc-cache -fvCentOS/RHEL系则是sudo yum install -y google-noto-sans-cjk-fonts fc-cache -fv没有root权限的极端情况也可以直接往用户目录塞字体。把Windows的C:\Windows\Fonts\simhei.ttf拷出来上传到Linux的~/.fonts/目录没有就创建然后fc-cache -fv刷新。这种方式依赖字体文件的授权合规性建议生产环境还是用开源协议字体更稳妥个人测试倒是无所谓。2.3 配置的核心一条rcParams 一个unicode_minus配置核心就两行我先把结论丢出来再解释原理import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [Noto Sans CJK SC, SimHei, Microsoft YaHei] plt.rcParams[axes.unicode_minus] False第一行是把中文字体加入候选列表并置于最前。font.family保持默认的sans-serif不变改的是sans-serif族下的具体字体顺序。第二行解决的是负号问题后面专门讲。有人习惯写成plt.rcParams[font.family] SimHei这样也能出效果但副作用是整体字体会全走这一种字体而Matplotlib内部很多数学符号渲染依赖专门的字体栈直接改family可能会引发奇怪的回退和警告。我更推荐只动sans-serif列表留出Matplotlib自己管理字体栈的空间。2.4 定制字体文件的注册方法系统没字体、又不方便安装的场景可以让Matplotlib直接加载一个字体文件这个方案对本地环境极度友好from matplotlib import font_manager font_manager.fontManager.addfont(/path/to/your/custom_font.ttf) prop font_manager.FontProperties(fname/path/to/your/custom_font.ttf) print(prop.get_name()) # 注册后得到字体名 import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [prop.get_name()] plt.rcParams[axes.unicode_minus] Falseaddfont会把指定路径的字体加入Matplotlib的字体管理器然后必须用FontProperties拿到字体内部注册名再把这个名字传给sans-serif列表。这里有个常见的坑addfont不等于自动生效只是“让Matplotlib认识这个字体”不继续配rcParams照样不会使用它。3. 不同系统下的实操配置记录3.1 WindowsSimHei和微软雅黑的取舍Windows不用装中文字体直接指定名字就行。黑体SimHei是我试过的大字号配置里最稳的标题加粗、坐标轴标签、图例都不出乱码。微软雅黑Microsoft YaHei显示观感更现代但在Matplotlib里偶尔会遇到某些字重下的渲染兼容问题比如加粗和斜体会触发合成字体警告。稳妥的首选是SimHeiplt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False不过SimHei默认没有斜体样式如果你给文字设置了fontstyleitalicMatplotlib会尝试用合成模式做伪斜体看着多少有点别扭。建议中文字体在图表里不要强行用斜体用正体加颜色区分就足够。3.2 macOS苹方与Noto Sans CJK的配合macOS上最常用的中文默认字体是苹方PingFang SC系统里叫PingFang SC直接写进sans-serif列表即可。另外冬青黑体Hiragino Sans GB和宋体STSong也能用但不同macOS版本的名字细微差别会引发“配置了却没生效”的错觉。我自己稳定的配置是plt.rcParams[font.sans-serif] [PingFang SC, Hiragino Sans GB]如果遇到PingFang SC在某个版本下找不着把Noto Sans CJK SC也塞进列表它会作为兜底。macOS的字体缓存路径也偶尔会干扰Matplotlib后面专门讲清缓存方法。3.3 Linux服务器从查字体到装字体再到验证服务器才是最考验人的。很多同学在本地Windows写好的脚本部署到Linux图里的中文全乱。原因百分之九十是Linux没中文字体。正确顺序是先确认有没有再装再验证最后才跑脚本。fc-list :langzh如果输出为空说明系统完全没有中文字体。然后apt install -y fonts-noto-cjk fc-cache -fv装完再执行一次fc-list :langzh此时应该能看到Noto Sans CJK的系列字体。接着进入Python验证from matplotlib import font_manager print(font_manager.findfont(Noto Sans CJK SC))如果输出了一个路径就可以放心在代码里写sans-serif [Noto Sans CJK SC]。这里我强烈建议不要在Linux上配SimHei除非你确实手动拷了simhei.ttf进去否则一行配置下来毫无反应排查还浪费时间。整个流程可以封装成一个函数在绘图脚本开头判断当前系统的可用中文字体实现“同一份代码在Windows和Linux都能画”的效果import platform import matplotlib.pyplot as plt def setup_chinese_font(): system platform.system() if system Windows: plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei] elif system Darwin: plt.rcParams[font.sans-serif] [PingFang SC, Hiragino Sans GB] else: plt.rcParams[font.sans-serif] [Noto Sans CJK SC, WenQuanYi Zen Hei] plt.rcParams[axes.unicode_minus] False调用一次setup_chinese_font()后面所有图都带中文。这个小函数我放进了自己的公共绘图模块再没被字体问题卡过。3.4 在线Notebook和云平台上传字体文件走addfont在内网Jupyter或者在线Notebook上经常遇到“本地能显示中文云上全是方框”的情况。云环境一般没有中文字体但你可以把字体文件直接上传到环境里再注册。以Google Colab为例from matplotlib import font_manager # 假设你把simhei.ttf上传到了当前目录 font_manager.fontManager.addfont(simhei.ttf) import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False如果你连上传都嫌麻烦Noto Sans CJK的在线获取方案也有人用动态下载的方式但我不推荐字体文件动辄十几MB每次启动都要重新拉还不如直接放项目目录里。4. 容易被忽略的负号乱码与特殊符号问题4.1 中文修好后负号怎么又变成方块这是中文乱码的“连坐问题”。常见画面是标题、坐标轴、图例里的中文都正常了但坐标轴上的负数负号变成一个个小方框。原因是Matplotlib默认用Unicode的减号U2212渲染负号而不是普通连字符-。DejaVu Sans里面有这个字形但换到SimHei、Noto Sans CJK之后部分版本的中文字体没有包含U2212这个字形于是又变成方框。一行代码解决plt.rcParams[axes.unicode_minus] False这行的作用是把负号渲染切回ASCII连字符也就是朴素的-。视觉上差别不大但彻底避开字体缺字形的问题。只要配置了中文字体建议这一行永远跟着写上属于固定搭配。4.2 摄氏度、全角引号、上标这类特殊字符怎么办有些场景会遇到℃、±、×、α这些符号中文字体不一定覆盖全部或者在部分字体下字形很丑。最直接的办法是给sans-serif列表追加一个数学符号覆盖更全的字体比如DejaVu Sans放在中文字体后面做兜底plt.rcParams[font.sans-serif] [Noto Sans CJK SC, DejaVu Sans]这样优先级是中文字体优先遇到中文字体缺失的符号Matplotlib会继续往下找DejaVu Sans补上。这个多字体回退机制是Matplotlib自带的平时没体现特殊符号一多就真香了。5. 保存图片、PDF、SVG和动画时的字体陷阱5.1 PNG和JPEG只要显示时正常保存就没问题PNG、JPEG这类栅格图在生成时会把文字直接“画”成像素点运行环境里字体正常显示保存出来的文件就正常。所以核心任务还是在绘图阶段解决字体配置输出环节基本不会出幺蛾子。如果发现PNG里中文正常但特别模糊那是dpi或figsize的问题不是字体问题。plt.savefig(output.png, dpi150, bbox_inchestight)bbox_inchestight会把所有文字边界裁进去防止中文标签被截断虽然这不是字体问题的直接解药但可以避免“标题文字被切断又被误认成乱码”的乌龙。5.2 PDF字体嵌入和文件体积的平衡保存PDF时Matplotlib会把所用字体的子集嵌入文件这本身是好事保证在别人没有该字体的机器上也能正常看。但中文字体文件往往比较大Noto Sans CJK一个家族几十MB嵌入子集后PDF体积可能从几百KB暴涨到几MB。如果介意体积可以换用字体子集更精简的字体或者在交付PDF时不追求完整嵌入配合系统级字体替代。总之别为了体积直接关闭嵌入否则别人打开PDF时的中文乱码才真的是灾难。5.3 SVG防不住查看者机器缺字体SVG保存的是矢量文字引用渲染由打开SVG的工具完成。这意味着你的电脑上Matplotlib能正常显示中文字体但对方机器没装对应字体打开SVG就乱码。这种情况下要么转成PDF/PNG交付要么确保所有查看者都有同一字体。如果坚持要SVG且对方不可控那基本只能接受字体替换的风险。5.4 保存动画GIF时的字体一致性用Matplotlib保存动画GIF热搜里那个“matplotlib保存动画伟gif”应该就是这回事时每一帧都会重新走一遍字体渲染流程。最容易踩的坑是动画绘制函数里改变了rcParams或者字体文件在帧与帧之间格式不一致导致前几帧正常、后面几帧变方框。我的建议是所有字体配置在动画生成代码块之前完成动画绘制函数内部不要动rcParams统一用同一个fig管理帧画面。import matplotlib.pyplot as plt from matplotlib import animation plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False fig, ax plt.subplots() def animate(i): ax.clear() ax.plot([1, 2, 3], [1 i, 2 i, 3 i]) ax.set_title(f第{i}帧中文标题) ani animation.FuncAnimation(fig, animate, frames10) ani.save(animation.gif, writerpillow)这样保存出来的GIF每一帧中文都是正常的。重点是字体配置放在FuncAnimation之前而不是放在animate内部每帧重复执行。6. 常见问题与排查技巧实录6.1 快速定位一张表判断问题层级我整理了一张排查表直接对着现象走流程效率会高很多现象可能的根因检查方法解决思路图里中文全是方框系统缺中文字体或未配置运行findfont(SimHei)看能否返回路径装字体并配置sans-serif配置了字体名但无效字体名写错或系统无该字体列出font_manager.fontManager.ttflist里的名字对比改用实际存在的字体名中文正常但负号是方框unicode_minus未关闭查看图中负数区域设置axes.unicode_minusFalse终端print乱码图内正常终端编码导致打开保存的图片确认不涉及Matplotlib调整终端编码换机器后同一脚本乱码新机器没相同字体在新机器执行fc-list :langzh部署字体或自动选择逻辑保存成SVG后别人打开乱码查看者机器缺字体换机器验证转PDF/PNG交付PDF体积异常大大型中文字体嵌入子集观看PDF文件大小换字体或接受体积变化6.2 改了配置还是乱码检查字体缓存Matplotlib会缓存系统中扫描到的字体列表以JSON形式保存在缓存目录里。当你新安装了中文字体但Matplotlib仍识别不到并且已经重新启动过Python进程十有八九是缓存文件过期。用几行代码找到缓存目录import matplotlib print(matplotlib.get_cachedir())然后删除这个目录下所有关于font的缓存文件比如fontlist-v330.json版本号可能不一样。删除后重启一个全新的Python会话重新执行findfont验证。这一步在Linux服务器上尤其重要因为服务器环境经常被管理员后装字体旧缓存会一直带着“没有这个字体”的记忆。6.3 我在实际项目里踩过的坑踩过的坑比教程值钱说几个最典型的。第一次是在服务器上部署定时报表脚本本地Windows测得好好的一上服务器图片全乱。当时我直接在代码里写了SimHei然后在服务器上折腾半天都没效果后来才发现服务器连SimHei文件都没有配置了等于白配。从那以后我再也不“想当然写字体名”每次先findfont验证才敢往下走。第二个坑是font.family和font.sans-serif同时设置反而产生了优先级混乱。我一度两个都配结果某个版本Matplotlib在选择字体时行为诡异导致部分元素用了中文字体、部分用了默认西文字体。后来统一为只修改font.sans-serif问题消失。第三坑是某些字体虽然能渲染中文但字形宽高比例不协调比如用了楷体后坐标轴密密麻麻的数字串在一起可读性非常低。中文图表的第一诉求永远是清晰易读黑体和无衬线中文字体是首选宋体和楷体更适合标题展示尽量避免用在数据标注上。最后分享一个实用收尾技巧把字体配置写在一个独立的配置文件里每次创建新图表项目时引用不要每个脚本重复粘贴两行rcParams。你可以建一个plot_style.py统一管理字体、颜色、画布尺寸、坐标轴线宽等风格参数这样新项目从第一天起就不会出现中文乱码也顺手解决了团队里不同成员因为各自环境不同导致图表风格不一致的问题。等哪天真遇到“侥幸没乱码”的脚本不要高兴太早检查一下是不是只对当前环境有效避免埋下部署后爆雷的隐患。