続・urllib2 で timeout を捕まえる
6/26 追記: Python2.7.3 について追試
urllib2 を使って Web API を呼び出す場合、タイムアウトが気になりますよね。
urllib2 では urllib.urlopen() の引数に timeout を指定することができます。
import urllib2 urllib2.urlopen('http://www.yahoo.co.jp/', timeout=1)
それでは、タイムアウトが発生したことはどのように得られるのでしょうか。
実験してみたところ例外 urllib2.URLError が発生していたので、
import urllib2 import timeout try: f = urllib2.urlopen('http://www.yahoo.co.jp/', timeout=1) content = f.read() except urllib2.URLError, exc: if len(exc.args) > 0 and isinstance(exc.args[0], socket.timeout): print "Caught timeout!"
のようにエラーを取得することにしました。
しかし、@r_rudi さんの環境では urllib2.URLError ではなく socket.timeout が返ってくるという指摘がありました。
そこでコードを読みつつ、挙動を確認してみると次のようなことが分かりました。
- urllib2.urlopen() は HTTPヘッダを受信し終わるまでブロックする
- f.read() は HTTP ボディを受信し終わるまでブロックする
- urllib2.urlopen() の中で発生したタイムアウトは urllib2.URLError として例外が発生する
- ただし Python 2.7.3 では通信状態によって例外が変化する
表にまとめると以下のような動きとなります。
HTTPヘッダ送信中(urlopen()中) | HTTPヘッダ受信中(urlopen()中) | HTTPヘッダ受信後(f.read()中) | |
---|---|---|---|
Python2.7.1 | urllib2.URLError | urllib2.URLError | socket.timeout |
Python2.7.2 | urllib2.URLError | urllib2.URLError | socket.timeout |
Python2.7.3 | urllib2.URLError | socket.timeout | socket.timeout |
というわけで、これらを踏まえると以下のようなコードでタイムアウトをキャッチすることができるようです。
import urllib2 import timeout try: f = urllib2.urlopen('http://www.yahoo.co.jp/', timeout=1) content = f.read() except urllib2.URLError, exc: if len(exc.args) > 0 and isinstance(exc.args[0], socket.timeout): print "Caught timeout!" except socke.timeout, exc: print "Caught timeout!"
ちゃんちゃん。