閉じる

PythonのUnicodeEncodeError/UnicodeDecodeError 原因と対処法まとめ

文字化けや奇妙なエラーで作業が止まるととても困ります。

本記事ではPythonのUnicodeEncodeErrorとUnicodeDecodeErrorに真正面から向き合い、何が起きているのか、エラーメッセージの正しい読み方、そして現場ですぐ使える具体的な対処法までを、初心者の方にも分かりやすく丁寧に解説します。

UnicodeEncodeError/UnicodeDecodeErrorの基礎

何が起きているか(文字コードとエンコード/デコード)

テキストは人間が読むための抽象的な文字の並びです。

コンピュータが扱うためには、これを数字に置き換える規則が必要です。

文字コードは各文字に番号を割り当てる約束ごとであり、Unicodeは世界中の文字を一つの巨大な体系として定義します。

PythonのstrはこのUnicodeの文字列です。

しかしファイルやネットワークでは生のバイト列しか送れません。

そこで、Unicode文字列をバイト列に変換する規則がエンコーディングです。

  • エンコード: Unicode文字列str → バイト列bytes
  • デコード: バイト列bytes → Unicode文字列str

次の表は流れをまとめたものです。

フェーズ入力変換出力
エンコードstrエンコーディング(codec)bytes“寿司” → UTF-8 → b’\xe5\xaf\xbf\xe5\x8f\xb8′
デコードbytesエンコーディング(codec)strb’\x82\xa0′ → CP932 → “あ”

エラーは、使ったエンコーディングが実際のデータと合っていないときや、文字がそのエンコーディングで表現できないときに発生します。

具体例で理解する

Python
# 正しい組み合わせ: CP932でエンコードしたものをCP932でデコード
b_cp932 = "あ".encode("cp932")
print(b_cp932)  # b'\x82\xa0'
print(b_cp932.decode("cp932"))  # "あ"

# 間違った組み合わせ: CP932のバイト列をUTF-8としてデコード -> 失敗
try:
    print(b_cp932.decode("utf-8"))
except UnicodeDecodeError as e:
    print("Decode error:", e)
実行結果
b'\x82\xa0'
あ
Decode error: 'utf-8' codec can't decode byte 0x82 in position 0: invalid start byte

文字列(str)とバイト列(bytes)の違い

Pythonではstrbytesは別物です。

混同がエラーの温床になります。

  • str: Unicodeの文字の並びです。1文字は「コードポイント」として扱われます。
  • bytes: 0〜255の整数の並びです。エンコーディングを伴って初めて文字列と相互変換できます。

長さの違いを見る

Python
text = "日本"
print(type(text), len(text))  # Unicodeとして2文字

b_utf8 = text.encode("utf-8")
b_cp932 = text.encode("cp932")
print(type(b_utf8), len(b_utf8), b_utf8)   # UTF-8は可変長 -> 6バイト
print(type(b_cp932), len(b_cp932), b_cp932)  # CP932では4バイト
実行結果
<class 'str'> 2
<class 'bytes'> 6 b'\xe6\x97\xa5\xe6\x9c\xac'
<class 'bytes'> 4 b'\x93\xfa\x96{'

同じ文字列でもエンコード方式によりバイト長も中身も変わることが分かります。

よく聞くUTF-8/CP932とは

実務でよく遭遇するエンコーディングを整理します。

名称別名主な用途特徴注意点
UTF-8なしWeb全般、Linux、macOS、現代の標準ほぼ全世界の文字を表現可能。ASCII互換。原則これを使うと安定します。
CP932Shift_JIS拡張、Windows-31J日本語Windowsのレガシーファイル、Excel CSV日本語向け。2バイト中心。絵文字や一部の記号が表現できないことがあります。

新規作成のファイルやAPIはUTF-8を使うのが強く推奨です。

レガシー互換でCP932が必要な場面のみ限定して使います。

エラーが起きやすい場面

ファイル読み込みでのUnicodeDecodeError

異なるエンコーディングで保存されたファイルをopenのデフォルト設定で読むと起きがちです。

Pythonのデフォルトは環境依存です。

失敗例

Python
# 例: 実際はCP932で保存されたファイルをUTF-8として読む
try:
    with open("data_cp932.txt", "r", encoding="utf-8") as f:
        print(f.read())
except UnicodeDecodeError as e:
    print("Decode error:", e)
実行結果
Decode error: 'utf-8' codec can't decode byte 0x82 in position 0: invalid start byte

対処

  • 正しいエンコーディングを指定する
  • 一時的にerrorsで回避し内容確認する
