ExcelのCSV保存で文字コードを指定する方法|UTF-8 BOM問題の完全解説

ExcelからCSVをエクスポートする際、UTF-8のBOM(Byte Order Mark)の有無によってデータインポートが失敗することがあります。CSV UTF-8 BOM問題は、システム間の連携時に最も多く発生するトラブルです。本記事では、Excelでの保存方法からプログラムでの制御方法まで、実装可能な解決策を提示します。

注記: 本記事の手順はExcel for Microsoft 365およびWindows環境を基にしています。macOS版Excelでは一部動作が異なる場合があります。

UTF-8のBOMとは何か——3バイトが引き起こすトラブル

UTF-8でエンコードされたテキストファイルの先頭に、0xEF 0xBB 0xBF という3バイトが付加されることをBOM(Byte Order Mark)と呼びます。このBOMは元々、文字エンコーディングのエンディアン(バイト順序)を示すマーカーですが、UTF-8では本来不要です。

Excelで「CSV UTF-8」形式を選択すると、デフォルトではこのBOMが自動的に付加されます。一方、LinuxやPythonなどの環境では「BOM無しUTF-8」が標準です。この相互の非互換性が、文字化けやパース失敗の原因となるのです。

BOM付きとBOM無しの違い(バイト列の比較表)

項目BOM付きUTF-8BOM無しUTF-8
ファイル先頭バイト0xEF 0xBB 0xBFなし
Excelでの保存形式名CSV UTF-8(カンマ区切り)テキスト形式として手動エンコード必要
Python読み込み時の指定encoding='utf-8-sig'encoding='utf-8'
対応システムWindows・Excel・BOM対応アプリLinux・Unix・多くのWebシステム
ファイルサイズテキストサイズ + 3バイトテキストサイズのまま

BOM付きは3バイトのオーバーヘッドがあるだけでなく、テキストの先頭に目に見えない制御文字が挿入されるため、行頭フィールドのパースがずれることがあります。

BOMが問題になる具体的なシーン

CSVをクラウドサービス(GoogleスプレッドシートやSalesforceなど)にインポートする際、BOM付きファイルを読み込むと「最初の列のヘッダーが正しく認識されない」エラーが発生します。これはファイルの最初の3バイトが行頭フィールド名に混在しているためです。

また、自作のPythonやNode.jsのCSVパーサーを使う場合、BOM無しUTF-8を期待すると、BOM付きファイルの1行目が fieldname1,fieldname2 のように不可視文字を含む状態で解析されます。結果として、DBへのキー照合に失敗したり、データ型の暗黙変換に失敗したりします。

Shift-JISからUTF-8への移行期には、BOMの付与・除去を意図的に実施することで、レガシーシステムとの互換性を保つ必要も出てきます。

ExcelでCSVをUTF-8保存する方法

「CSV UTF-8」形式で保存する(BOM付き)

Excelで最も簡単な保存方法は、標準の「CSV UTF-8(カンマ区切り)」形式を使うことです。

保存手順:

ステップ操作
1「ファイル」→「名前を付けて保存」を選択
2ファイル形式を「CSV UTF-8(カンマ区切り)(.csv)」に指定
3ファイル名を入力して保存

このメソッドはBOM付きでエンコードされます。メリットは手順が簡単なことですが、デメリットはBOMが自動付加されるため、BOM無しのシステムへのインポートでは前処理が必要になります。

Power Queryで文字コードを指定してエクスポートする

Excel for Microsoft 365の「Power Query」機能を使うと、より細かい制御が可能です。ただし、Power Query自体はBOM制御の直接的なオプションを提供していません。代わりに、Power Queryでデータを準備してから、外部ツール(テキストエディタやPython)でBOMを除去する方法が実用的です。

Power Queryを使う利点は、複数のCSV形式での出力や、データ変換パイプラインの設計が容易になることです。実務では、データソースの確認や重複排除などを先に実施してから、最終的なCSV化を行う流れが推奨されます。

テキストエディタでBOMを除去・付与する

保存済みのCSVファイルのBOMを操作する最も確実な方法は、テキストエディタを使うことです。

テキストエディタでのBOM操作(VS Code):

