取引先から届いたCSVをExcelで開いたら、こうなっていた。

ID,蝠�蜩∝錐,譚ア莠ャ驛ス,螢イ荳�
1,譬ェ蠑丈シ夂、セ...

あるいは、こう。

ID,���i��,�����s
1,������...

どちらも「文字化け」ですが、この2つは原因が正反対です。そして化け方を見れば、どちらが起きているのかを見分けられます。

なぜCSVだけ、こんなに文字化けするのか。

結論から言うと、CSVというファイル形式には「このファイルは何の文字コードで書かれている」と書いておく場所が存在しないからです。HTMLにもXMLにもJSONにもあるその仕組みが、CSVにはありません。

文字化けの正体は「同じ数字を別の表で読んだ」だけ

コンピュータはファイルの中身をすべて数字(バイト)で持っています。文字そのものは保存されていません。

たとえば「あ」という文字をUTF-8で保存すると、ファイルの中身はこの3バイトになります。

E3 81 82

この数字を「UTF-8の対応表」で引けば「あ」に戻ります。ところが同じ数字を「Shift_JISの対応表」で引くと、別の文字になります。

E3 81 82  →  UTF-8で読む   → あ
E3 81 82  →  Shift_JISで読む → 縺(+読めない1バイト)

これが文字化けです。ファイルが壊れているわけではなく、書いた側と読む側で参照した表が違っただけ。だから正しい表で開き直せば、元通りに読めます。

実際の化け方(UTF-8のファイルをShift_JISとして読んだ場合)

業務でよく出てくる単語で、実際に起きる変化がこちらです。

本来の文字 UTF-8での中身 Shift_JISとして読むと
商品名 E5 95 86 E5 93 81 E5 90 8D 蝠蜩∝錐
東京都 E6 9D B1 E4 BA AC E9 83 BD 譚ア莠ャ驛ス
株式会社 E6 A0 AA E5 BC 8F E4 BC 9A E7 A4 BE 譬ェ蠑丈シ夂、セ
日本語 E6 97 A5 E6 9C AC E8 AA 9E 譌・譛ャ隱

見慣れない漢字と半角カナが混じった、独特の見た目になります。「譌・」「蝠」「驛ス」あたりが出てきたら、まずこのパターンだと思って構いません。

【診断】化け方で原因が特定できる

ここが実務で一番役に立つところです。文字化けには2つの方向があり、見た目がはっきり違います。

見た目 起きていること
パターンA 蝠蜩∝錐
譚ア莠ャ驛ス
(変な漢字+半角カナ)
中身はUTF-8。
それをShift_JISとして読んでいる
パターンB ���i��
�����s
(黒いひし形の連続)
中身はShift_JIS。
それをUTF-8として読んでいる

なぜ見た目が違うのか。理由はバイト列の性質にあります。

  • パターンA:UTF-8のバイト列は、たまたまShift_JISの漢字としても成立してしまう範囲に収まることが多い。だから「読めるけれど意味不明な漢字」になる
  • パターンB:Shift_JISのバイト列は、UTF-8のルール上ありえない並びになる。読み手は「解釈不能」と判断し、置換文字「�」(U+FFFD)に置き換える

つまり「�」が並んでいたら、元ファイルはShift_JIS。変な漢字が並んでいたら、元ファイルはUTF-8。開き直すべき文字コードが即座に分かります。

根本原因:CSVには文字コードを書く場所がない

ではなぜ、読む側は文字コードを間違えるのか。答えは単純で、ファイルのどこにも書いていないからです。

他の形式と比べると、CSVの特殊さがはっきりします。

形式 文字コードの伝え方
HTML ファイル内に <meta charset="UTF-8"> と書ける
XML 1行目に <?xml version="1.0" encoding="UTF-8"?> と書ける
JSON 仕様でUTF-8が基本と決まっている
CSV ファイル内に書く場所がない

CSVの仕様を定めた文書(RFC 4180)を読むと、文字コードの扱いはこう書かれています。

CSVの一般的な用法はUS-ASCIIだが、IANAが定義する他の文字集合を 「charset」パラメータと組み合わせて 使ってもよい

