我有一个应用程序,它向Web API发出数以百万计的请求,目前我的工作方式有效,但效果不佳,而且笨重。我现在正在重写它,但是基本上,我有多个类需要向API发出请求,并且它们需要同时运行。 API约束只允许我创建~10 requests per second per API token
,但是我可以创建很多tokens
。
顺便说一句,几个月前,我确实向this very similair but less involved提出了一个问题,此后这个项目一直处于停滞状态,而且自问这个问题以来,我学到了很多东西。所以看看你自己的判断力哈哈
目前,(旧方法)我已经为每个类分配了自己的令牌集以供使用。就我的想法而言,这对于我的情况而言效率低下,为什么不让所有类都调用一个管理所有令牌的Singleton,这样就不会经常使用它们并对每个人执行所有请求,然后在响应中使用回调
我一直在进行一些主要的挖掘和阅读工作,在经历了许多头痛之后,我在以下资源之间拼凑了一个可以工作的原型:
所以这是我想出的:
import json
import uvloop
import aiohttp
import AsyncLeakyBucket
from pprint import pprint
from aiohttp import ClientSession
from itertools import cycle
loop = uvloop.new_event_loop()
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
class API(type, metaclass=Singleton):
def __init__(self, base_url):
super(API, self).__init__()
self._tokens = ('list', 'of', 'tokens')
num_tokens = len(self._tokens)
self._tokens = cycle(self._tokens)
self._base_url = base_url
num_concurrent_per_token = 10
total_concurrent = num_concurrent_per_token*num_tokens
self._bucket = AsyncLeakyBucket(10*total_concurrent)
self._queue = asyncio.PriorityQueue()
asyncio.run(_start_workers(total_concurrent))
async def _start_workers(self, num):
session = asyncio.get_event_loop().run_until_complete(aiohttp.ClientSession())
loop.run_until_complete(asyncio.gather(
*(_get_requests(session, queue) for _ in range(num))
))
async def _get_request(self, session, queue):
api_headers = {
'Accept': "application/json",
'Authorization': "Auth {}"
}
while True:
url_ext, callback = await queue.get()
with self._bucket:
try:
async with session.get(f"{self._base_url}{url_ext}",
headers=api_headers.format(next(self.tokens))) as response:
status = response.status
data = await response.read()
try:
data = json.loads(r)
except json.decoder.JSONDecodeError:
print(f"Error loading {data} as JSON")
#return
except aiohttp.ClientError as e:
print(f"Error retrieving request: {e}")
#return
queue.task_done()
asyncio.create_task(callback(data))
async def get(self, url_extension, callback, pri=1):
await self._queue.put(priority, (url_ext, callback))
async def request_done(data):
pprint(data)
a = API('https://jsonplaceholder.typicode.com/')
for num in range(500):
a.get(f"/comments/{num}", request_done)
我遇到错误,无法测试此代码的其余部分:
Traceback (most recent call last):
File "api.py", line 73, in <module>
a = API('https://jsonplaceholder.typicode.com/')
File "api.py", line 15, in __call__
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
TypeError: type.__new__() takes exactly 3 arguments (1 given)
从字面上看,我对Singleton和线程的了解有限,因此整个班级都写了这堂课,现在我被困住了,不想把它弄得太深。我所知道的并不是很多,因此希望代码和我的解释足以使我理解我的意图。
顺便说一句,我只是使用https://jsonplaceholder.typicode.com/来测试基本功能,因为您可能会说其他API需要标头和完全不同的地址...
一如既往,在此先感谢您,并希望能收到社区的反馈。如果有任何疑问,我在这里!