urllib.requestを使って日本語ドメインを含むURLを取得しようとするとエラーになる
# Python 3.6.1 import urllib.request res = urllib.request.urlopen('http://日本語.jp/about/') # UnicodeEncodeError: 'latin-1' codec can't encode characters in position 0-2: ordinal not in range(256)
urllibの手足である http.client と socket は既に国際化ドメインに対応してます。
しかし、urllib が punycode で符号化せずに Host ヘッダーをくっつけているので、エラーになっている模様。
回避策① 手動でHostヘッダーを設定する
import urllib.request req = urllib.request.Request('http://日本語.jp/about/') req.add_header('Host', req.host.encode('idna')) # これ! res = urllib.request.urlopen(req) # あとは通常どおり print(res.status, res.reason) content = res.read() print(repr(content[:1000]))
回避策② 他のライブラリを使う
たまに使われているのを見かけるrequests、こちらは国際化ドメインに対応してました。
しかし、標準ライブラリに入っていないので別途インストールが必要です。依存ライブラリが多いのでWindowsでもpipを使ってインストールした方がいいでしょう。コマンドラインから python -m pip install requests
みたにやると一発で入ります。
import requests res = requests.get('http://日本語.jp/about/') print(res.status_code, res.reason) print(repr(res.content[:1000]))
punycodeとidnaエンコーディングの違い
文字列をpunycode化するには、こうしますが、
print('abc日本語def'.encode('punycode').decode('ascii')) # abcdef-rl2mm8fl32k
ドメインの場合は各階層ごとにpunyる必要があるので、idnaを使います。
print('あいう.abc.日本語.jp'.encode('idna').decode('ascii')) # xn--l8jeg.abc.xn--wgv71a119e.jp
以上です。
参考
- encodings.idna — アプリケーションにおける国際化ドメイン名 (IDNA)
- idnaコーデックの説明。使うべき場所やドメインの正規化について軽く触れている。
- Requests: HTTP for Humans
- requestsの使い方。ドキュメントが充実している。やや古いけど日本語の翻訳もある。