如何在 Excel 中正确打开和保存 CSV|UTF-8 BOM 支持以防止乱码
「从系统导出的CSV文件在Excel中打开时日文字符乱码了」——这个问题在网络开发、数据分析和业务系统领域每天都在发生。根本原因通常是UTF-8和Shift_JIS(CP932)字符编码的差异。本文从字符乱码的根本原因开始,系统地讲解了使用UTF-8 BOM CSV的解决方案、Excel导入步骤、PHP和Python的实现方法,以及换行符代码问题。
问题:Excel 默认期望 Shift_JIS
在 Windows 版 Excel 中双击打开 CSV 文件时,会根据操作系统的区域设置自动检测编码。在日语 Windows 上,此默认编码为 <strong>Shift_JIS(代码页 932)</strong>。
因此,当您直接双击打开以UTF-8保存的CSV文件时,多字节字符(如日文)无法正确解释,导致乱码。这就是为什么「工程师在网络系统中生成的UTF-8 CSV被非工程师用Excel打开时出现乱码」这个问题反复发生的原因。
字符编码的对应关系可以整理如下。
| 字符编码 | 别名 | Excel 中的处理 | 在网络和 Linux 中的处理 |
|---|---|---|---|
| Shift_JIS | CP932、Windows-31J | 日本语版本的标准 | 遗留。不推荐使用 |
| UTF-8 (无 BOM) | — | 字符损坏(旧版本) | 网络标准 |
| UTF-8 with BOM(含 BOM) | UTF-8 with BOM | 正确识别 | BOM 可能会作为多余字符引起问题 |
| UTF-16 LE with BOM(含 BOM) | — | 正确识别 | 很少使用 |
防止 UTF-8 BOM 格式 CSV 文件乱码的方法
BOM(字节顺序标记)是添加在文件开头的特殊字节序列,用作识别文本文件字符编码的签名。UTF-8 BOM 是 3 字节:<code>EF BB BF</code>(十六进制)。
Excel 在打开文件时检测此 BOM 并判断 "此文件是用 UTF-8 编写的"。这就是为什么<strong>使用带 BOM 的 UTF-8 CSV 可以防止 Excel 中的文本乱码</strong>。
要验证带 BOM 的 UTF-8 和无 BOM 的 UTF-8 之间的差异,请使用文本编辑器或 <code>hexdump</code> 命令检查文件头字节。
# Linux / macOS での確認
hexdump -C sample.csv | head -1
# BOM なし UTF-8 の出力例:
# 00000000 e5 90 8d e5 89 8d 2c e5 ...
# BOM 付き UTF-8 の出力例:
# 00000000 ef bb bf e5 90 8d e5 89 ...(先頭に ef bb bf)
在 Excel 中从 "数据" → "文本文件" 导入的步骤
要在 Excel 中正确打开无 BOM 的 UTF-8 CSV 文件,请使用"导入数据"功能而不是双击。步骤因版本而异,但以下步骤适用于 Excel 2016 及更高版本(包括 Microsoft 365)。
- 启动 Excel 并打开新工作簿。
- 点击「数据」选项卡 → 「从文本或CSV」。
- 选择目标 CSV 文件,然后点击「导入」。
- 显示预览屏幕。从"文件来源"下拉菜单中选择 <strong>65001: Unicode (UTF-8)</strong>。
- 确认分隔符设置为「逗号」,然后点击「加载」。
对于 Excel 2013 及更早版本,使用 "数据" → "导入外部数据" → "文本文件",并在 "文本文件向导"的第 2 步中将字符编码更改为 UTF-8 (65001)。
PHP中输出带UTF-8 BOM的CSV的代码
当需要从 Web 系统允许下载 CSV 时,用 PHP 输出带 BOM 的 UTF-8 CSV 是最简单的解决方法。
// UTF-8 BOM 付き CSV のダウンロード出力
function outputCsvWithBom(array $headers, array $rows, string $filename = 'export.csv'): void
{
// キャッシュ無効化・ダウンロードヘッダーを設定
header('Content-Type: text/csv; charset=UTF-8');
header('Content-Disposition: attachment; filename="' . $filename . '"');
header('Cache-Control: no-cache, no-store, must-revalidate');
$output = fopen('php://output', 'w');
// UTF-8 BOM を出力(EF BB BF)
fputs($output, "\xEF\xBB\xBF");
// ヘッダー行を出力
fputcsv($output, $headers);
// データ行を出力
foreach ($rows as $row) {
fputcsv($output, $row);
}
fclose($output);
exit;
}
// 使用例
$headers = ['名前', 'メールアドレス', '登録日'];
$rows = [
['山田 太郎', 'taro@example.com', '2026-04-14'],
['鈴木 花子', 'hanako@example.com', '2026-04-13'],
];
outputCsvWithBom($headers, $rows, 'users_' . date('Ymd') . '.csv');
请注意,<code>fputcsv()</code> 默认使用逗号分隔和双引号转义。如果要更改分隔符,可以用第三个参数指定(例如:制表符分隔使用 <code>"\t"</code>)。
Python 中的字符编码转换代码
Python 便于转换现有 CSV 文件的字符编码,或将以 Shift_JIS 接收的数据转换为 UTF-8。
import csv
import codecs
# BOM なし UTF-8 → BOM 付き UTF-8 に変換して保存
def add_bom_to_utf8_csv(input_path: str, output_path: str) -> None:
with open(input_path, 'r', encoding='utf-8') as infile:
content = infile.read()
with open(output_path, 'w', encoding='utf-8-sig') as outfile:
# 'utf-8-sig' は自動的に BOM を付加する
outfile.write(content)
# Shift_JIS CSV → UTF-8 BOM 付き CSV に変換
def convert_sjis_to_utf8_bom(input_path: str, output_path: str) -> None:
with open(input_path, 'r', encoding='shift_jis', errors='replace') as infile:
rows = list(csv.reader(infile))
with open(output_path, 'w', encoding='utf-8-sig', newline='') as outfile:
writer = csv.writer(outfile)
writer.writerows(rows)
# 文字コードを自動検出して変換(chardet を使用)
# pip install chardet
import chardet
def detect_and_convert(input_path: str, output_path: str) -> None:
with open(input_path, 'rb') as f:
raw_data = f.read()
detected = chardet.detect(raw_data)
encoding = detected['encoding'] or 'utf-8'
print(f'検出した文字コード: {encoding}(信頼度: {detected["confidence"]*100:.0f}%)')
content = raw_data.decode(encoding, errors='replace')
with open(output_path, 'w', encoding='utf-8-sig', newline='') as outfile:
outfile.write(content)
在 Python 的 <code>open()</code> 中指定 <code>encoding='utf-8-sig'</code> 时,BOM 在写入时自动添加,在读取时自动删除。
macOS 版 Excel 的注意事项
macOS 版 Excel (Microsoft 365 for Mac) 与 Windows 版存在行为差异。
- <strong>带 UTF-8 BOM 的 CSV</strong>:即使在 Excel for macOS 中也能通过双击正确打开(从相对较新的版本开始)。
- <strong>较早版本(Excel 2016 for Mac 等)</strong>:即使使用 UTF-8 BOM,也可能出现字符乱码。在这种情况下,请使用「数据导入」功能
- <strong>在"另存为"中选择 CSV</strong>:在 macOS 版本中,可能会保存为不带 UTF-8 BOM 的格式。传递到 Windows 环境的 CSV 需要重新验证。
- <strong>与 Numbers.app 混用</strong>:macOS 的 Numbers 将 UTF-8 作为标准,但需要注意与 Excel 的兼容性。
换行码问题 (<code>CRLF</code> vs <code>LF</code>)
除了 CSV 中的乱码,换行符的差异也可能会导致问题。
| 换行码 | 字节序列 | 主要环境 | CSV 中的处理 |
|---|---|---|---|
| CRLF | 0D 0A |
Windows、HTTP | RFC 4180 中定义的标准 |
| LF | 0A |
Linux、macOS、Git 默认值 | 在大多数情况下也可在 Excel 中使用 |
| CR | 0D |
旧版 macOS(9 之前) | 可能仅在较旧的 Excel 版本中存在问题 |
尽管 CSV 的标准规范 RFC 4180 规定使用 CRLF,但现代 Excel 可以轻松处理仅使用 LF 的 CSV 文件。但是,当 CSV 包含字段内的换行符时,换行符的处理变得很重要。PHP 的 <code>fputcsv()</code> 默认使用 LF,但如果强调 Windows 兼容性,可以考虑使用 <code>str_replace("\n", "\r\n", $output)</code> 在输出后进行转换。
// PHP で CRLF 改行の CSV を出力する方法
function outputCsvCrlfWithBom(array $headers, array $rows, string $filename = 'export.csv'): void
{
header('Content-Type: text/csv; charset=UTF-8');
header('Content-Disposition: attachment; filename="' . $filename . '"');
// 一旦バッファに書き出して CRLF に変換する
ob_start();
$output = fopen('php://output', 'w');
fputs($output, "\xEF\xBB\xBF");
fputcsv($output, $headers);
foreach ($rows as $row) {
fputcsv($output, $row);
}
fclose($output);
$csv = ob_get_clean();
// LF を CRLF に変換(すでに CRLF になっているものは除外)
$csv = str_replace(["\r\n", "\n"], "\r\n", $csv);
echo $csv;
exit;
}
总结:无乱码CSV文件分发的最佳实践
- 向 Excel 用户分发的 CSV 应使用 UTF-8 BOM 格式输出
- 在PHP中,使用<code>fputs($output, "\xEF\xBB\xBF")</code>在开头添加BOM
- 在 Python 中指定 <code>encoding='utf-8-sig'</code>
- 要检查接收到的 CSV 的字符编码,请使用 <code>chardet</code> 或 <code>file</code> 命令
- 将无 BOM 的 UTF-8 CSV 提供给 Excel 用户时,请引导他们完成 "导入数据" 过程。
- <code>CRLF</code> 是 RFC 标准的换行码,但现代 Excel 也接受 <code>LF</code>
- macOS 版 Excel 可能存在行为差异,建议在接收端进行操作确认
本文中可用的测试文件
- <a href="/ja/files/encoding/" class="text-primary-600 dark:text-primary-400 hover:underline">字符编码测试文件列表</a> — UTF-8 BOM 有/无、Shift_JIS 等各种编码样本
- <a href="/ja/files/csv/" class="text-primary-600 dark:text-primary-400 hover:underline">测试用 CSV 文件列表</a> — 换行符·字符编码·BOM 有无的组合示例
- <a href="/ja/files/newline/" class="text-primary-600 dark:text-primary-400 hover:underline">换行符代码测试文件列表</a> — 确认 CRLF / LF / CR 各种模式
相关文章
- <a href="/ja/blog/csv-encoding-trouble-guide/" class="text-primary-600 dark:text-primary-400 hover:underline">彻底解决CSV文字乱码问题!字符编码、BOM、换行符的基础知识</a>
- <a href="/ja/blog/file-format-quick-reference/" class="text-primary-600 dark:text-primary-400 hover:underline">开发者文件格式快速参考</a>
- <a href="/ja/blog/base64-size-increase/" class="text-primary-600 dark:text-primary-400 hover:underline">Base64编码为什么会增加文件大小33%</a>