Python
# 正しくCP932で読む
with open("data_cp932.txt", "r", encoding="cp932") as f:
    content = f.read()
    print("OK:", content[:20])

# どうしても不明なら一時回避
with open("unknown.txt", "r", encoding="utf-8", errors="replace") as f:
    content = f.read()
    print("REPLACED:", content[:50])
実行結果
OK: こんにちは、世界
REPLACED:    ... 文字化けを含むが全体像は確認できる

errorsは根本解決ではありません。

原因の切り分けのために限定的に使います。

ファイル書き込みでのUnicodeEncodeError

出力先のエンコーディングが対象文字を表現できないときに発生します。

CP932では絵文字などが典型です。

失敗例

Python
text = "寿司🍣"  # 絵文字を含む
try:
    with open("out_cp932.txt", "w", encoding="cp932") as f:
        f.write(text)  # CP932では🍣が表現できない
except UnicodeEncodeError as e:
    print("Encode error:", e)
実行結果
Encode error: 'cp932' codec can't encode character '\U0001f363' in position 2: illegal multibyte sequence

対処

  • 出力は原則UTF-8にする
  • やむを得ずCP932ならerrors="replace"で代替表記にする
Python
# 推奨: UTF-8で書く
with open("out_utf8.txt", "w", encoding="utf-8") as f:
    f.write("寿司🍣")  # OK

# やむを得ずCP932のとき
with open("out_cp932_safe.txt", "w", encoding="cp932", errors="replace") as f:
    f.write("寿司🍣")  # 🍣は '?' に置換される

ターミナル出力の文字化け(Windows/UTF-8)

Windowsのコンソールは歴史的にCP932などを使います。

UTF-8のテキストをそのまま出すと文字化けやエラーの原因になります。

症状の確認

Python
import sys, locale
print("stdout encoding:", sys.stdout.encoding)
print("preferred encoding:", locale.getpreferredencoding(False))
print("テスト: こんにちは🌏")

実行時のstdout encodingcp932なら、UTF-8の絵文字などが表示できません。

対処

  • 一時的にPYTHONIOENCODING=utf-8を設定する
  • 端末のコードページをUTF-8にするchcp 65001またはWindows Terminalを使う
  • PythonのUTF-8モードを有効化する-X utf8またはPYTHONUTF8=1

例:

cmd.exe
PowerShell
bash系
  • 一時設定: set PYTHONIOENCODING=utf-8
  • 端末をUTF-8: chcp 65001
  • 実行: python -X utf8 script.py
  • 一時設定: $env:PYTHONIOENCODING="utf-8"
  • 実行: python -X utf8 script.py

export PYTHONIOENCODING=utf-8

標準出力のエンコーディングとプログラムが出す文字の整合を取ることが重要です。

外部データ(JSON/CSV/HTTP)のエンコード違い

  • JSONは仕様上UTF-8が標準ですが、実務では誤って別のエンコードで保存されているファイルもあります。
  • CSVはExcel起源でCP932のことが多いです。Web配布のCSVはUTF-8 BOM付きのこともあります。

例1 JSON

Python
import json

# JSONはUTF-8で読むのが原則
with open("data.json", "r", encoding="utf-8") as f:
    data = json.load(f)
print(type(data))
実行結果
<class 'dict'>

JSONを書き出すときはensure_ascii=Falseで可読性を保ちつつUTF-8にします。

Python
obj = {"title": "こんにちは世界", "emoji": "🌏"}
with open("out.json", "w", encoding="utf-8") as f:
    json.dump(obj, f, ensure_ascii=False)

例2 CSV

Python
import csv

# Excel想定ならCP932で出力する場合がある
rows = [["商品名", "価格"], ["寿司🍣", 1200]]
with open("out_excel.csv", "w", newline="", encoding="cp932", errors="replace") as f:
    writer = csv.writer(f)
    writer.writerows(rows)

例3 HTTP

Python
import requests

resp = requests.get("https://example.com/data.csv", timeout=10)
# サーバが宣言したエンコードを使う。なければ推定値を使う
resp.encoding = resp.encoding or resp.apparent_encoding
text = resp.text  # 自動デコード済みのstr
print(text[:200])

エラーメッセージの読み方(原因の特定)

“codec can’t decode byte” はデコード失敗

UnicodeDecodeError: 'utf-8' codec can't decode byte 0x82 in position 0: invalid start byte のように表示されたら、バイト列→文字列の変換に失敗しています。

典型的にはencodingの指定ミスです。

