Python aiohttp:速率限制并行请求

Python aiohttp:速率限制并行请求,python,parallel-processing,python-asyncio,aiohttp,Python,Parallel Processing,Python Asyncio,Aiohttp,API通常有用户必须遵守的速率限制。例如,让我们以每秒50个请求为例。连续请求需要0.5-1秒,因此速度太慢,无法接近该限制。但是,使用aiohttp的并行请求超过了速率限制 为了尽可能快地轮询API,需要对并行调用进行速率限制 到目前为止,我发现的示例装饰了session.get,大致如下: session.get = rate_limited(max_calls_per_second)(session.get) 这适用于连续调用。试图在并行调用中实现这一点并没有达到预期的效果 以下是一些代

API通常有用户必须遵守的速率限制。例如,让我们以每秒50个请求为例。连续请求需要0.5-1秒,因此速度太慢,无法接近该限制。但是,使用aiohttp的并行请求超过了速率限制

为了尽可能快地轮询API,需要对并行调用进行速率限制

到目前为止,我发现的示例装饰了
session.get
,大致如下:

session.get = rate_limited(max_calls_per_second)(session.get)
这适用于连续调用。试图在并行调用中实现这一点并没有达到预期的效果

以下是一些代码示例:

async with aiohttp.ClientSession() as session:
    session.get = rate_limited(max_calls_per_second)(session.get)
    tasks = (asyncio.ensure_future(download_coroutine(  
          timeout, session, url)) for url in urls)
    process_responses_function(await asyncio.gather(*tasks))
这样做的问题是,它将限制任务的排队。使用
collect
执行仍然或多或少会同时发生。两个世界中最糟糕的;-)

是的,我在这里发现了一个类似的问题,但两个答复都没有回答限制请求速率的实际问题。也仅适用于限制排队的速率


总而言之:如何限制并行
aiohttp
请求的每秒请求数?

如果我理解你的意思,你想限制同时请求数

asyncio
中有一个名为
Semaphore
的对象,它的工作原理类似于异步
RLock

semaphore = asyncio.Semaphore(50)
#...
async def limit_wrap(url):
    async with semaphore:
        # do what you want
#...
results = asyncio.gather([limit_wrap(url) for url in urls])
更新 假设我同时发出50个请求,它们都在2秒内完成。因此,它没有触及限制(每秒仅25个请求)

这意味着我应该同时发出100个请求,它们也都在2秒内完成(每秒50个请求)。但在你真正提出这些请求之前,你怎么能确定它们会完成多久呢

或者,如果您不介意每秒完成的请求,但是每秒发出的请求。你可以:

async def loop_wrap(urls):
    for url in urls:
        asyncio.ensure_future(download(url))
        await asyncio.sleep(1/50)

asyncio.ensure_future(loop_wrap(urls))
loop.run_forever()

上面的代码将每隔
1/50
秒创建一个
Future
实例。

我通过创建一个子类
aiohttp.ClientSession()
和一个基于漏桶算法的RateLimitor来解决这个问题。我使用
asyncio.Queue()
来限制速率,而不是
信号量。我只重写了
\u request()
方法。我发现这种方法更干净,因为您只将
session=aiohttp.ClientSession()
替换为
session=ThrottledClientSession(rate\u limit=15)

class ThrottledClientSession(aiohttp.ClientSession):
“”“从aiohttp.ClientSession继承的速率限制客户端会话类”
最小睡眠=0.1
定义初始值(自身,速率限制:浮点=无,*args,**kwargs)->无:
super()
自费率限制=费率限制
self.\u fillerTask=无
self.\u队列=无
self.\u start\u time=time.time()
如果费率限制!=无:
如果费率限制列表:
如果是自费率限制!=无:
返回最大值(1/self.rate\u limit,self.MIN\u SLEEP)
一无所获
异步def关闭(自)->无:
“”“关闭速率限制器的“铲斗填充器”任务”“”
如果是自己的话。_fillerTask!=无:
self.\u fillerTask.cancel()
尝试:
等待异步IO。等待(self.\u fillerTask,超时=0.5)
除asyncio.TimeoutError作为错误外:
打印(str(err))
等待super().关闭()
异步def_填充器(自身,速率限制:浮动=1):
“”“填充漏桶算法的填充任务”“”
尝试:
如果self.\u queue==无:
返回
自费率限制=费率限制
睡眠=自我
更新的_at=time.monotonic()
分数=0
额外增量=0
对于范围内的i(0,self.\u queue.maxsize):
self.\u queue.put\u nowait(一)
尽管如此:
如果不是self.\u queue.full():
现在=时间。单调()
增量=速率限制*(现在更新)
分数+=增量%1
额外增量=分数//1
items_2_add=int(最小值(self.\u queue.maxsize-self.\u queue.qsize(),int(增量)+额外增量))
分数=分数%1
对于范围内的i(0,项2添加):
self.\u queue.put\u nowait(一)
更新时间=现在
等待asyncio.sleep(睡眠)
除asyncio.Cancelled错误外:
打印('已取消')
除异常作为错误外:
打印(str(err))
异步定义允许(自)->无:
如果是自组。_队列!=无:
#调试
#如果self.\u start\u time==无:
#self.\u start\u time=time.time()
等待自我。_queue.get()
self.\u queue.task\u done()
一无所获
异步定义请求(self、*args、**kwargs):
“”“已阻止请求()”“”
等待自我
return wait super()。_请求(*args,**kwargs)
```

我喜欢@sraw用asyncio来解决这个问题,但他们的回答对我来说并不合适。因为我不知道我的下载调用是否会比速率限制快或慢,所以我希望在请求速度慢时可以选择并行运行多个,在请求速度非常快时一次运行一个,这样我的速率限制总是正确的

我通过使用一个队列来实现这一点,该队列中的生产者以速率限制生成新任务,然后许多消费者要么都在等待下一个作业(如果他们速度快),要么在队列中备份工作(如果他们速度慢),并将以处理器/网络允许的速度运行:

导入异步IO
从日期时间导入日期时间
异步def下载(url):
#下载什么的
任务时间=1/10
等待asyncio.sleep(任务时间)
结果=datetime.now()
返回结果,url
异步def生产者(队列、URL、最大每秒):
对于url中的url:
等待队列。放置(url)
等待asyncio.sl