はじめに
「PythonでCSVを保存してExcelで開いたら文字化けした」
「取引先からもらったCSVが読み込めずエラーになる」
「Windowsでは正しく見えるのにMacでは崩れる」
日本語データを扱っていると必ず遭遇する問題です。この記事では、文字化けが起きる仕組みと、実務での対処法を整理します。
原因が分かれば対処は単純です。感覚で encoding を変えて試す状態から抜け出しましょう。
なぜ文字化けするのか
文字コードとは「文字を数値に変換するルール」です。保存したときのルールと、読み込むときのルールが違うと文字化けします。
保存: Shift_JIS のルールで「あ」→ 0x82A0
読込: UTF-8 のルールで 0x82A0 を解釈 → 意味不明な文字
日本語環境で登場する文字コードは主に3つです。
| 文字コード | 主な使用場面 |
|---|---|
| UTF-8 | Web、Mac、Linux、最近のシステム全般 |
| Shift_JIS(CP932) | Windows、古い業務システム、Excel出力 |
| EUC-JP | 古いUnixシステム(今はほぼ見かけない) |
補足: Pythonで指定するときは
shift_jisよりcp932が安全です。cp932はShift_JISをWindows向けに拡張したもので、「①」「㈱」などの機種依存文字も扱えます。
BOMとは何か
BOM(Byte Order Mark)は、ファイル先頭に付く「これはUTF-8ですよ」という3バイトの目印です。
ここが厄介なところで、Excelは基本的にBOMがないとUTF-8だと判別してくれません。
BOMなし UTF-8 → Excelで開くと文字化け ❌
BOMあり UTF-8 → Excelで正しく開ける ✅
Pythonでは utf-8-sig を指定すると、BOM付きで保存・BOMを自動除去して読み込みができます。
| 指定 | 動作 |
|---|---|
utf-8 |
BOMなし |
utf-8-sig |
書き込み時:BOMを付ける/読み込み時:BOMを除去する |
実務の鉄則
まずは結論です。この2つを覚えておけば大半は解決します。
【書き込み】Excelで開くCSVなら → encoding='utf-8-sig'
【読み込み】相手のCSVなら → 文字コードを判定してから読む
書き込み:Excelで文字化けしないCSVを作る
pandas の場合
import pandas as pd
df = pd.DataFrame({
'商品名': ['りんご', 'みかん'],
'価格': [150, 100],
})
# ✅ Excelで開いても文字化けしない
df.to_csv('output.csv', index=False, encoding='utf-8-sig')
# ❌ Excelで開くと文字化けする
df.to_csv('output.csv', index=False, encoding='utf-8')
標準ライブラリの場合
import csv
rows = [
['商品名', '価格'],
['りんご', 150],
]
with open('output.csv', 'w', newline='', encoding='utf-8-sig') as f:
writer = csv.writer(f)
writer.writerows(rows)
newline=''を忘れずに。 これを省くとWindowsで空行が1行おきに入ります。
相手が古い業務システムの場合
Shift_JISでしか受け付けないシステムもあります。
# cp932で保存
df.to_csv('output.csv', index=False, encoding='cp932')
# 変換できない文字があるとエラーになるので、事前に確認する
try:
df.to_csv('output.csv', index=False, encoding='cp932')
except UnicodeEncodeError as e:
print(f'cp932に変換できない文字が含まれています: {e}')
cp932で表現できない文字の例: 絵文字、「𠮷」(つちよし)などの一部の漢字、「〜」(波ダッシュの種類違い)、ローマ数字の一部。
読み込み:文字コードが分からないCSVを扱う
方法① 順番に試す(シンプルで確実)
import pandas as pd
def read_csv_auto(path: str) -> pd.DataFrame:
"""文字コードを順に試してCSVを読み込む"""
encodings = ['utf-8-sig', 'utf-8', 'cp932', 'euc-jp']
for enc in encodings:
try:
df = pd.read_csv(path, encoding=enc)
print(f'読み込み成功: {enc}')
return df
except UnicodeDecodeError:
continue
raise ValueError('対応する文字コードが見つかりませんでした')
df = read_csv_auto('unknown.csv')
順番が重要です。 utf-8-sig を先に試すことで、BOM付きファイルを正しく処理できます。
方法② ライブラリで判定する
pip install charset-normalizer
from charset_normalizer import from_path
import pandas as pd
def detect_encoding(path: str) -> str:
result = from_path(path).best()
return result.encoding if result else 'utf-8'
enc = detect_encoding('unknown.csv')
print(f'判定結果: {enc}')
df = pd.read_csv('unknown.csv', encoding=enc)
判定は万能ではないので、実務では方法①のフォールバック方式のほうが安定します。
コマンドラインで確認する
# Linux / Mac
file -I data.csv
# → data.csv: text/plain; charset=utf-8
# 先頭バイトを見てBOMの有無を確認
head -c 3 data.csv | xxd
# → 00000000: efbb bf ← これがUTF-8のBOM
よくあるトラブルと対処
① 「0」で始まる番号が消える
01234 のような商品コードや電話番号が、Excelで開くと 1234 になってしまう問題です。
これはCSV側ではなくExcelの自動変換が原因なので、文字コードでは解決しません。
# 対策A: Excelファイル(.xlsx)で出力し、文字列として書き込む
df['商品コード'] = df['商品コード'].astype(str)
df.to_excel('output.xlsx', index=False)
# 対策B: どうしてもCSVなら、ダブルクォートと=で囲む(Excel限定の記法)
df['商品コード'] = df['商品コード'].apply(lambda x: f'="{x}"')
df.to_csv('output.csv', index=False, encoding='utf-8-sig')
最も確実なのは .xlsx で渡すことです。CSVである必要がないなら、Excelファイルにしてしまうのが実務上の正解です。
② 読み込み時に数値が勝手に変換される
# 郵便番号や電話番号を文字列として読み込む
df = pd.read_csv(
'data.csv',
encoding='utf-8-sig',
dtype={'郵便番号': str, '電話番号': str, '商品コード': str},
)
# 全列を文字列として読み込む
df = pd.read_csv('data.csv', encoding='utf-8-sig', dtype=str)
③ 改行が変になる
改行コードはOSによって異なります。
| OS | 改行コード |
|---|---|
| Windows |
\r\n(CRLF) |
| Mac / Linux |
\n(LF) |
# 明示的に指定する
df.to_csv('output.csv', index=False, encoding='utf-8-sig', lineterminator='\r\n')
④ セル内の改行でデータが崩れる
住所や備考欄にセル内改行が含まれると、行がずれることがあります。
# 読み込み時:クォート処理を明示
df = pd.read_csv('data.csv', encoding='utf-8-sig', quotechar='"')
# 書き込み前に改行を除去してしまうのも手
df['備考'] = df['備考'].astype(str).str.replace('\n', ' ', regex=False)
実用例:どんなCSVでも安全に読み込む関数
import pandas as pd
def safe_read_csv(path: str, str_columns: list = None) -> pd.DataFrame:
"""
文字コードを自動判定し、指定列を文字列として読み込む
Args:
path: CSVファイルのパス
str_columns: 文字列として扱う列名のリスト(郵便番号など)
"""
encodings = ['utf-8-sig', 'utf-8', 'cp932', 'euc-jp']
dtype = {col: str for col in (str_columns or [])}
last_error = None
for enc in encodings:
try:
df = pd.read_csv(path, encoding=enc, dtype=dtype)
print(f'[OK] {path} を {enc} で読み込みました({len(df)}行)')
return df
except UnicodeDecodeError as e:
last_error = e
continue
raise ValueError(f'{path} の文字コードを判定できませんでした: {last_error}')
# 使用例
df = safe_read_csv('取引先データ.csv', str_columns=['郵便番号', '電話番号'])
まとめ
| 場面 | 対処 |
|---|---|
| Excelで開くCSVを作る | encoding='utf-8-sig' |
| 古い業務システム向け | encoding='cp932' |
| 文字コード不明のCSVを読む | 複数エンコーディングを順に試す |
| Shift_JIS指定するとき |
shift_jis より cp932
|
| 「0」始まりが消える |
.xlsx で出力するのが確実 |
| 番号系の列 |
dtype=str で読み込む |
| Windows向けの改行 | lineterminator='\r\n' |
文字化けは原因さえ分かればすぐ対処できます。「Excel向けは utf-8-sig」 だけでも覚えておくと、日々のストレスがかなり減るはずです。
データ連携・変換処理のご相談
「取引先ごとにフォーマットが違って手作業になっている」「システム間のデータ連携を自動化したい」などのご相談を受け付けています。