コンテンツにスキップ

ExcelでCSVを正しく開く・保存する方法|文字化けしないUTF-8 BOM対応

カテゴリ:データ処理・Excel

「システムからエクスポートした 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 含む)では以下の手順が使えます。

  1. Excel を起動し、新規ブックを開きます。
  2. 「データ」タブ → 「テキストまたは CSV から」をクリックします。
  3. 対象の CSV ファイルを選択し「インポート」をクリックします。
  4. プレビュー画面が表示されます。「ファイルの発信元」のドロップダウンから 65001: Unicode (UTF-8) を選択します。
  5. 区切り文字が「コンマ」になっていることを確認し「読み込み」をクリックします。

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 の文字コードを確認するには chardetfile コマンドを使う
  • Excel ユーザーに BOM なし UTF-8 の CSV を渡す場合は「データの取り込み」手順を案内する
  • 改行コードは CRLF が RFC 標準だが、現代の Excel は LF も許容する
  • macOS 版 Excel は挙動が異なることがあるため、受け取り側での動作確認を推奨する

この記事で使えるテストファイル

よくある質問

ExcelでCSVを開くと文字化けするのはなぜ?

ExcelはデフォルトでShift_JIS(Windows環境)として読み込むため、UTF-8のCSVが文字化けします。UTF-8 BOM付きで保存すると解決します。

BOM(Byte Order Mark)とは何ですか?

ファイル先頭に付与される3バイト(EF BB BF)のマークで、UTF-8であることをソフトウェアに伝えます。ExcelがUTF-8を正しく認識するために必要です。

macOS版ExcelでCSVを正しく開くには?

macOS版Excelでもデータタブからテキストファイルインポートを使い、文字コードにUTF-8を指定して読み込むのが確実です。

📚 関連記事

PNG vs WebP vs AVIF|画像フォーマットの選び方と変換方法

PNG / JPEG / WebP / AVIF の特徴・用途・ブラウザ対応状況を比較。picture 要素での出し分け、DevLab の画像フォーマット変換ツールの使い方も解説。

2026-04-18

Whois でドメイン情報を調べる方法|有効期限・ネームサーバー・登録者

Whois でわかること (登録者・有効期限・レジストラ・NS)、GDPR によるプライバシー保護の影響、ドメイン管理の実務的な使い方を解説。

2026-04-18

HTTP ステータスコード完全ガイド|よくあるエラーの原因と対処法

開発者が頻出する HTTP ステータスコード (200/301/302/400/401/403/404/413/422/429/500/502/503/504) の意味・原因・対処法を解説。301 vs 302 の SEO 影響、400 vs 422 の使い分けも。

2026-04-18

cURL コマンドを JavaScript fetch・Python requests に変換する方法|DevTools 連携

Chrome DevTools の Copy as cURL を fetch / axios / Python requests / PHP cURL / Go net/http に変換する手順を解説。主要 cURL オプション (-X / -H / -d / -F / -u / -b / -L) の変換パターン、認証トークンの扱い、注意点まで。

2026-04-16

Cookie のセキュリティフラグ完全ガイド|Secure / HttpOnly / SameSite / __Host-

Cookie のセキュリティ属性 Secure / HttpOnly / SameSite (Strict/Lax/None) / __Host- __Secure- プレフィックス / 4096 バイト制限を解説。CSRF / XSS / セッションハイジャック対策と、Laravel / Express の実装例。

2026-04-16

JWT のセキュリティベストプラクティス|alg none 攻撃 / 有効期限 / 署名検証

JWT (JSON Web Token) の代表的な脆弱性 6 種類 (alg none 攻撃 / 鍵混同 / 無期限トークン / payload への機密情報 / 失効不可 / 弱いシークレット) と対策。リフレッシュトークンパターン、失効リスト、HttpOnly Cookie 格納まで。

2026-04-16