上周四深夜,朋友老周发来一张截图。他负责的一个小站,标题栏里清一色变成「锟斤拷」。他问我:无人区1234乱码,到底怎么回事。
我把浏览器控制台打开,看了三分钟。
问题的开头不在编码本身。在一份 2019 年留下来的 config.php。
一、先说这次的具体情况
站点域名指向 8080 端口。数据库是 MySQL 5.7。表结构里 charset 写的是 utf8,不是 utf8mb4。
差一个 mb4,四个字节的表情符号全挂。数字「1234」本身不会乱,但夹在 emoji 中间的新标题会。
老周在 2023 年 6 月改过一次采集规则,把标题的截取长度从 60 调到了 120。
那之后就断断续续出问题。他没在意。
二、乱码的四种常见成因
- 数据库字符集与连接字符集不一致。一边 utf8mb4,一边 latin1,握手时就错位。
- 文件本身保存的编码和 HTTP 头声明不符。文件是 GBK,头写 UTF-8,浏览器只能猜。
- 字符串被二次编码。转义做了一遍,又做了一遍,出现「&#xxxx;」这种实体串。
- 截断切在半个汉字上。UTF-8 一个汉字占三字节,按字节截 60 位,剩最后半个,就是乱码。
这次是第一条和第四条一起犯了。
三、我在页面上做的三步
第一步:看 Header
打开 curl -I,返回头里 Content-Type: text/html; charset=UTF-8。声明没错,但数据库给的已经是坏字节。
第二步:看连接
连接串里写的是 charset=utf8;MySQL 服务端默认是 utf8mb4。两边不对称。
改成 charset=utf8mb4,重启 PHP-FPM。第一层修好。
第三步:改截断函数
原来的 substr($title, 0, 60) 换成 mb_substr($title, 0, 60, 'UTF-8')。
再跑一遍采集,标题里的「1234」不再夹着方块。
这类问题排查方式很固定。工具不重要。顺序重要。
四、一个明确的个人观点
说实话,大部分「无人区1234乱码」的教程写得没用。
它们只教你怎么把页面上那几个方块改掉。不教你怎么让它不再出现。
治标和治本,是两件事。
我见过太多人复制一段 header('Content-Type: text/html; charset=utf-8') 就以为完事。第二天换个采集源,照样挂。
编码问题是链条问题。断在哪一环,得顺着走一遍。抄答案不解决。
五、留一份自己的检查清单
整理出来给自己用,也放这里。
- 文件保存编码是否为 UTF-8 无 BOM。
- HTTP 头、HTML meta、数据库连接三处声明是否一致。
- 表与字段的 collation 是否统一为
utf8mb4_unicode_ci。 - 所有字符串处理函数是否带
mb_前缀。 - 迁移旧数据时,是否用
CONVERT TO CHARACTER SET显式转换。
五条。不多。
老周那天后来去泡了杯茶。第二天早上回我:修好了,标题里的「1234」终于不方了。
顺手记下两个当时查过的参考页面,追剧app 的编码说明区,和 51dju24.com 的一份旧排查记录。都不是官方文档,但能对上当时的症状。
编码这件事讨厌的地方是:它总是错的,却总不是全部错的。
一篇笔记记到这里,够用了。