文字化けや奇妙なエラーで作業が止まるととても困ります。
本記事では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) | str | b’\x82\xa0′ → CP932 → “あ” |
エラーは、使ったエンコーディングが実際のデータと合っていないときや、文字がそのエンコーディングで表現できないときに発生します。
具体例で理解する
# 正しい組み合わせ: 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ではstr
とbytes
は別物です。
混同がエラーの温床になります。
- str: Unicodeの文字の並びです。1文字は「コードポイント」として扱われます。
- bytes: 0〜255の整数の並びです。エンコーディングを伴って初めて文字列と相互変換できます。
長さの違いを見る
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互換。 | 原則これを使うと安定します。 |
CP932 | Shift_JIS拡張、Windows-31J | 日本語Windowsのレガシーファイル、Excel CSV | 日本語向け。2バイト中心。 | 絵文字や一部の記号が表現できないことがあります。 |
新規作成のファイルやAPIはUTF-8を使うのが強く推奨です。
レガシー互換でCP932が必要な場面のみ限定して使います。
エラーが起きやすい場面
ファイル読み込みでのUnicodeDecodeError
異なるエンコーディングで保存されたファイルをopen
のデフォルト設定で読むと起きがちです。
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
で回避し内容確認する
# 正しく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では絵文字などが典型です。
失敗例
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"
で代替表記にする
# 推奨: 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のテキストをそのまま出すと文字化けやエラーの原因になります。
症状の確認
import sys, locale
print("stdout encoding:", sys.stdout.encoding)
print("preferred encoding:", locale.getpreferredencoding(False))
print("テスト: こんにちは🌏")
実行時のstdout encoding
がcp932
なら、UTF-8の絵文字などが表示できません。
対処
- 一時的に
PYTHONIOENCODING=utf-8
を設定する - 端末のコードページをUTF-8にする
chcp 65001
またはWindows Terminalを使う - PythonのUTF-8モードを有効化する
-X utf8
またはPYTHONUTF8=1
例:
- 一時設定:
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
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にします。
obj = {"title": "こんにちは世界", "emoji": "🌏"}
with open("out.json", "w", encoding="utf-8") as f:
json.dump(obj, f, ensure_ascii=False)
例2 CSV
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
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
の指定ミスです。
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
は、文字列→バイト列の変換に失敗しています。
ターゲットのエンコーディングがその文字を表現できないときです。
"🍣".encode("cp932")
Traceback (most recent call last):
...
UnicodeEncodeError: 'cp932' codec can't encode character '\U0001f363' in position 0: illegal multibyte sequence
どの位置で失敗したか(position)を確認
エラーには失敗位置が含まれます。
e.start
やe.end
属性で範囲を取得できます。
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進ダンプすると手掛かりになります。
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を明示すれば、環境依存のデフォルトに左右されません。
# 読み書きともに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
を使えば例外を抑制できます。
ただしデータは欠落または変形します。
# 置換して読む
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(...)
を呼ぶことです。
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
で上書きできます。
スクリプト内のprint
やinput
に影響します。
- Windows cmd:
set PYTHONIOENCODING=utf-8
- PowerShell:
$env:PYTHONIOENCODING="utf-8"
- bash:
export PYTHONIOENCODING=utf-8
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ターミナルを使う
REM cmd.exeの例
chcp 65001
set PYTHONUTF8=1
python script.py
Active code page: 65001
UTF-8モードでは明示しない入出力の多くがUTF-8に固定され、トラブルが減ります。
ライブラリのencoding引数を必ず指定する
読んでいるのはstrかbytesか、そしてエンコーディングは何かを常に意識します。
# 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-except
とlogging
を使います。
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
で上位に再通知し、呼び出し側で処理を分岐できます。
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
など、用途ごとに方針を文書化しておきます。
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 decode
かcodec can't encode
かを見極め、position
と問題のバイトを確認して原因を特定します。
さらに、errors
を一時的に使ってプログラムを止めずに調査を進め、logging
で情報を残します。
最後に、プロジェクト全体でUTF-8を標準にすることで、将来の文字化けやエンコード起因の不具合を予防できます。
これらを習慣化すれば、Unicodeのエラーは怖くありません。