歩いたら休め

If the implementation is easy to explain, it may be a good idea.

【Python】urllibでマルチバイト文字のURLのhttps通信が失敗する

サラリーマンたるもの、日々の情報収集は欠かせません。

そのため、気になる業界ニュースをチャットに通知するクローラーを実装して使っているのですが、度々未知のエラーが出てしまいます。

今朝は、こちらのニュースをクローリングしようとして、

from urllib import request
request.urlopen('https://xn--u9j460nu9a58aw75c.com')

すると、以下のようなエラーが出てしまっていました。

CertificateError: hostname '株の教科書.com' doesn't match either of 'xn--u9j460nu9a58aw75c.com', 'www.xn--u9j460nu9a58aw75c.com'

利用しているPythonのバージョンは3.6.1です。

$ python3 -V
Python 3.6.1

ここで2点可能性があるのですが、

  1. 証明書の認証に失敗している
  2. マルチバイト文字のURLの比較で失敗している

requestsライブラリを利用した場合はエラーが出ないため、2. の可能性が濃厚です。

import requests
requests.get('https://xn--u9j460nu9a58aw75c.com')

暫定的な対処

18.2. ssl — ソケットオブジェクトに対する TLS/SSL ラッパー — Python 3.6.1 ドキュメント

ssl.match_hostname(cert, hostname)(原文)

(SSLSocket.getpeercert() が返してきたようなデコードされたフォーマットの) cert が、与えられた hostname に合致するかを検証します。HTTPS サーバの身元をチェックするために適用されるルールは RFC 2818, RFC 6125 で概説されているものです。HTTPS に加え、この関数は他の SSL ベースのプロトコル、例えば FTPS, IMAPS, POPS などのサーバの身元をチェックするのに相応しいはずです。

失敗すれば CertificateError が送出されます。成功すれば、この関数は何も返しません:

今回はアクセスするURLが安全なものだとわかっているので、こちらのページに従い「証明書のチェックを行わない」という設定をして、一時的な対処を行いました。ただし、いろいろとセキュリティの問題が出てきそうなので今後はこの設定は使いたくありません。

shinespark.hatenablog.com

import ssl
from urllib import request

ssl._create_default_https_context = ssl._create_unverified_context
request.urlopen('https://xn--u9j460nu9a58aw75c.com')

TODO::

  • urllibライブラリのエラーが出ている箇所のコードをチェックする。
  • バージョンアップでの対処やイシューが出ているかどうかを調べる。