Исправление 429 Too Many Requests: откат, бюджеты и распределение IP

429 — это единственный блок, который точно указывает, как его исправить — если вы читаете заголовки ответа, а не просто повторяете попытку. Вот арифметика безопасной пропускной способности скрапинга.

HTTP 429 Too Many Requests — это самый честный блок, который может отправить вам веб-сайт. В отличие от 403, он называет проблему — вы слишком быстро — и часто предоставляет решение в заголовке ответа. Однако стандартная реакция — обернуть вызов в цикл повторных попыток и надеяться, что это превратит решаемую проблему темпа в медленный, блокирующий беспорядок. Это руководство рассматривает 429 как то, чем он является: математической задачей с четырьмя рычагами. Прочтите ответ, соблюдайте опубликованный лимит, правильно откатывайтесь, когда превышаете его, и распределяйте оставшуюся нагрузку по идентичностям.

Что означает 429 и когда он лжет

Ограничитель скорости считает запросы по идентичности — обычно по IP-адресу, иногда по API-ключу или cookie сессии — в пределах временного окна. Перейдите порог, и вы получите 429 вместо контента. Реализации различаются: токен-бакеты выдают фиксированное количество, которое пополняется по расписанию, скользящие окна считают за скользящий период, а не за минуту по часам, а многоуровневые системы ограничивают на мягком лимите перед жесткой блокировкой на более высоком. Какой из них вы встретите, определяет, достаточно ли короткой паузы или нужно ждать целое окно.

Теперь оговорка, которая сэкономит часы: 429 на ваш первый запрос — это не ограничение скорости. Это ответ бота, замаскированный под ограничение скорости. Широко читаемая ветка на Stack Overflow описывает именно это — первый вызов скрапера вернул страницу с надписью "Misbehaving Content Scraper Please use robots.txt Your IP has been rate limited" вместе с 429. Ничего не было превышено; сервер просто решил, что клиент — бот, и выбрал этот код. Если вы видите 429 до того, как отправили какой-либо объем, рассматривайте это как проблему обнаружения и работайте с заголовками и проверками IP в нашем руководстве по 403 forbidden errors.

Прочтите ответ, прежде чем менять код

Добросовестные серверы сообщают, когда вернуться. Retry-After содержит либо количество секунд, либо дату HTTP. Многие API добавляют X-RateLimit-Limit (потолок), X-RateLimit-Remaining (что осталось в текущем окне) и X-RateLimit-Reset (когда оно пополняется). Эти три превращают реактивные повторные попытки в проактивное соблюдение темпа: вы можете замедлиться до блока, а не после него.

import requests

r = requests.get("https://target.example/api/items", timeout=20)
print(r.status_code)
for h in ("Retry-After", "X-RateLimit-Limit", "X-RateLimit-Remaining", "X-RateLimit-Reset"):
    if h in r.headers:
        print(f"{h}: {r.headers[h]}")

# No headers at all? The limit is undocumented - measure it:
# send a slow ramp (1 req/s, then 2, then 4) and note where 429 starts.

Если ничего полезного не возвращается, измерьте лимит самостоятельно с помощью рампы: запускайте по одному запросу в секунду в течение минуты, затем два, затем четыре и записывайте скорость, при которой появляются 429. Десять минут измерений лучше недели догадок, и найденное число становится бюджетом, на котором строится все остальное.

Диаграмма полос, показывающая безопасные, предельные и вызывающие 429 интервалы запросов при ограничении скорости в 100 запросов в минуту
Вся расчет: 60 секунд, деленные на опубликованный лимит, затем добавьте буфер для сетевого джиттера.

Арифметика темпа

Возьмите задокументированный лимит и разделите. Ограничение в 100 запросов в минуту означает 60 / 100 = 0,6 секунды между запросами как абсолютный минимум — и минимум не является целью. Сетевая задержка варьируется, ваши часы и серверные не совпадают, и всплеск на границе окна может удвоить вашу кажущуюся скорость. Стремитесь к 70-80% от лимита: примерно 0,8 секунды на запрос в этом примере, что все еще дает вам 75 страниц в минуту.

Параллелизм следует из того же числа. Если вы хотите 1,25 запроса в секунду и каждый запрос занимает 2 секунды на круг, вам нужно 1,25 x 2 = 2,5 активных запроса — так что семафор 3, а не 50, на которые по умолчанию настроен ваш асинхронный код. Неограниченное асинхронное разветвление — это самая распространенная причина 429: сто корутин, запущенных одновременно, прибывают как один мгновенный всплеск, независимо от того, насколько вежливо выглядит среднее значение. Если вы скрапите с asyncio, шаблоны семафоров в асинхронном Python скрапинге с httpx и aiohttp — это решение.

Откат, который работает: экспоненциальный, ограниченный, с джиттером

Когда вы сталкиваетесь с 429, уважайте Retry-After, если он присутствует. В противном случае начните с одной секунды и удваивайте — 1, 2, 4, 8, 16 — до жесткого ограничения, чтобы сломанная цель не могла навсегда остановить вашу очередь. Затем добавьте джиттер. Без рандомизации каждый рабочий, который столкнулся со стеной в один и тот же момент, повторяет попытку в один и тот же момент, воспроизводя всплеск, вызвавший проблему.

import random, time, requests

def get_with_backoff(session, url, max_tries=6, cap=120.0):
    for attempt in range(max_tries):
        r = session.get(url, timeout=20)
        if r.status_code != 429:
            return r

        ra = r.headers.get("Retry-After", "")
        wait = float(ra) if ra.isdigit() else 2.0 ** attempt   # 1, 2, 4, 8, 16, 32
        wait = min(wait, cap)
        wait += random.uniform(0, wait * 0.3)                   # jitter: break the lockstep

        time.sleep(wait)
    raise RuntimeError(f"still 429 after {max_tries} attempts: {url}")

