正确终止greenlet

4

我正在使用gevent下载一些HTML页面。 有些网站速度非常慢,有些在一段时间后停止服务请求。这就是为什么我不得不限制我发出的请求组的总时间。为此,我使用gevent的“Timeout”。

timeout = Timeout(10)
timeout.start()

def downloadSite():
    # code to download site's url one by one
    url1 = downloadUrl()
    url2 = downloadUrl()
    url3 = downloadUrl()
try:
    gevent.spawn(downloadSite).join()
except Timeout:
    print 'Lost state here'

但问题在于,当异常出现时,我会失去所有状态。

想象一下,我正在爬取网站'www.test.com'。在网站管理员决定切换Web服务器进行维护之前,我已经成功下载了10个URL。在这种情况下,当异常出现时,我将失去关于爬取页面的信息。

问题是 - 即使超时发生,我该如何保存状态并处理数据?


为什么不为每个请求定义一个超时时间?downloadUrl() 实际上在做什么?它是协作阻塞的吗?你能提供一个自包含的示例吗? - Dr. Jan-Philip Gehrcke
代码已经简化。downloadSite()函数包含获取第一页的代码,查找好的内部链接,下载它们,查找更多链接等等...我无法想象如何将每个请求包装在单独的超时中。在我看来,从编程角度来看这是错误的 + 它会对网站产生重大影响(想象一下同时从“www.test.com”请求100个网页)。 - Termos
2个回答

3
为什么不试试这样做:

timeout = Timeout(10)

def downloadSite(url):
    with Timeout(10):
        downloadUrl(url)

urls = ["url1", "url2", "url3"]

workers = []
limit = 5
counter = 0
for i in urls:
    # limit to 5 URL requests at a time
    if counter < limit:
        workers.append(gevent.spawn(downloadSite, i))
        counter += 1
    else:
        gevent.joinall(workers)
        workers = [i,]
        counter = 0
gevent.joinall(workers)

您也可以为每个URL保存一个状态,例如字典之类的东西,或者将失败的URL追加到另一个数组中,以便稍后重试。

谢谢,Gabriel,这个可行。我是 Python 新手,不知道“with”结构 :) - Termos

2
一个自包含的例子:
import gevent
from gevent import monkey
from gevent import Timeout

gevent.monkey.patch_all()
import urllib2

def get_source(url):
    req = urllib2.Request(url)
    data = None
    with Timeout(2):
        response = urllib2.urlopen(req)
        data = response.read()
    return data

N = 10
urls = ['http://google.com' for _ in xrange(N)]
getlets = [gevent.spawn(get_source, url) for url in urls]
gevent.joinall(getlets)
contents = [g.get() for g in getlets]

print contents[5]

它为每个请求实现了一个超时。在这个例子中,contents包含google.com的HTML源代码的10倍,每个都是在独立的请求中检索到的。如果其中一个请求超时了,contents中对应的元素将是None。如果您对此代码有疑问,请在评论中不要犹豫地提问。
我看到了你的最后一条评论。从编程角度来看,为每个请求定义一个超时绝对不是错误的。如果您需要限制对网站的流量,则只需不要同时生成100个greenlet。生成5个,等待它们返回。然后,您可能需要等待一定的时间,然后生成下一个5个(如我现在看到的Gabriel Samfira的其他答案所示)。对于我上面的代码,这意味着您将不断调用。
N = 10
urls = ['http://google.com' for _ in xrange(N)]
getlets = [gevent.spawn(get_source, url) for url in urls]
gevent.joinall(getlets)
contents = [g.get() for g in getlets]

相反,N 不应该太高。


谢谢Jan-Philip!我接受了Gabriel的答案,只是因为他首先提到了“with Timeout(10):”结构,尽管代码基本相似。再次感谢! - Termos

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