Что именно не так с моими асинхронными запросами?
По вводным: у меня есть бот который взаимодействует с api.
В коде что я представил:
По вводным: 1. Класс Requester - моя попытка переделать запросы на асинхронные с ограничением 20 в сек.
Сейчас ситуация такая: 2. Функция которая ждет от api список id и повторяет запрос несколько раз если сервис возвращает не полный список в csv.
По вводным: теперь я вижу какое то странное поведение - запрос может отправиться сразу, а может зависнуть на несколько минут. Если запрос ожидает 10 id все нормально работает, а если должно прийти 100 id - то я могу ждать целую минуту (до переделок нормально работало)
Сейчас ситуация такая: что я сделал не так? Как будто запрос при 100 id заклинивает. Может потому что при 10 сервис сразу грузит, а при 100 может понужнобиться вторая попытка запроса.
class Requester: def __init__(self, max_per_second: int = 20): self.limiter = AsyncLimiter(max_per_second, 1.0) self.client = httpx.AsyncClient(timeout=30.0) async def get(self, url, **kwargs): async with self.limiter: response = await self.client.get(url, **kwargs) print(response.text) return response.text async def post(self, url, **kwargs): async with self.limiter: response = await self.client.post(url, **kwargs) print(response.text) return response.text async def close(self): await self.client.aclose()
async def get_call_ids(self, phonelist_id, country, need_len): url = (f"https://zvonok.com/manager/cabapi_external/api/v1/phonelists/call_ids_by_phonelist/" f"?public_key={s.d[f'zvonok_api_{country}']}" f"&campaign_id={s.d[f'zvonok_id_{country}']}" f"&phonelist_id={phonelist_id}") logger.info(f"Получение call_ids. {phonelist_id} {country} {url}") tryes = 15 while tryes: try: print('отправка получения ид') resp = await self.requester.get(url) print('ид получены') if resp: f = StringIO(resp) data = [] reader = csv.DictReader(f) for row in reader: data.append([row['phoneval'], row['ats_call_id']]) if len(data) < need_len: if tryes == 1: logger.info(f"Возврат меньшего количества номеров {len(data)}") return data logger.info(f"В ответе не все номера - {len(data)} из {need_len}") await sleep(3) raise Exception except Exception as e: traceback.print_exc() tryes -= 1 return False
Опишите проблему, и специалист поможет с настройкой, исправлением ошибки или доработкой сайта. Подберём понятный план работ без лишней переписки.
Пока нет других ответов. Будьте первым, кто поможет автору.
Ответить на вопрос
По описанию проблема похожа на смешение rate limit, повторных запросов и ожидания ответа API без нормальных timeout/backoff. Если при 10 id все быстро, а при 100 id запрос может “зависнуть”, то вероятно API обрабатывает большие выборки дольше, возвращает неполный CSV, а ваш код держит семафор/лимитер или сессию так, что следующие попытки ждут не там, где вы ожидаете.
Для aiohttp/httpx важно иметь одну ClientSession на пачку запросов, явный timeout, ограничение параллелизма и понятный retry. AsyncLimiter ограничивает частоту входа в блок, но не ограничивает количество одновременно висящих запросов, если API отвечает долго. Поэтому часто нужен еще asyncio.Semaphore.
import asyncio import aiohttp from aiolimiter import AsyncLimiter class Requester: def __init__(self, max_per_second=20, concurrency=10): self.limiter = AsyncLimiter(max_per_second, 1) self.sem = asyncio.Semaphore(concurrency) self.timeout = aiohttp.ClientTimeout(total=60, connect=10) async def get(self, session, url, params=None): async with self.sem: async with self.limiter: async with session.get(url, params=params, timeout=self.timeout) as resp: resp.raise_for_status() return await resp.text() async def run_many(self, jobs): async with aiohttp.ClientSession(timeout=self.timeout) as session: return await asyncio.gather(*[ self.get(session, url, params) for url, params in jobs ])
Если API для 100 id сначала готовит результат, а потом возвращает неполный список, лучше не делать слепые повторы без паузы. Добавьте backoff и логируйте размер ответа, статус, время выполнения и номер попытки.
for attempt in range(1, 5): started = time.monotonic() text = await requester.get(session, url, params) print('attempt', attempt, 'seconds', time.monotonic() - started, 'bytes', len(text)) if is_complete(text): return text await asyncio.sleep(min(2 ** attempt, 20))
Еще проверьте, нет ли ошибки “создаю новую сессию на каждый запрос”. Это резко ухудшает скорость. И не парсите тяжелый CSV синхронно внутри event loop, если он большой: вынесите CPU-тяжелую обработку в отдельный поток или делайте после получения всех ответов.
Вывод: разделите rate limit и concurrency, добавьте timeout, одну ClientSession, backoff и подробные логи. Тогда станет видно, где задержка: API долго готовит 100 id, ваш retry ждет неполный результат или код блокирует event loop обработкой CSV.