Еще лучше, замкните цикл. Аддитивное увеличение/мультипликативное уменьшение дает вам скрапер, который сам находит лимит и остается чуть ниже его: увеличивайте скорость, пока ответы чисты, уменьшайте ее вдвое, как только прилетает 429. Держите одного темпорегулятора на домен — лимиты зависят от хоста, и одна агрессивная цель не должна замедлять остальные сорок.

class Pacer:
    """One per domain. Additive increase, multiplicative decrease."""
    def __init__(self, rps=2.0, floor=0.2, ceiling=8.0):
        self.rps, self.floor, self.ceiling = rps, floor, ceiling

    def ok(self):          # clean response: creep faster
        self.rps = min(self.ceiling, self.rps + 0.05)

    def throttled(self):   # 429: halve immediately
        self.rps = max(self.floor, self.rps / 2)

    @property
    def gap(self):
        return 1.0 / self.rps

Распределение нагрузки: бюджет зависит от идентичности, а не от проекта

Когда вы правильно соблюдаете темп и все еще нуждаетесь в большей пропускной способности, единственный оставшийся рычаг — это идентичности. Поскольку счетчик привязан к вашему IP, N выходных IP дают вам N раз больше бюджета — арифметика настолько проста. Если сайт допускает 60 запросов в минуту на адрес, а вам нужно 1 200 страниц в минуту, это 20 одновременных выходов, работающих комфортно ниже лимита, а не один выход, работающий в двадцать раз выше него.

Это то, для чего на самом деле предназначены ротационные прокси. Ротационный шлюз назначает каждому запросу другой резидентный IP из пула более 90 миллионов адресов в более чем 200 странах, так что счетчики по IP никогда не заполняются. Два правила делают разницу между распределением нагрузки и сжиганием пула: держите скорость по IP ниже лимита даже после ротации (ротация умножает ваш бюджет, она его не убирает), и используйте липкие сессии для любого потока, который охватывает несколько запросов — вход в систему, корзина, набор результатов с разбивкой на страницы — чтобы сессия не прерывалась на полпути. Когда вам нужен один и тот же IP на несколько минут и новый после этого, компромиссы изложены в липкие против ротационных сессий.

Умножьте свой бюджет скорости с помощью резидентных IP

Панель статистики, показывающая ключевые цифры для избежания ошибок HTTP 429: минимальный интервал 600 мс, экспоненциальный откат, сокращение скорости на 50 процентов и бюджеты по IP
Четыре числа управляют всей системой. Выводите их из лимитов цели, а не из оптимизма.

Дешевле, чем больше IP: отправляйте меньше запросов

Дисциплина пропускной способности окупается дважды — меньше запросов означает меньше 429 и меньший счет, что является тем же аргументом, который мы приводим в снижении затрат на пропускную способность прокси. И если вы предпочитаете вообще не строить инфраструктуру темпа, QuantumProxies Scraper API поглощает повторные попытки, ротацию и ограничение по доменам за одним конечным точкой, которая возвращает markdown, JSON или HTML.

Часто задаваемые вопросы

Как избежать HTTP ошибки 429 too many requests в Python?

Установите преднамеренный интервал между запросами на основе опубликованного лимита цели, ограничьте параллелизм семафором, размер которого соответствует скорости, умноженной на задержку, уважайте Retry-After, когда он появляется, и повторяйте попытку с экспоненциальным откатом и джиттером. Если после этого вам нужно больше пропускной способности, распределите запросы по ротационным прокси IP, а не сокращайте интервал.

Как долго ждать после 429?

Ровно столько, сколько указано в Retry-After, если сервер его отправляет — это может быть количество секунд или дата HTTP. Без этого заголовка начните с одной секунды и удваивайте при каждом последующем 429 до ограничения в минуту или две, добавляя случайный джиттер, чтобы параллельные рабочие не повторяли попытку одновременно.

Исправляют ли прокси ошибки 429?

Они умножают ваш бюджет, они не снимают лимит. Поскольку счетчики привязаны к IP клиента, распределение запуска по многим резидентным выходам удерживает каждый адрес ниже порога. Но пул, который подвергается нагрузке в десять раз выше лимита по IP, все равно будет собирать 429 — и сжигать свою репутацию. Сначала соблюдайте темп, затем ротацию.

Является ли 429 тем же, что и бан?

Нет. 429 по замыслу временный и исчезает, когда окно сбрасывается, что отличает его от 403 блокировки идентичности. Игнорирование его неоднократно — это то, как он становится постоянным: устойчивое превышение — это именно тот сигнал, который переводит ограничение в более долгосрочный бан IP.

Почему я получаю 429 на самый первый запрос?

Потому что ничего на самом деле не было подсчитано. Некоторые серверы возвращают 429 любому клиенту, которого они считают ботом, независимо от объема — код статуса просто их выбранный ответ. Проверьте тело ответа: если оно упоминает robots.txt, скраперы или брандмауэр, исправьте свои заголовки, отпечаток TLS и выходной IP, а не ваш темп.

Относитесь к ограничениям скорости как к бюджету, который вы тратите сознательно. Измерьте потолок, работайте на уровне 70-80% от него, откатывайтесь с джиттером, когда превышаете, и покупайте больше идентичностей только после того, как темп будет правильным. Сделано в этом порядке, 429 перестают быть классом ошибок и становятся числом в конфигурационном файле.

Получите ротационные прокси и прекратите бороться с ограничениями скорости