Python中requests库在大量请求时如何优化性能?

网上有 grequests 库 但是有限制 。只能把所有的 url 拉出来一起请求、 我们的项目里要请求各种 url 并且是在不同代码块儿。 做不到拉出来一块儿请求 请问各位 python 网络编程的大佬 能不能支支招。

梦想中的场景 -- > 像 requests 一样的简单 但是是异步的。(就开开玩笑)


Python中requests库在大量请求时如何优化性能?
23 回复

本地单独起个 tornado 服务
每次都请求 localhost:10000/fetch/{ip}/,代码里用 AsyncHttpClient


对于大量请求,用requests库直接搞确实会慢,主要是卡在同步I/O上。最直接的优化思路就是别让它干等着,上异步或者并发。

核心方案:用 requests + concurrent.futures 搞线程池并发。 这招对I/O密集型任务(比如网络请求)特管用,因为线程在等待响应时,CPU可以切去处理其他线程的任务。

下面是个完整例子,对比一下单线程和用线程池的差距,你就明白为啥要这么干了:

import requests
import concurrent.futures
import time

# 假设我们要请求的URL列表
urls = [
    'https://httpbin.org/delay/1',  # 这个端点会故意延迟1秒响应
    'https://httpbin.org/delay/1',
    'https://httpbin.org/delay/1',
    'https://httpbin.org/delay/1',
    'https://httpbin.org/delay/1',
] * 10  # 重复10次,模拟50个请求

def fetch_url(url):
    """单个请求任务"""
    try:
        resp = requests.get(url, timeout=5)
        return resp.status_code
    except Exception as e:
        return str(e)

# 1. 传统单线程方式 (基线对比)
def sequential_requests():
    start = time.time()
    results = []
    for url in urls:
        results.append(fetch_url(url))
    end = time.time()
    print(f"顺序执行耗时: {end - start:.2f} 秒")
    return results

# 2. 使用ThreadPoolExecutor并发执行
def concurrent_requests(max_workers=10):
    start = time.time()
    results = []
    # 创建线程池,max_workers控制并发数
    with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
        # 提交所有任务到线程池,map会保持返回结果的顺序
        future_to_url = {executor.submit(fetch_url, url): url for url in urls}
        for future in concurrent.futures.as_completed(future_to_url):
            try:
                result = future.result()
                results.append(result)
            except Exception as e:
                results.append(str(e))
    end = time.time()
    print(f"线程池并发 (workers={max_workers}) 耗时: {end - start:.2f} 秒")
    return results

if __name__ == '__main__':
    print("开始测试...")
    print(f"总请求数: {len(urls)}")
    print("-" * 40)
    
    # 跑一下单线程的
    seq_results = sequential_requests()
    
    # 跑一下线程池并发的,你可以调整max_workers试试效果
    conc_results = concurrent_requests(max_workers=20)
    
    print("-" * 40)
    print("测试完成。")

简单解释一下:

  • ThreadPoolExecutor 搞了一堆工人(线程),默认数量是CPU核心数*5,但I/O任务你可以大胆多开点,比如设成50或100,别太过分把人家服务器搞崩就行。
  • executor.submit 把任务(fetch_url函数和URL参数)丢进池子排队,立刻返回一个Future对象(可以理解为“欠条”)。
  • as_completed 哪个任务先完成就先处理哪个,不卡顺序。

跑一下你就会发现,50个每个延迟1秒的请求,单线程要50多秒,而用20个线程的池子可能就3秒左右搞定,这就是并发的威力。

如果还想更猛:

  • 对于超级大量的请求(比如上万),可以考虑 aiohttp + asyncio 的纯异步方案,理论上比线程池开销更小、效率更高。
  • 记得配个 会话 (requests.Session) 并设置合理的 连接池适配器 (HTTPAdapter),比如调大 pool_connectionspool_maxsize,这样复用TCP连接能省掉大量握手开销。
  • 该加 超时 (timeout)重试机制 的地方也得加上,生产环境别裸奔。

总结:换并发或异步,别让程序傻等。

gevent + httplib2

直接 gevent spawn 然後 joinall 吧, 要不然直接 python3.6

gevent celery 都可以解决

在异步的前提下用 aiorequests

