ふくふくHukuhuku Inc.
EP.15Data Fetch 9分公開: 2026-09-02

文字化けと改行 ── 取得した直後に壊れる

日本語のデータでは、いまだに Shift_JIS 系のファイルが配布されています。推測に任せると化け、化けたまま保存すると元に戻せません。入口で決着をつけます。

#Python#文字コード#落とし穴
執筆 / 監修
松尾 亮合同会社ふくふく 代表社員

データ基盤・データパイプライン構築 / BI / 生成 AI 活用支援を専門とするエンジニア (28 年)。 本記事は AI 利用ポリシーに基づき、生成 AI の補助で執筆 → 人間が監修・編集して公開しています。

プロフィール詳細
シェア

日本語のデータを扱っていると、文字化けは今でも起きます。公的機関が配布するは Shift_JIS 系であることが珍しくなく、素直に読むと化けます。しかも化けたまま処理は進みます

厄介なのは、化けたものを保存してしまうと元に戻せない場合があることです。読めなかった文字が「?」や別の記号に置き換わっていれば、そこから復元はできません。入口で決着をつけないと、後の工程では手遅れになります。

相手の申告を鵜呑みにしない

HTTP の応答には文字コードの申告が に入っていることがあります。ただしこの申告が間違っていることがあります。中身は Shift_JIS なのに UTF-8 と申告されている、あるいは申告そのものが無い。

requests は申告が無い場合に推測しますが、この推測も外れます。日本語のように候補が複数ある言語では、短いテキストだと判別が付きません。最終的には人が確かめるしかない場面が残ります。

確実にしたいなら、復号を自分で行うのが早いです。応答を生のまま受け取り、候補の文字コードを順に試します。 を返す場合は仕様上 UTF-8 と決まっているので悩む必要がありませんが、 のファイル配布では自分で決着をつけることになります。

一度どの文字コードだったか分かれば、以降は決め打ちにして構いません。毎回推測させるより、明示して外れたら例外にするほうが安全です。配信元が変えたときに、推測だと黙って別の解釈をしますが、明示していれば止まります。

候補を順に試して、日本語として読めたものを採る
Python
CANDIDATES = ("utf-8-sig", "utf-8", "cp932", "euc-jp", "iso-2022-jp")

def decode_japanese(data: bytes) -> tuple[str, str]:    """(復号した文字列, 使った文字コード) を返す。    cp932 は Shift_JIS の上位互換なので、Shift_JIS より先に試す。"""    for enc in CANDIDATES:        try:            text = data.decode(enc)        except UnicodeDecodeError:            continue        # 復号できても意味が通らないことがあるので、        # 日本語が含まれるはずのデータなら簡単な確認を入れる        return text, enc    raise UnicodeDecodeError("unknown", data, 0, 1, "候補のどれでも復号できない")
errors に ignore や replace を指定しない

読めない文字を黙って捨てたり置き換えたりする指定です。例外が出なくなるので一見解決したように見えますが、データは壊れています。しかも壊れた箇所を後から特定できません。例外が出るなら、出させてください。

cp932 と Shift_JIS は同じではない

Windows で作られた日本語のファイルは、正確には Shift_JIS ではなく cp932 であることがほとんどです。cp932 は Shift_JIS に文字を追加した拡張で、丸数字やローマ数字、一部の記号が含まれます。

Shift_JIS として読むと、これらの文字で例外になります。日本語のファイルを読むなら cp932 を先に試すのが実際的です。逆に cp932 で読めて Shift_JIS で読めないファイルは珍しくありません。

改行コードと BOM

文字コードと並んで問題になるのが改行です。Windows 由来のファイルは行末が CR+LF になっており、そのまま行を分割すると各行の末尾に CR が残ります。目に見えないので、値を比較したときだけ一致しない、という形で表に出ます。

先頭に付く BOM も同様です。UTF-8 の BOM 付きファイルを普通に読むと、1列目の列名の先頭に見えない文字が入ります。列名で参照している処理が、その列だけ見つけられなくなります。

見えないところで壊れるもの
症状原因対処
値の比較が一致しない行末の CR が残っている改行を正規化して読む
1列目だけ参照できない先頭の BOMutf-8-sig で読む
数字が文字列になる全角数字・桁区切り取得直後に変換する
「?」が混ざる誤った文字コードで読んだ取り直す。復元はできない
BOM と改行をまとめて処理してから中身を扱う
Python
import csvimport io

def read_csv_text(data: bytes) -> list[dict]:    """BOM を取り除き、改行を揃えてから CSV として読む。"""    text, enc = decode_japanese(data)
    # BOM は utf-8-sig で復号すれば消えるが、他の文字コードでは残ることがある    text = text.lstrip("\ufeff")
    # newline="" を渡すのは、csv 側に改行の扱いを任せるため。    # 自分で splitlines すると、値の中にある改行まで行区切りにしてしまう    buf = io.StringIO(text, newline="")    return list(csv.DictReader(buf))

上のコードで splitlines() を使っていないのは、値の中に改行が含まれている場合に壊れるからです。住所や備考の欄には改行が入っていることがあり、自分で行を分けると1件が2件に割れます。読み込みは専用の仕組みに任せてください。

受け取った生のバイト列をに残しておくと、文字コードの判断を間違えても取り直さずにやり直せます。復号したあとの文字列だけを保存すると、間違えた解釈が確定してしまいます。ここは EP.05 の「生のまま残す」がそのまま効く場面です。

文字コードの問題は、原因が分かってしまえば対処は単純です。しかし分かるまでが長い。取得直後に「どの文字コードで読んだか」を記録に残しておくと、後から追いやすくなります。次回は、そもそもスクレイピングに手を出してよいかの線引きを扱います。

シェア

この記事の感想を教えてください

あなたの 1 クリックで、本当にこの記事は更新されます。「もっと詳しく」「続編希望」が一定数集まった記事は、 ふくふくが 実際に内容を拡充したり続編記事を公開 します。 送信したリアクションはお使いのブラウザに記録され、再カウントされません。

シリーズの外も探す:

まずは、現状を聞かせてください。

要件が固まっていなくて大丈夫です。現状診断と方針提案までを無料でお手伝いします。

無料相談フォームへ hello [at] hukuhuku [dot] co [dot] jp