重要なのは「組み合わせて」の部分です。charsetはファイルの中ではなく、ファイルの外側——たとえばWebサーバーが送信時に付ける Content-Type: text/csv; charset=UTF-8 というヘッダーで伝える設計になっています。

ここに落とし穴があります。

CSVをメールに添付した瞬間、USBメモリにコピーした瞬間、文字コードの情報は消える。

残るのは中身のバイト列だけ。受け取った側のExcelは、手がかりのない状態で「たぶんこれだろう」と推測するしかありません。その推測が外れると文字化けします。API経由のやり取りでヘッダーが付く場合と違い、ファイルの手渡しでは情報が失われるわけです。

UTF-8とShift_JISの違い

そもそもこの2つは何が違うのか。実務に効く点だけ整理します。

UTF-8 Shift_JIS
読み方 ユーティーエフエイト シフトジス
扱える文字 世界中のほぼ全文字 日本語と英数字のみ
漢字1文字の容量 3バイト 2バイト
絵文字 使える 使えない
現在の位置づけ Web・システムの標準 古い日本製システムで残存

Shift_JISは日本語専用に作られた古い規格です。容量が小さくて済む利点はありましたが、致命的な弱点があります。

Shift_JISに変換すると、文字が消えることがある

Shift_JISは日本語しか表現できません。表現できない文字を無理に変換すると、その文字は失われます。

文字 UTF-8 Shift_JIS
あ 3バイト 2バイト
𠮷(つちよし) 4バイト 変換できない
😀(絵文字) 4バイト 変換できない
ハングル・タイ文字 可 変換できない

顧客名簿の「𠮷田」さんが「?田」や「吉田」に変わってしまう——これがShift_JIS変換で起きる事故です。文字化けは開き直せば直りますが、変換して保存した場合は元に戻りません。電子帳簿保存法の対象データのように原本性が問われるものでは、特に注意が必要です。

BOMの正体——本来UTF-8には不要なもの

CSVの文字化け対策を調べると、必ず「BOM付きUTF-8で保存する」という話が出てきます。このBOMが何なのか、実はかなりねじれた事情があります。

BOM(Byte Order Mark)はファイルの先頭に置かれる3バイトの目印です。

EF BB BF

本来の役割は「バイト順(Byte Order)の指定」でした。UTF-16のように2バイト単位で文字を扱う方式では、2バイトをどちらの順で並べるかが2通りあり、その区別が必要だったのです。

ところがUTF-8は1バイト単位で処理する方式なので、そもそもバイト順という概念がありません。UTF-8の仕様書(RFC 3629)にもこう明記されています。

UTF-8は単一オクテットの符号化単位を持つため、この(バイト順を認識する)機能は無意味である

それどころか、同じ仕様書はBOMの使用を積極的に制限すべきだと述べています。

常にUTF-8であることが定められている要素については、プロトコルはBOMの使用を禁止すべきである(SHOULD forbid)。その場合、目印としての機能はまったく無用だからである

Unicodeを策定している団体自身も、UTF-8でのBOMは「許容はするが推奨しない」という立場です。

ではなぜ、BOMを付けるのか

ここで話が一周します。RFC 3629がBOMを禁止すべきだとする理由は「文字コードを識別する仕組みが他にあるなら不要だから」でした。

裏を返せば——識別する仕組みがない場合には、BOMに頼るしかないということです。

そしてCSVは、まさにその「仕組みがない」形式です。Excelに伝える手段が先頭の3バイトしか残っていないため、規格上は望ましくないものが実務では必需品になっています。

BOM付きUTF-8は、CSVに文字コード宣言がないことへの苦肉の策。

BOMが引き起こす別のトラブル

規格がBOMを嫌う理由は、実害があるからです。BOMは目印であると同時にファイルの中身の一部でもあるため、それを知らないプログラムはデータとして読んでしまいます。

先頭の列名がおかしくなる

最も多いトラブルがこれです。BOM付きCSVをプログラムで読むと、1列目の見出しが変わります。

# 期待している列名
'ID'

# 実際に読み込まれる列名
'ID'