ステップ操作
1ファイルをVS Codeで開く
2ステータスバー右側の「UTF-8」をクリック
3「エンコーディングで保存」を選択
4「UTF-8」(BOM無し)または「UTF-8 with BOM」を選択

VS Codeではファイルエンコーディングがステータスバーに表示されるため、視覚的にBOMの有無を確認できます。Notepaddの場合も同様に「文字コード」メニューから選択可能です。

テキストエディタでの手作業は数ファイル程度なら実用的ですが、大量のCSVファイルを処理する場合はプログラムによる自動化が必須です。

プログラムからBOMを制御する

PythonでBOM付き/無しUTF-8を書き出す

Pythonで最も一般的な制御方法は、encoding パラメータを指定することです。

BOM無しUTF-8で書き出す例:

import csv

data = [
    ['名前', '年齢', '所属'],
    ['田中太郎', '35', '営業部'],
    ['鈴木花子', '28', 'マーケティング部'],
]

with open('output.csv', 'w', encoding='utf-8', newline='') as f:
    writer = csv.writer(f)
    writer.writerows(data)

このコードは標準的なBOM無しUTF-8でファイルを出力します。

BOM付きUTF-8で書き出す例:

import csv

with open('output_with_bom.csv', 'w', encoding='utf-8-sig', newline='') as f:
    writer = csv.writer(f)
    writer.writerows(data)

encoding='utf-8-sig' を指定すると、自動的に先頭に 0xEF 0xBB 0xBF が付加されます。utf-8-sig は「UTF-8 with Signature」の意味です。

既存ファイルのBOM確認・変換:

def check_bom(filepath):
    with open(filepath, 'rb') as f:
        first_bytes = f.read(3)
        if first_bytes == b'\xef\xbb\xbf':
            print(f"{filepath}: BOM付き")
        else:
            print(f"{filepath}: BOM無し")

def remove_bom(filepath):
    with open(filepath, 'rb') as f:
        content = f.read()
    
    if content.startswith(b'\xef\xbb\xbf'):
        content = content[3:]
    
    with open(filepath, 'wb') as f:
        f.write(content)
    print(f"{filepath}: BOMを除去しました")

PowerShellでBOMを除去する

Windows環境では、PowerShellでバッチ的にBOMを除去できます。

$filePath = "C:\path\to\file.csv"

# ファイルを読み込み(BOM付きで読み込まれる可能性)
$content = Get-Content -Path $filePath -Encoding UTF8

# BOM無しで書き出し
$content | Out-File -FilePath $filePath -Encoding UTF8NoBOM

PowerShellの -Encoding UTF8NoBOM は、BOM無しUTF-8を明示的に指定するパラメータです。このパラメータはPowerShell 6(Core)以降で利用可能です。Windows標準のPowerShell 5.1では -Encoding UTF8 を指定するとBOM付きで出力されるため注意してください。複数ファイルを処理する場合は、以下のようにループさせます。

$csvFiles = Get-ChildItem -Path "C:\data\" -Filter "*.csv"

foreach ($file in $csvFiles) {
    $content = Get-Content -Path $file.FullName -Encoding UTF8
    $content | Out-File -FilePath $file.FullName -Encoding UTF8NoBOM
    Write-Host "$($file.Name): BOMを除去しました"
}

Node.js / PHPでの対処法

Node.jsの例(BOM無しで読み込み・書き込み):

const fs = require('fs');
const csv = require('csv-parser');

fs.createReadStream('input.csv', { encoding: 'utf-8' })
  .pipe(csv())
  .on('data', (row) => {
    console.log(row);
  });

Node.jsの標準ファイル読み込みはBOM無しを想定しています。BOM付きファイルを正しく処理するには、事前にPythonやPowerShellで除去するか、以下のようにBOMチェック関数を挟みます。

function stripBOM(content) {
  if (content.charCodeAt(0) === 0xFEFF) {
    return content.slice(1);
  }
  return content;
}

const fileContent = fs.readFileSync('input.csv', 'utf-8');
const cleanContent = stripBOM(fileContent);

PHPの例(BOM無しで読み込み):

