PHP读取Excel中文乱码的根本原因是Excel编码、PHP库解析方式与mb_internal_encoding()三者不一致;应使用PhpSpreadsheet 2.x,调用getFormattedValue()并用mb_convert_encoding多编码试探转换。

PHP读取Excel文件时中文乱码的根本原因
不是PHP本身不支持多语言,而是Excel文件编码、PHP读取库的默认解析方式、以及mb_internal_encoding()三者不一致导致的。常见现象是:Excel里显示正常的中文,在$row['姓名']中变成???或空字符串;日文、越南文、阿拉伯文直接截断或报Warning: mb_convert_encoding(): Illegal character encoding。
- Excel 2007+(.xlsx)本质是ZIP包里的XML,字符集声明在
[Content_Types].xml和sheet1.xml中,多数为UTF-8,但部分国产办公软件导出时会写错或省略 - 用
PhpSpreadsheet读取时,默认启用setReadDataOnly(true)会跳过格式信息,但不会跳过编码解析逻辑;若未显式调用setEncoding('UTF-8')(该方法并不存在),实际依赖底层XML解析器(libxml)的自动探测,而libxml对BOM缺失或混合编码容忍度极低 -
iconv()或mb_convert_encoding()强行转码前,必须确认源字符串当前真实编码——误判会导致双编码(如UTF-8→GBK→UTF-8)产生不可逆乱码
用PhpSpreadsheet安全导入含多语言的班级通信录
别碰PHPExcel(已废弃),直接上PhpSpreadsheet 2.x,并绕过其“自动编码猜测”陷阱:
- 初始化
IOFactory后,立即设置setReadDataOnly(true)+setIncludeCharts(false),减少干扰项 - 对每个单元格值,不直接使用
$cell->getValue(),改用$cell->getFormattedValue()——它返回字符串而非原始类型,避免数字/日期被误转 - 关键一步:读取整行后,对每个字段值执行
mb_convert_encoding($value, 'UTF-8', 'UTF-8, GBK, BIG5, Shift_JIS, EUC-JP'),把编码候选列表按常见度排序,让mb_convert_encoding逐个尝试,首个能无损转换的即为真实编码 - 若仍失败(如含emoji或组合字符),加一层兜底:
mb_detect_encoding($value, ['UTF-8', 'GBK', 'BIG5'], true) ?: 'UTF-8',再转
$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader('Xlsx');
$reader->setReadDataOnly(true);
$spreadsheet = $reader->load($file);
$sheet = $spreadsheet->getActiveSheet();
foreach ($sheet->getRowIterator() as $row) {
$cellIterator = $row->getCellIterator();
$cellIterator->setIterateOnlyExistingCells(false);
$data = [];
foreach ($cellIterator as $cell) {
$val = $cell->getFormattedValue();
// 多编码安全转换
$val = mb_convert_encoding($val, 'UTF-8', 'UTF-8, GBK, BIG5, Shift_JIS, EUC-JP');
$data[] = $val;
}
// $data 现在可安全存入数据库或JSON输出
}
CSV导入时处理BOM与混合分隔符的实操要点
班级通信录常被用户用Excel另存为CSV,此时BOM(EF BB BF)和Windows换行符(\r\n)是最大隐患,fgetcsv()默认无法识别BOM,且对;或,分隔符完全无感。
- 打开文件前先检测并剥离BOM:
$content = file_get_contents($file); $content = preg_replace('/^\xEF\xBB\xBF/', '', $content);,再用str_getcsv()逐行解析,比fgetcsv()更可控 - 用
mb_list_encodings()列出所有可能编码,对每行做mb_convert_encoding($line, 'UTF-8', $encodings)试探,优先试UTF-8(有BOM)、GBK(中文Windows默认)、Big5(繁体)、CP932(日文) - 分隔符不能硬编码为
,:检查首行是否含;、、、\t,用str_getcsv($line, $delimiter, '"', '\\')显式传入分隔符 - 注意Excel导出的CSV中,字段含换行符时会被双引号包裹,
str_getcsv()能正确处理,但需确保第四个参数escape设为'\\'(PhpSpreadsheet默认值)
入库前验证多语言字段长度与合法性
MySQL的VARCHAR(50)在utf8mb4下最多存50个字符,但一个emoji占4字节、一个阿拉伯字母连字(ligature)可能占多个Unicode码位——光看strlen()会误判超长。
立即学习“PHP免费学习笔记(深入)”;
- 用
mb_strlen($name, 'UTF-8')代替strlen()计算字符数,匹配数据库字段定义 - 过滤控制字符和非法Unicode:用
preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/u', '', $str)清除零宽空格、BOM残留等 - 对姓名、地址类字段,允许常见多语言标点(如「」、『』、،、؟),但禁止
\0、\x00、等注入敏感字符,可用filter_var($str, FILTER_SANITIZE_STRING, FILTER_FLAG_NO_ENCODE_QUOTES)粗筛,再人工白名单补充 - 若用PDO插入,务必设置
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4",且连接DSN中包含;charset=utf8mb4,否则即使数据正确,MySQL也会按latin1存
真正麻烦的不是读取,而是用户用不同系统、不同版本的Excel反复导出再导入——每次BOM、换行、分隔符、默认编码都可能变。与其写一堆兼容逻辑,不如在前端上传时就用SheetJS解析并校验编码,把转换压力前置。











