urllib.URLopenerのエラーを詳しく検出する

タイムアウトの設定についてはPython 2.6以降urllib.urlopenerのオプション引数でサポートされるようになりました。後ほど書き直します。

urllib.URLopenerはすべてのエラーをIOErrorで返します。except文はひとつで済むのですが、DNSのエラー、タイムアウト、URLの不正形式からHTTPサーバにおけるNotFound、認証要求、リダイレクトまで全部一緒に扱われてしまいます。エラーの内容によって処理を振り分けたい場合、error valueを取って調べることになります。

e.errno        e.strerror e.args
============== ========== =========================================
'url error'    '...'      ('url error', '...')
'socket error' *1         ('socket error', '...')
'socket error' *1         ('socket error', 'timed out')
None           None       ('http error', STATUS, '...', MIMEOBJECT)

  *1 ... socket関係のエラーはsocket.gaierrorのインスタンスタイムアウトのときはsocket.timeoutのインスタンスが入る

error valueの値は大きく分けて3種類となり、それぞれurllib・socket・httplibの各モジュールで発生したエラーであることを示しています。socketについてはさらにネットワーク接続不可とタイムアウトの2種類に原因を切り分けることができます。

from urllib import URLopener
import socket

opener = URLopener()
url = '...'
socket.setdefaulttimeout(10.0)

try:
    f = opener.open(url)

except IOError, e:
    if e.args[0] == 'http error':
        print 'サーバに接続しましたが、ステータス%dが返されました' % e.args[1]
    elif e.args[0] == 'url error':
        print 'URLが不正です - %s' % e.args[1]
    elif e.args[0] == 'socket error':
        if isinstance(e.strerror, socket.timeout):
            print '指定時間内に応答がありません'
        elif isinstance(e.strerror, socket.gaierror):
            print 'サーバに接続できません - %s' % e.args[1]
        else:
            print '通信関係のエラー - %s' % e.args[1]
    else:
        print 'その他のエラー - %s' % e.args[0]

なお、HTTPのステータスによって処理を振り分ける場合はurllib.FancyURLopenerを使った方がいいでしょう。このサブクラスとしてopenerを定義すれば各HTTPステータスに対応するメソッドを用意できますし、標準でリダイレクトの処理まで行ってくれます。