<?php
function removeBOM($content) {
    if (substr($content, 0, 3) === pack('CCC', 0xEF, 0xBB, 0xBF)) {
        return substr($content, 3);
    }
    return $content;
}

$csvContent = file_get_contents('input.csv');
$cleanContent = removeBOM($csvContent);
$lines = explode("\n", $cleanContent);

foreach ($lines as $line) {
    $data = str_getcsv($line);
    print_r($data);
}
?>

PHPで file_get_contents() を使う場合、BOM付きファイルは先頭3バイトが残ります。removeBOM() 関数で明示的に除去することで、確実な処理ができます。

文字コードトラブルの予防策

チーム内で「BOM無しUTF-8」をルール化する

組織全体でCSVを扱う際は、「全ファイルはBOM無しUTF-8」というルールを明記することが重要です。

BOM標準化のメリット:

メリット説明
インポート安定化ExcelからのエクスポートやWebシステムへのインポートでの予期しない失敗を防止
チーム認識統一ドキュメント化・定期確認で開発チーム・ビジネスチーム間の齟齬を解消
下流対応自動変換スクリプトで下流システムの要件に対応

受け取り側のシステム仕様を事前に確認する

外部企業のシステムやSaaS製品へのCSVインポートを予定する場合、事前に「BOM付き/無しのどちらに対応しているか」を確認しましょう。

主流SaaS製品のBOM対応状況:

SaaS製品BOM標準
SalesforceBOM無しUTF-8
kintoneBOM無しUTF-8
Google SheetsBOM無しUTF-8
レガシーWindowsシステムBOM付きを期待する場合がある

API仕様書に明記されていなければ、テスト用サンプルCSVを送付して動作確認することをお勧めします。

CIでCSVの文字コードを自動チェックする

GitHub ActionsなどのCI/CDパイプラインに、CSVファイルの文字コード自動チェックを組み込むことで、意図しないBOM付きファイルのコミットを防げます。

GitHub ActionsでのPythonスクリプト例:

name: Check CSV Encoding

on: [push, pull_request]

jobs:
  check-encoding:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Check CSV encoding
        run: |
          python3 << 'EOF'
          import os
          import glob
          
          csv_files = glob.glob('data/**/*.csv', recursive=True)
          has_bom_files = []
          
          for filepath in csv_files:
              with open(filepath, 'rb') as f:
                  first_bytes = f.read(3)
                  if first_bytes == b'\xef\xbb\xbf':
                      has_bom_files.append(filepath)
          
          if has_bom_files:
              print("BOM付きファイルが見つかりました:")
              for f in has_bom_files:
                  print(f"  - {f}")
              exit(1)
          else:
              print("全CSVファイルはBOM無しです")
          EOF

このスクリプトはPushやプルリクエスト時に自動実行され、BOM付きファイルが検出されたらビルド失敗として通知します。

fileコマンドを使用した方法(Linux環境):

#!/bin/bash
for csv_file in data/*.csv; do
  if file "$csv_file" | grep -q "BOM"; then
    echo "Error: $csv_file has BOM"
    exit 1
  fi
done
echo "All CSV files are BOM-free"

fileコマンドはファイルタイプを判定する標準的なUNIXツールで、BOMの有無も検出できます。シェルスクリプトと組み合わせることで、プレコミットフックやCI処理に簡単に統合できます。

まとめ——BOMの有無はシステム間の「方言」と考える

UTF-8のBOM問題は、エンコーディング自体の理解が不足していると、解決が難しいトラブルです。しかし、BOMを単なる「3バイトの制御文字」と理解し、その付与・除去を意識的に制御できれば、ほぼすべてのシーンで対応可能です。

Excelの「CSV UTF-8」形式はBOM付きですが、これを下流システムの仕様に合わせて変換する工程を組み込むことが、安定運用のカギになります。Python、PowerShell、テキストエディタなど、環境や用途に応じた選択肢が複数あるため、チーム全体で統一的なルールを設けることが重要です。

クラウド移行やシステム間連携が進む現在、BOMはシステム間の「方言」と考えるべき問題です。受け取り側の仕様を確認し、自動変換やCI・CDでの品質保証を組み込むことで、文字化けやパース失敗の未然防止が実現できます。