テキストの取得と文字化けの原因
Python の requests ライブラリは、HTTP リクエストの実行とレスポンス処理を簡潔に記述できることから広く利用されています。しかし、日本語や中国語などの複数バイト文字を含むウェブページを取得する際、r.text で正しく表示されず、文字化けが発生するケースがよくあります。
requests は内部で以下のことを自動的に処理します:
r.content:生のバイト列(bytes型)。r.text:自動的文字エンコーディング推測に基づいたデコード済み文字列(str型)。
ただし、推測されたエンコーディングが適切でない場合、特に日本語/中国語コンテンツで nổiトラブルが起きます。典型的には、r.encoding が 'ISO-8859-1' に設定されることがあります。
安定したテキスト取得の方法
方法1:バイト列を直接デコード
».content» で取得したバイト列を明示的に指定エンコーディングでデコードする方法です。
import requests
url = 'http://music.baidu.com'
response = requests.get(url)
html_bytes = response.content
html_str = html_bytes.decode('utf-8', errors='ignore') # エラー無視でデコード
print(html_str)方法2:計算エンコーディングの明示設定
requests が推測したエンコーディングを強制的に utf-8 に上書きします。
import requests
url = 'http://music.baidu.com'
response = requests.get(url)
response.encoding = 'utf-8'
print(response.text)※ レスポンスヘッダーやHTMLのに従って最適なエンコーディングを参照する方式(後述の「最終解法」)に比べて低精度な場合があります。
ファイルへの保存処理
Direct bytes save(バイナリモード)
import requests
res = requests.get("http://www.baidu.com")
with open("download.html", "wb") as f:
f.write(res.content)decode → text save
import requests
res = requests.get("http://www.baidu.com")
text = res.content.decode('utf-8', errors='ignore')
with open("decode.html", "w", encoding="utf-8") as f:
f.write(text)text 属性使用 + 明示エンコード
import requests
res = requests.get("http://www.baidu.com")
res.encoding = "utf-8"
with open("r-text.html", "w", encoding="utf-8") as f:
f.write(res.text)解析処理ケース:Requests + lxml 連携
import requests
from lxml import etree
url = "http://music.baidu.com"
response = requests.get(url)
response.encoding = "utf-8"
html = response.text
tree = etree.HTML(html)
title_nodes = tree.xpath('//title/text()')
if title_nodes:
print(title_nodes[0])※ 出力例:»百度音乐-听到极致«
最適な文字エンコーディング推定方式
より高確率で正しいエンコーディングを推定するには、レスポンスコンテンツ本体からcharset情報を抽出・利用します。
import requests
import re
url = "http://news.sina.com.cn/"
res = requests.get(url)
# レスポンス本体内にcharset宣言があれば採択
content_type = res.headers.get('Content-Type', '')
encoding = None
# meta charsetの検出
match = re.search(r'charset=([^\s;]+)', res.text[:1024], re.IGNORECASE)
if match:
encoding = match.group(1)
else:
# ヘッダーのcharsetがある場合
match = re.search(r'charset=([^\s;]+)', content_type, re.IGNORECASE)
if match:
encoding = match.group(1)
# 推定に失敗すれば apparentEncoding を参照
if not encoding:
encoding = res.apparent_encoding
decoded_text = res.content.decode(encoding or 'utf-8', errors='replace')
with open('sina_news.html', 'w', encoding='utf-8') as f:
f.write(decoded_text)※ この方式では、HTML内に明示されたcharset宣言やContent-Typeのcharsetパラメータ、およびBody内容から charset を推定し、解析・デコードするため、より正確なテキスト再現が期待できます。