画面上は「ID」に見えるのに、プログラムからは別の名前として扱われる。そのため「ID列が見つからない」というエラーになります。目に見えない文字が原因なので、非常に気づきにくいトラブルです。同じ「見えない文字」によるトラブルは、改行コード(CRLFとLF)の違いでも起こります。

対処法

Pythonなら、読み込み時のエンコーディング指定を utf-8 ではなく utf-8-sig にすればBOMを自動で取り除いてくれます。

# BOMが列名に混入する
open('data.csv', encoding='utf-8')

# BOMがあれば自動的に除去される
open('data.csv', encoding='utf-8-sig')

設定ファイルやスクリプトの先頭にBOMが入って動かなくなる、というトラブルも同じ原因です。YAMLのような設定ファイル形式でも起こりえます。

結局どうすればいいか(ケース別)

用途によって正解が変わります。ここを混同すると対策が空振りします。

用途 推奨 理由
Excelで開いてもらう BOM付きUTF-8 Excelが文字コードを判別できる
システム間の連携 BOMなしUTF-8 BOMがデータに混入する事故を防ぐ
相手が古いシステム 指定に従う Shift_JIS指定なら従うほかない

渡す相手が「人」か「システム」かで分ける、と覚えると迷いません。

Excelでの保存方法

Excelの「名前を付けて保存」で、ファイルの種類にこの2つが並んでいます。

  • CSV UTF-8(コンマ区切り) … BOM付きUTF-8で保存される
  • CSV(コンマ区切り) … 日本語環境ではShift_JISで保存される

相手を選ばない安全側は前者です。ただし前述のとおり、システムに取り込ませる用途では逆にBOMが問題になります。

届いたCSVが化けていたときの手順

  1. 化け方を見る(変な漢字ならUTF-8、「�」ならShift_JIS)
  2. Excelで直接開かず、メモ帳などで開いて文字コードを確認する
  3. Excelの「データ」タブ →「テキストまたはCSVから」を使い、文字コードを指定して読み込む

ダブルクリックで開くとExcelが勝手に推測してしまいます。「データ」タブからの読み込みなら文字コードを明示的に選べるため、確実です。

よくある質問

Q. 文字化けしたファイルは壊れているのですか?

いいえ。中身のバイト列は無傷で、読み方を間違えているだけです。正しい文字コードで開き直せば元通りになります。ただし化けた状態で上書き保存すると本当に壊れます。まず保存せず、開き方を変えてください。

Q. UTF-8とUTF-8 BOM付きは別の文字コードですか?

文字コードとしては同じUTF-8です。違いは先頭に3バイトの目印があるかどうかだけです。

Q. なぜExcelは勝手にShift_JISだと判断するのですか?

CSVに文字コードの情報がないため、推測するしかないからです。日本語版Excelは長らくShift_JISを既定としてきた経緯があり、その挙動が残っています。BOMがあればUTF-8だと判断できます。

Q. Shift_JISはもう使わないほうがいいですか?

新しく作るデータではUTF-8を選ぶべきです。ただし取引先の既存システムがShift_JIS指定なら従うしかありません。その場合は表現できない文字が失われる点を前提に、氏名や住所のデータを扱う際は注意が必要です。

Q. 「文字コード」と「フォント」は違うものですか?

まったく別物です。文字コードは「どの文字か」を決める数字の対応表、フォントは「どう見えるか」を決めるデザインです。フォントを変えても文字化けは直りません。

参考にした主な調査・資料

まとめ

CSVの文字化けは、CSVという形式の構造的な欠陥から生まれています。

  • 文字化けは同じバイト列を別の表で読んだ結果。ファイルは壊れていない
  • 変な漢字なら元はUTF-8、「�」なら元はShift_JIS。化け方で原因が分かる
  • 原因はCSVに文字コードを書く場所がないこと。ファイルで渡すと情報が消える
  • BOMは規格上は非推奨だが、CSVには他に手段がないため実務では必要悪
  • 渡す相手が人(Excel)ならBOM付き、システムならBOMなし
  • Shift_JISへの変換は不可逆。表現できない文字は失われる

文字化けを見たときに慌てて保存し直すのではなく、まず化け方を観察する。それだけで対処の精度が大きく変わります。