Python的urllib2模块不遵守超时设置。

4
以下两行代码会一直挂起:
import urllib2
urllib2.urlopen('https://www.5giay.vn/', timeout=5)

我使用的是Python2.7,没有设置http_proxy或其他环境变量。其他网站都可以正常访问。我也可以使用wget下载该网站而没有任何问题。可能出现了什么问题?


我在Linux(Amazon AMI)和Mac OS上都看到了这个问题。而且,这似乎与DNS无关,因为即使是这个也会挂起:urllib2.urlopen('https://210.245.123.158', timeout=1)。 - user3822497
1个回答

5

如果您运行

import urllib2

url = 'https://www.5giay.vn/'
urllib2.urlopen(url, timeout=1.0)

等待几秒钟,然后使用C-c来中断程序,你会看到

  File "/usr/lib/python2.7/ssl.py", line 260, in read
    return self._sslobj.read(len)
KeyboardInterrupt

这表明程序卡在了self._sslobj.read(len)上。

SSL超时会引发socket.timeout异常

您可以通过调用socket.setdefaulttimeout(1.0)来控制引发socket.timeout异常之前的延迟时间。

例如,

import urllib2
import socket

socket.setdefaulttimeout(1.0)
url = 'https://www.5giay.vn/'
try:
    urllib2.urlopen(url, timeout=1.0)
except IOError as err:
    print('timeout')

% time script.py
timeout

real    0m3.629s
user    0m0.020s
sys 0m0.024s

请注意,虽然 urllib2 失败了,但requests 模块 在这里成功了。
import requests
r = requests.get('https://www.5giay.vn/')

如何对整个函数调用设置超时:

socket.setdefaulttimeout 只会在服务器未响应时,等待 Python 抛出异常前的时间受到影响。

它和 urlopen(..., timeout=...) 都不能对整个函数调用强制执行时间限制。

要做到这一点,您可以使用 eventlets,如此展示

如果您不想安装 eventlets,可以使用标准库中的 multiprocessing; 虽然这种解决方案的扩展性不如异步解决方案,例如 eventlets提供的解决方案。

import urllib2
import socket
import multiprocessing as mp

def timeout(t, cmd, *args, **kwds):
    pool = mp.Pool(processes=1)
    result = pool.apply_async(cmd, args=args, kwds=kwds)
    try:
        retval = result.get(timeout=t)
    except mp.TimeoutError as err:
        pool.terminate()
        pool.join()
        raise
    else:
        return retval

def open(url):
    response = urllib2.urlopen(url)
    print(response)

url = 'https://www.5giay.vn/'
try:
    timeout(5, open, url)
except mp.TimeoutError as err:
    print('timeout')

执行此操作将在大约5秒钟的墙钟时间内成功或超时。


感谢调查。设置1秒超时时,确实会超时。但如果将超时时间timeout=5.0,则会永远挂起。奇怪! - user3822497
谢谢,这种情况是由于Web服务器配置错误,每秒发送一个字符。因此超时未触发,请求仍将永远持续下去。 - user3822497

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接