要不试试我的库 https://github.com/maliubiao/simple_http
还能控制并发量, 超时时间
### 异步方式
shell <br><br>In [21]: def print_it(x): <br> import pprint<br> ....: pprint.pprint(x)<br> ....: <br><br>In [22]: async_http.repeat_tasks([{"url": "<a target="_blank" href="http://www.baidu.com" rel="nofollow noopener">http://www.baidu.com</a>", "parser": print_it}])<br>{'chain': None,<br> 'chain_idx': 0,<br> 'con': &lt;socket._socketobject object at 0x2812bb0&gt;,<br> 'fd': 5,<br> 'header_only': False,<br> 'parser': &lt;function print_it at 0x283da28&gt;,<br> 'proxy': '',<br> 'random': '60804c2a0b053fbd',<br> 'recv': &lt;cStringIO.StringO object at 0x283a3e8&gt;,<br> 'redirect': 0,<br> 'res_cookie': {'BAIDUID': {'domain': '.baidu.com',<br> 'expires': 'Thu, 31-Dec-37 23:55:55 GMT',<br> 'max-age': '2147483647',<br> 'path': '/',<br> 'value': 'BCB0BBBB4312D00C88BCDC9EEAAE3726:FG=1'},<br> 'BD_LAST_QID': {'Max-Age': '1',<br> 'path': '/',<br> 'value': '16069052107084303783'},<br> 'BIDUPSID': {'domain': '.baidu.com',<br> 'expires': 'Thu, 31-Dec-37 23:55:55 GMT',<br> 'max-age': '2147483647',<br> 'path': '/',<br> 'value': 'BCB0BBBB4312D00C88BCDC9EEAAE3726'}},<br> 'res_header': {'Connection': 'Keep-Alive',<br> 'Content-Length': '215',<br> 'Content-Type': 'text/html',<br> 'Date': 'Thu, 21 May 2015 15:50:43 GMT',<br> 'Location': '<a target="_blank" href="https://www.baidu.com/'" rel="nofollow noopener">https://www.baidu.com/'</a>,<br> 'P3P': 'CP=" OTI DSP COR IVA OUR IND COM "',<br> 'Server': 'BWS/1.1',<br> 'Set-Cookie': 'BAIDUID=BCB0BBBB4312D00C88BCDC9EEAAE3726:FG=1; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com\r\nBIDUPSID=BCB0BBBB4312D00C88BCDC9EEAAE3726; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com\r\nBD_LAST_QID=16069052107084303783; path=/; Max-Age=1',<br> 'X-UA-Compatible': 'IE=Edge,chrome=1'},<br> 'res_status': {'message': 'Moved Temporarily',<br> 'protocol': 'HTTP/1.1',<br> 'status': 302},<br> 'retry': 0,<br> 'send': &lt;cStringIO.StringO object at 0x25fb8f0&gt;,<br> 'ssl': False,<br> 'start': 1432223278.489937,<br> 'status': 512,<br> 'text': '&lt;html&gt;\r\n&lt;head&gt;&lt;title&gt;302 Found&lt;/title&gt;&lt;/head&gt;\r\n&lt;body bgcolor="white"&gt;\r\n&lt;center&gt;&lt;h1&gt;302 Found&lt;/h1&gt;&lt;/center&gt;\r\n&lt;hr&gt;&lt;center&gt;pr-nginx_1-0-221_BRANCH Branch\nTime : Wed May 20 10:35:46 CST 2015&lt;/center&gt;\r\n&lt;/body&gt;\r\n&lt;/html&gt;\r\n',<br> 'url': '<a target="_blank" href="http://www.baidu.com" rel="nofollow noopener">http://www.baidu.com</a>'}<br>async_http Thu May 21 23:47:58 2015: 'acnt: 1, fcnt: 0, time: 0'<br>

要么用多线程,要么用协程咯

如果同一个域名的比较多,再用上连接池特性来加速

看看 treq 这个包?

request_future ?

gevent monkey patch 试试?

去状态化+分布式负载均衡

哇 真的是好想法…崭新的思路!

如果后端是 url 是同一个域名的,用 requests 的 session 长连接,性能会快一点点。
要不然就用 gevent 和 celery

实测这个真是聊胜于无,感觉就是每次帮你把 cookie 自动带上的语法糖而已,可能是我使用的方式不对,如果知道具体运作原理还请指教。

你要是开启过 debug 日志的话就知道如果是同一个 session 下会保持 keepalive,不会每次访问一个 url 都重新开一个新的短链接

thanks,我试一下,如果是长链接的话,省去了频繁的握手,按理说效果应该不错啊。

requests 的 session 不仅仅是 cookie 保存。 还有个特性是 keepalive 长连接,不用每次都重新建立 tcp 连接。

少年,请起,站起来吧

requests 的 session 是 keepalive 的,通过传递 session 的方式可以多个请求共享 TCP 连接。
其次,文档就有提到: http://docs.python-requests.org/en/master/user/advanced/#blocking-or-non-blocking

最后,现在问 Python 的问题已经不用带上 Python 的版本了吗???🤔

requests 周边生态是 23 兼容的

本来跪一下就想起来了 但是看到这么多大神的回复以后。 我已经起不来了

回到顶部