ExcelでCSVを正しく開く・保存する方法|文字化けしないUTF-8 BOM対応
「システムからエクスポートした CSV ファイルを Excel で開いたら日本語が文字化けした」——この問題は Web 開発・データ分析・業務システムの現場で日常的に発生するトラブルです。原因の多くは UTF-8 と Shift_JIS(CP932)の文字コードの違いにあります。本記事では、文字化けの根本原因から始まり、UTF-8 BOM 付き CSV による対処法、Excel のインポート手順、PHP・Python での実装方法、改行コードの問題まで体系的に解説します。
Excel がデフォルトで Shift_JIS を期待する問題
Windows 版 Excel は CSV ファイルをダブルクリックで開く際、OS のロケール設定に基づいてエンコーディングを自動判定します。日本語版 Windows では、このデフォルトのエンコーディングが Shift_JIS(コードページ 932) になっています。
そのため、UTF-8 で保存された CSV をそのままダブルクリックで開くと、マルチバイト文字(日本語など)が正しく解釈されず文字化けが発生します。「エンジニアが Web システムで生成した UTF-8 の CSV を非エンジニアの担当者が Excel で開いたら文字化けした」という問題が繰り返し発生するのはこのためです。
文字コードの対応関係を整理すると以下のようになります。
| 文字コード | 別名 | Excel での扱い | Web・Linux での扱い |
|---|---|---|---|
| Shift_JIS | CP932、Windows-31J | 日本語版で標準 | レガシー。使用非推奨 |
| UTF-8(BOMなし) | — | 文字化けする(旧バージョン) | Web の標準 |
| UTF-8 BOM付き | UTF-8 with BOM | 正しく認識される | BOM が余計な文字として問題になることも |
| UTF-16 LE BOM付き | — | 正しく認識される | あまり使われない |
UTF-8 BOM 付き CSV で文字化けを防ぐ方法
BOM(Byte Order Mark)は、ファイルの先頭に付加される特殊なバイト列で、テキストファイルの文字コードを識別するためのシグネチャです。UTF-8 BOM は EF BB BF(16進数)の3バイトです。
Excel はファイルを開く際にこの BOM を検出し、「このファイルは UTF-8 で書かれている」と判断します。これが UTF-8 BOM 付き CSV を使えば Excel で文字化けしない 理由です。
BOM 付き UTF-8 と BOM なし UTF-8 の違いを確認するには、テキストエディタや hexdump コマンドでファイル先頭バイトを確認します。
# 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 で「データ」→「テキストファイル」からインポートする手順
BOM なしの UTF-8 CSV を Excel で正しく開くには、ダブルクリックではなく「データの取り込み」機能を使います。バージョンによって手順が異なりますが、Excel 2016 以降(Microsoft 365 含む)では以下の手順が使えます。
- Excel を起動し、新規ブックを開きます。
- 「データ」タブ → 「テキストまたは CSV から」をクリックします。
- 対象の CSV ファイルを選択し「インポート」をクリックします。
- プレビュー画面が表示されます。「ファイルの発信元」のドロップダウンから 65001: Unicode (UTF-8) を選択します。
- 区切り文字が「コンマ」になっていることを確認し「読み込み」をクリックします。
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');
なお、fputcsv() はデフォルトでカンマ区切り・ダブルクォートでのエスケープを行います。区切り文字を変更したい場合は第3引数で指定できます(例: タブ区切りは "\t")。
Python での文字コード変換コード
既存の CSV ファイルの文字コードを変換したり、Shift_JIS で届いたデータを UTF-8 に変換するには Python が便利です。
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 の open() で encoding='utf-8-sig' を指定すると、書き込み時に BOM が自動付加され、読み込み時は BOM が自動除去されます。
macOS 版 Excel の注意点
macOS 版 Excel(Microsoft 365 for Mac)では、Windows 版と挙動が異なる点があります。
- UTF-8 BOM 付き CSV:macOS 版 Excel でもダブルクリックで正しく開けます(比較的新しいバージョンから)。
- 古いバージョン(Excel 2016 for Mac など):UTF-8 BOM 付きでも文字化けするケースがあります。この場合は「データの取り込み」を使ってください。
- 「名前を付けて保存」で CSV を選択:macOS 版では UTF-8 BOM なしで保存される場合があります。Windows 環境に渡す CSV は再確認が必要です。
- Numbers.app との混用:macOS の Numbers は UTF-8 を標準として扱いますが、Excel との互換性に注意が必要です。
改行コード(CRLF vs LF)の問題
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 の fputcsv() はデフォルトで LF を使用しますが、Windows 環境との互換性を重視する場合は出力後に str_replace("\n", "\r\n", $output) で変換することを検討してください。
// 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 では
fputs($output, "\xEF\xBB\xBF")で BOM を先頭に付加する - Python では
encoding='utf-8-sig'を指定する - 受け取った CSV の文字コードを確認するには
chardetやfileコマンドを使う - Excel ユーザーに BOM なし UTF-8 の CSV を渡す場合は「データの取り込み」手順を案内する
- 改行コードは CRLF が RFC 標準だが、現代の Excel は LF も許容する
- macOS 版 Excel は挙動が異なることがあるため、受け取り側での動作確認を推奨する
この記事で使えるテストファイル
- 文字コードテストファイル一覧 — UTF-8 BOM 有無・Shift_JIS など各種エンコーディングのサンプル
- テスト用 CSV ファイル一覧 — 改行コード・文字コード・BOM 有無の組み合わせサンプル
- 改行コードテストファイル一覧 — CRLF / LF / CR の各パターンを確認