当時、Python のエンコーディング問題には本当に苦しめられ、いつか記事にまとめようとずっと思っていた。ここではまず、当時自分にとって最も役に立った 2 本の記事と重要な結論を整理しておく。今後 Beautiful Soup、gb2312、gbk、gb18030、Windows-1252 の文字化けに遭遇したとき、すばやく原因を絞り込めるようにするためだ。
問題の現象
中国語の Web ページや RSS を取得すると、ページ側では gb2312 や gbk と宣言しているのに、解析後の中国語が文字化けすることがある。Beautiful Soup、feedparser、あるいは他のライブラリがエンコーディングを windows-1252 と判定してしまい、中国語本文がまったく読めなくなる場合もある。
よくある原因は、解析ライブラリが gb2312 をまったく認識できないことではなく、Web ページの実際の内容が厳密な gb2312 文字集合に収まっていないことだ。多くの中国語ページでは gb2312 と gbk が混用されており、場合によっては gb2312 と宣言しながら、本文にはすでに gb2312 の範囲を超える文字が含まれている。厳密に検査したとき、バイト列が gb2312 に適合しないと判断されると、パーサーが windows-1252 にフォールバックし、その結果として文字化けが発生する。
重要な結論
注意すべき点は、多くの Web ページが名乗っている gb2312 は、必ずしも本当に厳密な gb2312 ではないということだ。この種のページに遭遇したら、まず gb18030 でのデコードを試すとよい。
gb18030 は gbk と gb2312 に対して下位互換性があり、カバー範囲も広い。Microsoft も gb2312 や gbk を、より広い中国語エンコーディングの扱いにマッピングすることが多い。これは一部の場面では便利だが、エンコーディング宣言と実際の内容が混ざり合いやすくなる原因にもなる。
つまり、ページには次のように書かれていても、
<meta charset="gb2312">
実際の内容は gb18030 として処理する必要がある可能性が高い。
Beautiful Soup での対処方法
Python 2 時代の書き方を使う場合は、Beautiful Soup に渡すときにエンコーディングを指定できる。
page = urllib2.build_opener().open(req).read()
soup = BeautifulSoup(page, fromEncoding="GB18030")
ここで重要なのは、HTML 内で宣言されている gb2312 を完全には信用せず、生のバイト列をより広い GB18030 としてパーサーに渡すことだ。
比較的新しい Beautiful Soup では、引数名は通常 from_encoding と書く。
from bs4 import BeautifulSoup
html = response.content
soup = BeautifulSoup(html, "html.parser", from_encoding="gb18030")
すでに手動でデコードしている場合は、先にバイト列を Unicode 文字列に変換してから Beautiful Soup に渡してもよい。
text = response.content.decode("gb18030", errors="replace")
soup = BeautifulSoup(text, "html.parser")
errors="replace" はデコードできないバイトを置換するため、取得時に本文をできるだけ残したい場合に向いている。デバッグ時にはあえてこれを使わず、例外を発生させて、どのバイト列が問題なのかを確認してもよい。
なぜ Windows-1252 と認識されるのか
典型例の一つが百度ニュース RSS だ。ページはエンコーディングを gb2312 と宣言しているが、実際の内容には gbk 範囲の文字が含まれていることがある。feedparser が gb2312 として厳密に検査すると、gb2312 の範囲を超える文字を検出し、gb2312 を諦めて windows-1252 にフォールバックする。すると中国語のバイト列が西欧文エンコーディングとして解釈され、最終的に文字化けして表示される。
したがって問題は必ずしも「ライブラリが賢くない」ことではなく、「Web ページが宣言しているエンコーディングと実際のバイト列が一致していない」ことにある。
調査手順
中国語ページの文字化けに遭遇したら、次の順序で確認するとよい。
- 生のバイト列を保持し、早い段階で
.decode()しない。 - HTTP ヘッダーの
Content-Typeと HTML 内のmeta charsetを確認する。 - 宣言が
gb2312またはgbkなのに解析に失敗する場合は、gb18030を試す。 - 端末表示だけを見ず、
repr()を出力するか、ファイルに書き出して確認する。 - 端末、エディター、データベース接続、出力ファイル自体が UTF-8 を使っているか確認する。
例:
raw = response.content
for encoding in ["utf-8", "gb18030", "gbk", "gb2312"]:
try:
text = raw.decode(encoding)
print("decoded as", encoding)
break
except UnicodeDecodeError:
pass
gb2312 が失敗して gb18030 が成功するなら、ページの宣言が十分に正確ではないことはほぼ説明できる。
参考記事
記事 1:Beautiful Soup gb2312 文字化け問題
- http://powerelite.blog.163.com/blog/static/429658912014394820777/
- http://groups.google.com/group/python-cn/browse_thread/thread/cb418ce811563524
- 午前中に Web ページ解析の文字化け問題を解決した
- http://blog.csdn.net/fanfan19881119/article/details/6789366
- 元の出典:http://leeon.me/a/beautifulsoup-chinese-page-resolve
記事 2:
まとめ
中国語 Web ページの文字化けを処理するとき、最も重要なのは「宣言されたエンコーディング」と「実際のエンコーディング」を区別することだ。多くの古い Web ページは gb2312 と書いていても、実際には gbk または gb18030 に近い。Beautiful Soup や feedparser のような解析ツールでは、Web ページの宣言を信じるより、直接 gb18030 を指定するほうが信頼できることが多い。