Python
b = "あ".encode("cp932")  # b'\x82\xa0'
print(b.decode("utf-8"))  # 間違い
実行結果
Traceback (most recent call last):
  ...
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x82 in position 0: invalid start byte

“codec can’t encode character” はエンコード失敗

UnicodeEncodeError: 'cp932' codec can't encode character は、文字列→バイト列の変換に失敗しています。

ターゲットのエンコーディングがその文字を表現できないときです。

Python
"🍣".encode("cp932")
実行結果
Traceback (most recent call last):
  ...
UnicodeEncodeError: 'cp932' codec can't encode character '\U0001f363' in position 0: illegal multibyte sequence

どの位置で失敗したか(position)を確認

エラーには失敗位置が含まれます。

e.starte.end属性で範囲を取得できます。

Python
try:
    b = b"\x82\xa0ABC"  # CP932の「あ」+ ASCII
    b.decode("utf-8")   # 間違い
except UnicodeDecodeError as e:
    print("encoding:", e.encoding)
    print("reason:", e.reason)
    print("start-end:", e.start, e.end)
    print("problem bytes:", e.object[e.start:e.end])
実行結果
encoding: utf-8
reason: invalid start byte
start-end: 0 1
problem bytes: b'\x82'

問題のバイトと位置が分かれば、データの実体と期待のエンコーディングの食い違いを推測できます。

問題の文字と文字コードを特定する

失敗周辺のバイトを16進ダンプすると手掛かりになります。

Python
def hexdump(bs, around=8):
    # bytesを16進表記で可視化するヘルパ
    return " ".join(f"{b:02X}" for b in bs[:around])

try:
    b = "価格:1200円".encode("cp932")  # CP932で生成
    b_bad = b.replace(b"\x46", b"\x20")  # 壊れたバイトを混入
    b_bad.decode("cp932")  # デコード失敗
except UnicodeDecodeError as e:
    frag = e.object[max(0, e.start-4):e.end+4]
    print("pos:", e.start, "hex around:", hexdump(frag))
実行結果
pos: 4 hex around: 89 BF 8A 69 81 20 31 32

このように壊れたバイト、期待コーデック、位置が分かれば原因の切り分けに大きく前進します。

すぐ使える対処法(初心者向け)

ファイルはopen(…, encoding=’utf-8′)を明示

UTF-8を明示すれば、環境依存のデフォルトに左右されません。

Python
# 読み書きともにUTF-8を明示
with open("input.txt", "r", encoding="utf-8") as f:
    data = f.read()

with open("output.txt", "w", encoding="utf-8") as f:
    f.write(data)

不明なファイルはerrors=’replace’やerrors=’ignore’で一時回避

最初は内容を確認したいことがあります。

errorsを使えば例外を抑制できます。

ただしデータは欠落または変形します。

Python
# 置換して読む
with open("unknown.bin", "r", encoding="utf-8", errors="replace") as f:
    preview = f.read(200)
print(preview)

# 無視して読む
with open("unknown.bin", "r", encoding="utf-8", errors="ignore") as f:
    preview2 = f.read(200)
print(preview2)

代表的なエラーハンドラは次の通りです。

errors動作
strict例外を投げる(デフォルト)
replace代替文字に置換する
ignore問題箇所を捨てる
backslashreplaceエスケープ表記にする
surrogateescape生バイトをサロゲートで保存し後で復元可能にする

原因究明が済んだらstrictに戻すのが基本です。

.encode(‘utf-8’)/.decode(‘utf-8’)の正しい使い分け

  • str.encode("utf-8")文字列→バイト列
  • bytes.decode("utf-8")バイト列→文字列

よくある誤りはstr.decode(...)bytes.encode(...)を呼ぶことです。

Python
s = "こんにちは"
b = s.encode("utf-8")     # OK
s2 = b.decode("utf-8")    # OK

try:
    s.decode("utf-8")     # NG: strにdecodeはない
except AttributeError as e:
    print("AttributeError:", e)

try:
    b.encode("utf-8")     # NG: bytesにencodeはない
except AttributeError as e:
    print("AttributeError:", e)
実行結果
AttributeError: 'str' object has no attribute 'decode'
AttributeError: 'bytes' object has no attribute 'encode'

ターミナルのエンコーディングを合わせる(PYTHONIOENCODING)

標準入出力のエンコーディングはPYTHONIOENCODINGで上書きできます。

スクリプト内のprintinputに影響します。

  • Windows cmd: set PYTHONIOENCODING=utf-8
  • PowerShell: $env:PYTHONIOENCODING="utf-8"
  • bash: export PYTHONIOENCODING=utf-8
