如何在Python 3中处理urllib的超时?

时间:2012-01-06 19:36:45

标签: python exception urllib

首先,我的问题与this one非常相似。我希望urllib.urlopen()超时以生成我可以处理的异常。

这不属于URLError吗?

try:
    response = urllib.request.urlopen(url, timeout=10).read().decode('utf-8')
except (HTTPError, URLError) as error:
    logging.error(
        'Data of %s not retrieved because %s\nURL: %s', name, error, url)
else:
    logging.info('Access successful.')

错误消息:

  

resp = urllib.request.urlopen(req,timeout = 10).read()。decode('utf-8')
      文件“/usr/lib/python3.2/urllib/request.py”,第138行,在urlopen中       return opener.open(url,data,timeout)
      文件“/usr/lib/python3.2/urllib/request.py”,第369行,处于打开状态
      response = self._open(req,data)
      文件“/usr/lib/python3.2/urllib/request.py”,第387行,在_open中       '_open',req)
      文件“/usr/lib/python3.2/urllib/request.py”,第347行,在_call_chain中       result = func(* args)
      在http_open中输入文件“/usr/lib/python3.2/urllib/request.py”,第1156行       return self.do_open(http.client.HTTPConnection,req)
      文件“/usr/lib/python3.2/urllib/request.py”,第1141行,在do_open中       r = h.getresponse()
      文件“/usr/lib/python3.2/http/client.py”,第1046行,在getresponse中       response.begin()
      文件“/usr/lib/python3.2/http/client.py”,第346行,开头
      版本,状态,原因= self._read_status()
      文件“/usr/lib/python3.2/http/client.py”,第308行,在_read_status中       line = str(self.fp.readline(_MAXLINE + 1),“iso-8859-1”)
      文件“/usr/lib/python3.2/socket.py”,第276行,在readinto中       return self._sock.recv_into(b)
      socket.timeout:超时

当他们将urlliburllib2模块重组为urllib时,Python 3发生了重大变化。是否有可能导致这种情况发生变化?

3 个答案:

答案 0 :(得分:27)

例外是socket的超时,所以

from socket import timeout
try:
    response = urllib.request.urlopen(url, timeout=10).read().decode('utf-8')
except (HTTPError, URLError) as error:
    logging.error('Data of %s not retrieved because %s\nURL: %s', name, error, url)
except timeout:
    logging.error('socket timed out - URL %s', url)
else:
    logging.info('Access successful.')

应该抓住新的例外。

答案 1 :(得分:5)

上一个答案无法正确拦截超时错误。超时错误会引发为URLError,因此,如果要专门捕获它们,则需要编写:

from urllib.error import HTTPError, URLError
import socket

try:
    response = urllib.request.urlopen(url, timeout=10).read().decode('utf-8')
except HTTPError as error:
    logging.error('Data not retrieved because %s\nURL: %s', error, url)
except URLError as error:
    if isinstance(error.reason, socket.timeout):
        logging.error('socket timed out - URL %s', url)
    else:
        logging.error('some other error happened)
else:
    logging.info('Access successful.')

请注意,ValueError可以独立引发,即网址无效。像HTTPError一样,它与超时无关。

答案 2 :(得分:0)

什么是“超时”?总的来说,我认为这意味着“服务器没有及时响应的情况,通常是因为高负载,值得再试一次。”

HTTP 状态 504“网关超时”将是此定义下的超时。它通过 HTTPError 传递。

HTTP 状态 429“请求过多”也属于该定义下的超时。它也是通过 HTTPError 传递的。

否则,超时是什么意思?我们是否在通过 DNS 解析器解析域名时包含超时?尝试发送数据时超时?等待数据返回时超时?

我不知道如何审核 urllib 的源代码,以确保我可能会考虑超时的所有可能方式都以我能理解的方式提出。在没有检查异常的语言中,我不知道如何。我有一种预感,连接到 dns 错误可能会作为 socket.timeout 返回,而连接到远程服务器错误可能会作为 URLError(socket.timeout) 返回?这只是一种猜测,可以解释早期的观察结果。

所以我回到了一些真正防御性的编码。 (1) 我正在处理一些指示超时的 HTTP 状态代码。 (2) 有报告说有些超时是通过 socket.timeout 异常产生的,有些是通过 URLError(socket.timeout) 异常产生的,所以我同时抓住了两者。 (3) 为了以防万一,我也抛出了 HTTPError(socket.timeout)。

while True:
    reason : Optional[str] = None
    try:
        with urllib.request.urlopen(url) as response:
            content = response.read()
            with open(cache,"wb") as file:
                file.write(content)
            return content
    except urllib.error.HTTPError as e:
        if e.code == 429 or e.code == 504: # 429=too many requests, 504=gateway timeout
            reason = f'{e.code} {str(e.reason)}'
        elif isinstance(e.reason, socket.timeout):
            reason = f'HTTPError socket.timeout {e.reason} - {e}'
        else:
            raise
    except urllib.error.URLError as e:
        if isinstance(e.reason, socket.timeout):
            reason = f'URLError socket.timeout {e.reason} - {e}'
        else:
            raise
    except socket.timeout as e:
        reason = f'socket.timeout {e}'
    except:
        raise
    netloc = urllib.parse.urlsplit(url).netloc # e.g. nominatim.openstreetmap.org
    print(f'*** {netloc} {reason}; will retry', file=sys.stderr)
    time.sleep(5)