如果socket.setdefaulttimeout()无效,我该怎么办?

8 投票
4 回答
30568 浏览
提问于 2025-04-17 08:09

我正在写一个脚本(多线程的),用来从一个网站获取内容,但这个网站不太稳定,所以时不时会出现请求挂起的情况,连 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 个回答

0

你可以试试用 mechanize和eventlet。虽然这并不能解决你的超时问题,但因为greenlet是非阻塞的,所以它可以帮助你提高性能。

2

根据他们的文档:

从Python 2.6开始,urllib2在请求对象内部使用了一个叫做.timeout的属性。不过,urllib2.Request并没有提供一个可以设置超时时间的构造参数,而urllib2.urlopen()也会忽略这个参数。相比之下,mechanize.Request有一个可以设置超时时间的构造参数,这个参数会用来设置同名的属性,而mechanize.urlopen()不会忽略这个超时时间属性。

也许你可以试着把urllib2.Request换成mechanize.Request。

24

虽然 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操作(连接/接收/发送)提供超时,但如果你有多个这样的操作,可能会导致执行时间非常长。

撰写回答