2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

CSVの文字化けと戦う【Shift_JIS・UTF-8・BOMを正しく扱う】

2
Posted at

はじめに

「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」 だけでも覚えておくと、日々のストレスがかなり減るはずです。


データ連携・変換処理のご相談

「取引先ごとにフォーマットが違って手作業になっている」「システム間のデータ連携を自動化したい」などのご相談を受け付けています。

🌐 https://datarou.com

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?