19

对于此代码:

import sys

import gevent
from gevent import monkey

monkey.patch_all()

import requests
import urllib2

def worker(url, use_urllib2=False):
    if use_urllib2:
        content = urllib2.urlopen(url).read().lower()
    else:
        content = requests.get(url, prefetch=True).content.lower()
    title = content.split('<title>')[1].split('</title>')[0].strip()

urls = ['http://www.mail.ru']*5

def by_requests():
    jobs = [gevent.spawn(worker, url) for url in urls]
    gevent.joinall(jobs)

def by_urllib2():
    jobs = [gevent.spawn(worker, url, True) for url in urls]
    gevent.joinall(jobs)

if __name__=='__main__':
    from timeit import Timer
    t = Timer(stmt="by_requests()", setup="from __main__ import by_requests")  
    print 'by requests: %s seconds'%t.timeit(number=3)
    t = Timer(stmt="by_urllib2()", setup="from __main__ import by_urllib2")  
    print 'by urllib2: %s seconds'%t.timeit(number=3)
    sys.exit(0)

这个结果:

by requests: 18.3397213892 seconds
by urllib2: 2.48605842363 seconds

在嗅探器中它看起来是这样的:

描述:前 5 个请求由 requests 库发送,接下来的 5 个请求由 urllib2 库发送。红色 - 是工作冻结的时间,黑暗 - 接收数据时...... wtf?!

如果修补了套接字库并且库必须以相同的方式工作,这怎么可能?如何使用没有 requests.async 的请求进行异步工作?

4

5 回答 5

17

对不起肯尼斯·赖茨。他的图书馆很棒。

我很蠢。我需要为 httplib 选择猴子补丁,如下所示:

gevent.monkey.patch_all(httplib=True)

因为 httplib 的补丁默认是禁用的。

于 2012-03-05T08:45:28.867 回答
7

正如 Kenneth 所指出的,我们可以做的另一件事是让requests模块处理异步部分。我已相应地更改了您的代码。同样,对我来说,结果始终表明该requests模块的性能优于urllib2

这样做意味着我们不能“线程化”回调部分。但这应该没问题,因为由于请求/响应延迟,主要收益只应与 HTTP 请求有关。

import sys

import gevent
from gevent import monkey

monkey.patch_all()

import requests
from requests import async
import urllib2

def call_back(resp):
    content = resp.content
    title = content.split('<title>')[1].split('</title>')[0].strip()
    return title

def worker(url, use_urllib2=False):
    if use_urllib2:
        content = urllib2.urlopen(url).read().lower()
        title = content.split('<title>')[1].split('</title>')[0].strip()

    else:
        rs = [async.get(u) for u in url]
        resps = async.map(rs)
        for resp in resps:
            call_back(resp) 

urls = ['http://www.mail.ru']*5

def by_requests():
    worker(urls)
def by_urllib2():
    jobs = [gevent.spawn(worker, url, True) for url in urls]
    gevent.joinall(jobs)

if __name__=='__main__':
    from timeit import Timer
    t = Timer(stmt="by_requests()", setup="from __main__ import by_requests")
    print 'by requests: %s seconds'%t.timeit(number=3)
    t = Timer(stmt="by_urllib2()", setup="from __main__ import by_urllib2")
    print 'by urllib2: %s seconds'%t.timeit(number=3)
    sys.exit(0)

这是我的结果之一:

by requests: 2.44117593765 seconds
by urllib2: 4.41298294067 seconds
于 2012-03-01T23:00:02.617 回答
5

Requests 已将 gevent 支持集成到代码库中:

http://docs.python-requests.org/en/latest/user/advanced/#asynchronous-requests

于 2012-03-01T19:44:33.070 回答
2

我在我的机器上运行了你的代码(python 2.7.1, gevent 0.13.0, requests 0.10.6)。事实证明,使用 requests 模块时,时间总是快一两秒。你用的是什么版本?升级可能会简单地为您解决问题。

by requests: 3.7847161293 seconds
by urllib2: 4.92611193657 seconds

by requests: 2.90777993202 seconds
by urllib2: 7.99798607826 seconds
于 2012-03-01T17:58:36.427 回答
2

从请求文档Blocking Or Non-Blocking

如果您担心阻塞 IO 的使用,有很多项目将请求与 Python 的异步框架之一结合起来。两个很好的例子是grequestsrequests-futures

于 2016-06-22T07:48:13.863 回答