如果socket.setdefaulttimeout()无效,我该怎么办?
我正在写一个脚本(多线程的),用来从一个网站获取内容,但这个网站不太稳定,所以时不时会出现请求挂起的情况,连 socket.setdefaulttimeout()
都无法让它超时。因为我无法控制这个网站,所以我能做的就是改进我的代码,但现在我有点没主意了。
示例代码:
socket.setdefaulttimeout(150)
MechBrowser = mechanize.Browser()
Header = {'User-Agent': 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8 GTB7.1 (.NET CLR 3.5.30729)'}
Url = "http://example.com"
Data = "Justatest=whatever&letstry=doit"
Request = urllib2.Request(Url, Data, Header)
Response = MechBrowser.open(Request)
Response.close()
我该怎么做才能强制挂起的请求退出呢?其实我想知道为什么 socket.setdefaulttimeout(150)
一开始就不管用。有没有人能帮帮我?
补充说明:(问题依然没有解决)
好的,我按照tomasz的建议,把代码改成了 MechBrowser.open(Request, timeout = 60)
,但情况还是一样。到现在为止,我还是会随机遇到请求挂起的情况,有时候挂几个小时,有时候甚至几天。那我现在该怎么办?有没有办法强制这些挂起的请求退出?
4 个回答
你可以试试用 mechanize和eventlet。虽然这并不能解决你的超时问题,但因为greenlet是非阻塞的,所以它可以帮助你提高性能。
根据他们的文档:
从Python 2.6开始,urllib2在请求对象内部使用了一个叫做.timeout的属性。不过,urllib2.Request并没有提供一个可以设置超时时间的构造参数,而urllib2.urlopen()也会忽略这个参数。相比之下,mechanize.Request有一个可以设置超时时间的构造参数,这个参数会用来设置同名的属性,而mechanize.urlopen()不会忽略这个超时时间属性。
也许你可以试着把urllib2.Request换成mechanize.Request。
虽然 socket.setsocketimeout
可以设置新创建的socket的默认超时时间,但如果你不是直接使用这些socket,这个设置可能会被轻易覆盖。特别是,如果某个库在它的socket上调用了 socket.setblocking
,那么超时时间就会被重置。
urllib2.open
有一个超时参数,但 urllib2.Request
并没有超时设置。既然你在使用 mechanize
,你应该查看它们的文档:
从Python 2.6开始,urllib2在Request对象内部使用了一个.timeout属性。不过,
urllib2.Request
并没有超时构造参数,而urllib2.urlopen()
会忽略这个参数。mechanize.Request
有一个超时构造参数,用来设置同名的属性,而mechanize.urlopen()
不会忽略这个超时属性。
来源: http://wwwsearch.sourceforge.net/mechanize/documentation.html
---编辑---
如果 socket.setsockettimeout
或者给 mechanize
传递超时参数在小值时有效,但在大值时无效,问题的根源可能完全不同。一个可能的原因是你的库可能会打开多个连接(这里要感谢 @Cédric Julien),所以超时会应用到每一次socket.open的尝试上,如果第一次失败没有停止,可能会耗时到 timeout * num_of_conn
秒。另一个原因是 socket.recv
:如果连接真的很慢,而你又不走运,整个请求可能会耗时到 timeout * incoming_bytes
,因为每次 socket.recv
可能只接收到一个字节,而每次这样的调用可能会耗时 timeout
秒。虽然你不太可能遇到这种极端情况(每秒只接收一个字节?那你得真是个麻烦的家伙),但在非常慢的连接和非常高的超时时间下,请求确实可能会花费很长时间。
你唯一的解决办法是强制整个请求在固定的秒数后超时,但这和socket没有关系。如果你在Unix系统上,可以使用简单的 ALARM
信号解决方案。你设置一个信号在 timeout
秒后触发,这样你的请求就会被终止(别忘了捕获这个信号)。你可以使用 with
语句让代码更整洁易用,例如:
import signal, time
def request(arg):
"""Your http request"""
time.sleep(2)
return arg
class Timeout():
"""Timeout class using ALARM signal"""
class Timeout(Exception): pass
def __init__(self, sec):
self.sec = sec
def __enter__(self):
signal.signal(signal.SIGALRM, self.raise_timeout)
signal.alarm(self.sec)
def __exit__(self, *args):
signal.alarm(0) # disable alarm
def raise_timeout(self, *args):
raise Timeout.Timeout()
# Run block of code with timeouts
try:
with Timeout(3):
print request("Request 1")
with Timeout(1):
print request("Request 2")
except Timeout.Timeout:
print "Timeout"
# Prints "Request 1" and "Timeout"
如果你想要更具可移植性,就得用一些更复杂的工具,比如 multiprocessing
,这样你可以启动一个进程来调用你的请求,并在超时后终止它。由于这是一个独立的进程,你需要用某种方式将结果传回你的应用程序,可以使用 multiprocessing.Pipe
。下面是一个例子:
from multiprocessing import Process, Pipe
import time
def request(sleep, result):
"""Your http request example"""
time.sleep(sleep)
return result
class TimeoutWrapper():
"""Timeout wrapper using separate process"""
def __init__(self, func, timeout):
self.func = func
self.timeout = timeout
def __call__(self, *args, **kargs):
"""Run func with timeout"""
def pmain(pipe, func, args, kargs):
"""Function to be called in separate process"""
result = func(*args, **kargs) # call func with passed arguments
pipe.send(result) # send result to pipe
parent_pipe, child_pipe = Pipe() # Pipe for retrieving result of func
p = Process(target=pmain, args=(child_pipe, self.func, args, kargs))
p.start()
p.join(self.timeout) # wait for prcoess to end
if p.is_alive():
p.terminate() # Timeout, kill
return None # or raise exception if None is acceptable result
else:
return parent_pipe.recv() # OK, get result
print TimeoutWrapper(request, 3)(1, "OK") # prints OK
print TimeoutWrapper(request, 1)(2, "Timeout") # prints None
如果你想强制请求在固定的秒数后终止,选择不多。socket.timeout
会为单个socket操作(连接/接收/发送)提供超时,但如果你有多个这样的操作,可能会导致执行时间非常长。