Python
import sys
print("sys.stdout.encoding:", sys.stdout.encoding)
print("こんにちは🌏")

WindowsはUTF-8モードを有効化する(chcp 65001/設定)

Windowsでの実用的な選択肢は次の通りです。

  • 端末をUTF-8にする: chcp 65001
  • PythonをUTF-8モードで動かす: python -X utf8 script.py または set PYTHONUTF8=1
  • Windows TerminalやVS Codeターミナルを使う
Batch File
REM cmd.exeの例
chcp 65001
set PYTHONUTF8=1
python script.py
実行結果
Active code page: 65001

UTF-8モードでは明示しない入出力の多くがUTF-8に固定され、トラブルが減ります。

ライブラリのencoding引数を必ず指定する

読んでいるのはstrかbytesか、そしてエンコーディングは何かを常に意識します。

Python
# pandas
import pandas as pd
df = pd.read_csv("in.csv", encoding="cp932")  # Excel由来ならcp932が多い
df.to_csv("out.csv", index=False, encoding="utf-8")

# json
import json
json.loads('{"title":"こんにちは"}')  # 文字列の場合は既にUnicode
with open("in.json", encoding="utf-8") as f:
    obj = json.load(f)

# requests
import requests
r = requests.get("https://example.com/page", timeout=10)
r.encoding = r.encoding or r.apparent_encoding
html = r.text  # デコード済みのstr

try-exceptでUnicodeErrorを捕まえて原因をログ出力する

エラーで止めない、原因を記録するにはtry-exceptloggingを使います。

Python
import logging

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(message)s"
)
logger = logging.getLogger(__name__)

def read_text(path, encoding="utf-8"):
    try:
        with open(path, "r", encoding=encoding) as f:
            return f.read()
    except UnicodeError as e:
        # 詳細を拾って調査に活かす
        if hasattr(e, "start"):
            logger.error("UnicodeError at %s [%s:%s] %s",
                         path, getattr(e, "start", "?"),
                         getattr(e, "end", "?"),
                         getattr(e, "reason", str(e)))
        logger.exception("Stack:")
        # 状況に応じて再スローまたは代替処理
        return None

text = read_text("mystery.txt", encoding="utf-8")
実行結果
2025-09-15 12:00:00,000 ERROR UnicodeError at mystery.txt [0:1] invalid start byte
2025-09-15 12:00:00,001 ERROR Stack:
Traceback (most recent call last):
  ...
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x82 in position 0: invalid start byte

必要ならraiseで上位に再通知し、呼び出し側で処理を分岐できます。

Python
def strict_read(path, encoding="utf-8"):
    try:
        with open(path, "r", encoding=encoding) as f:
            return f.read()
    except UnicodeDecodeError as e:
        # 独自の例外に包んで再スロー
        raise RuntimeError(f"Failed to decode {path} with {encoding}") from e

CSV/JSONはUTF-8に統一して保存する

プロジェクトの規約として新規ファイルはUTF-8を徹底すると、将来のトラブルを大幅に減らせます。

Excelとの互換が必要なら、UTF-8 BOM付きcp932など、用途ごとに方針を文書化しておきます。

Python
import csv, json

# CSVをUTF-8で
with open("data.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.writer(f)
    writer.writerow(["name", "note"])
    writer.writerow(["寿司", "海鮮"])

# JSONをUTF-8で、非ASCIIをそのまま
obj = {"title": "寿司", "emoji": "🍣"}
with open("data.json", "w", encoding="utf-8") as f:
    json.dump(obj, f, ensure_ascii=False)

まとめ

Unicodeのエラーは魔法のように見えて、実は「文字列とバイト列の行き来」「どのエンコーディングを使うか」の二点に集約されます。

ポイントは次の通りです。

まず入出力ではUTF-8を明示し、外部との互換要件がある場合のみCP932などを慎重に選びます。

次に、エラー発生時はcodec can't decodecodec can't encodeかを見極め、positionと問題のバイトを確認して原因を特定します。

さらに、errorsを一時的に使ってプログラムを止めずに調査を進め、loggingで情報を残します。

最後に、プロジェクト全体でUTF-8を標準にすることで、将来の文字化けやエンコード起因の不具合を予防できます。

これらを習慣化すれば、Unicodeのエラーは怖くありません。

この記事を書いた人
エーテリア編集部
エーテリア編集部

人気のPythonを初めて学ぶ方向けに、文法の基本から小さな自動化まで、実際に手を動かして理解できる記事を書いています。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!