Двойная выгода

Динамич.+10 ГБ за каждые 50 ГБ

Статич.За 30 дн.: +7 дн. и 500 МБ в подарок

Купить >

Сообщение о безопасностиФишинговый сайт мошенничества (rola-ip.com ) подделка нашего старого официального сайта. Единственный официальный сайт — rola-ip.co(обратите внимание на.co(суффикс). Не производите выплаты на других доменах, чтобы избежать финансовых потерь!

Повторение запросов в Python requests: настройка retry, таймаутов и прокси

Daniel Zhao

13 окт. 2026 г. · Руководства · 18 мин. чтения

Повторение запросов в Python requests обычно настраивают через Session, HTTPAdapter и urllib3.Retry. Эта связка позволяет ограничить число повторов, выбрать HTTP-методы и коды ответа, задать задержку и учитывать Retry-After. Она подходит для временных сбоев соединения и части ответов 5xx. Ошибку авторизации, неверный адрес прокси или проблему с сертификатом нужно сначала диагностировать: дополнительные попытки сами по себе их не исправят.

Такой подход полезен для интеграций с API, мониторинга доступности и сбора открытых данных с разрешённых ресурсов. Например, задача периодически получает JSON из каталога поставщика: при кратком сбое 503 она может повторить GET, при 429 — подождать, а при 401 — остановиться и сообщить об ошибке доступа. Разные причины отказа требуют разных действий.

Ниже — законченные примеры клиента, локального сервера с управляемыми ошибками и проверки прокси. Используйте их для ресурсов, к которым у вас есть право доступа, с учётом условий API и ограничений частоты запросов.

Главное о повторных запросах

  • total=4 — до четырёх повторов после первоначального запроса, то есть до пяти попыток в пределах одной цепочки повторов.
  • timeout=(5, 20) задаёт таймаут соединения и ожидания данных. Это не общий лимит времени для задачи или загрузки файла.
  • При 429 и других ответах с Retry-After учитывайте указанный сервером срок. Если ждать слишком долго, завершите задачу или перенесите её, не сокращая ожидание ради раннего повтора.
  • GET обычно можно повторить. Для POST, платежей, заказов и других операций записи требуется явный контракт идемпотентности.
  • allowed_methods ограничивает повторы по статусам и соответствующим ошибкам чтения, но не запрещает абсолютно все повторы на стадии соединения для других методов.
  • read=4 не гарантирует повтор любого запроса, оборвавшегося при чтении тела ответа.
  • При ошибке прокси сначала проверьте шлюз, протокол и авторизацию. Session не закрепляет внешний IP сам по себе.

Что означает retry в Requests

Retry — повторная отправка запроса после сбоя, который клиент считает временным. Рабочая политика повторов определяет пять вещей: какие ошибки повторять, сколько раз, с какой задержкой, нужно ли сохранять прокси-сессию и что делать после окончательной неудачи.

Простой бесконечный цикл не решает эти задачи. Во время сбоя он способен увеличить нагрузку на уже перегруженный сервис и расходовать трафик без полезного результата.

Передача числа в HTTPAdapter(max_retries=3) не заменяет подробную политику обработки HTTP-ответов. Для выбора кодов, методов и задержек передавайте объект Retry. Различие описано в справочнике HTTPAdapter библиотеки Requests.

Как настроить повторение GET-запросов через HTTPAdapter

1. Подготовьте окружение

Для примеров используйте Python 3.14 или новее. Команды ниже фиксируют версии Requests и urllib3, на которые ориентируются настройки в статье.

Windows PowerShell:

py -m venv .venv
.\.venv\Scripts\python.exe -m pip install requests==2.34.2 urllib3==2.7.0

Linux и macOS:

python3 -m venv .venv
source .venv/bin/activate
python -m pip install requests==2.34.2 urllib3==2.7.0

В дальнейших командах python означает интерпретатор этого окружения. В PowerShell без активации используйте .\.venv\Scripts\python.exe. Все файлы примеров сохраните в одной папке.

Область проверки: примеры кода в этой статье не выполнялись. Выводы в учебных блоках ниже обозначены как ожидаемые, а не как результаты измерений. Реальное подключение через Rola IP, работа внешних API, macOS и Linux здесь не проверялись. Локальный сервер позволяет самостоятельно проверить поведение, не создавая тестовые ошибки на чужом сайте.

2. Создайте клиент с ограниченными повторами

Сохраните как httpadapter_demo.py:

import sys

import requests
from requests.adapters import HTTPAdapter
from urllib3.util import Retry


def build_session() -> requests.Session:
    retries = Retry(
        total=4,
        connect=4,
        read=4,
        status=4,
        other=0,
        allowed_methods=frozenset({"GET", "HEAD", "OPTIONS"}),
        status_forcelist={429, 500, 502, 503, 504},
        backoff_factor=0.25,
        backoff_jitter=0.1,
        respect_retry_after_header=True,
        raise_on_status=False,
    )
    adapter = HTTPAdapter(
        max_retries=retries,
        pool_connections=10,
        pool_maxsize=10,
    )
    session = requests.Session()
    session.trust_env = False
    session.mount("http://", adapter)
    session.mount("https://", adapter)
    return session


def main():
    path = sys.argv[1] if len(sys.argv) > 1 else "/flaky"
    if path not in {"/flaky", "/rate-limit", "/missing"}:
        raise SystemExit("Укажите /flaky, /rate-limit или /missing")

    with build_session() as session:
        response = session.get(
            "http://127.0.0.1:8877" + path,
            timeout=(5, 20),
            allow_redirects=False,
        )
        print("История повторов:", [h.status for h in response.raw.retries.history])
        print("Итоговый HTTP-статус:", response.status_code)
        response.raise_for_status()
        print("JSON:", response.json())


if __name__ == "__main__":
    main()

Клиент обращается к локальному серверу из следующего раздела. Его нужно запустить первым.

raise_on_status=False позволяет получить последний HTTP-ответ после исчерпания повторов по статусу. Затем raise_for_status() явно сообщает об итоговой ошибке 4xx или 5xx. Это не означает, что все сетевые исключения превращаются в обычный ответ.

trust_env=False изолирует пример от прокси, заданных в окружении. Настройка также влияет на использование .netrc и переменных окружения для CA. В рабочем приложении учитывайте корпоративные сертификаты: при необходимости передайте корректный путь через verify, не отключайте проверку TLS.

Как воспроизвести 503, 429 и 404 на своём компьютере

reproducible-test-environment

1. Создайте локальный сервер

Сохраните как flaky_server.py:

import json
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from threading import Lock

COUNTS = {"flaky": 0, "rate-limit": 0}
LOCK = Lock()


class Handler(BaseHTTPRequestHandler):
    def send_json(self, status, payload, headers=None):
        body = json.dumps(payload).encode("utf-8")
        self.send_response(status)
        self.send_header("Content-Type", "application/json; charset=utf-8")
        self.send_header("Content-Length", str(len(body)))
        for name, value in (headers or {}).items():
            self.send_header(name, value)
        self.end_headers()
        self.wfile.write(body)

    def do_GET(self):
        if self.path == "/reset":
            with LOCK:
                COUNTS.update({"flaky": 0, "rate-limit": 0})
            self.send_json(200, {"ok": True})
            return

        if self.path == "/flaky":
            with LOCK:
                COUNTS["flaky"] += 1
                attempt = COUNTS["flaky"]
            status = 503 if attempt <= 2 else 200
            self.send_json(status, {"ok": status == 200, "attempt": attempt})
            return

        if self.path == "/rate-limit":
            with LOCK:
                COUNTS["rate-limit"] += 1
                attempt = COUNTS["rate-limit"]
            if attempt == 1:
                self.send_json(
                    429,
                    {"ok": False, "attempt": attempt},
                    {"Retry-After": "1"},
                )
            else:
                self.send_json(200, {"ok": True, "attempt": attempt})
            return

        self.send_json(404, {"ok": False, "message": "Ресурс не найден"})

    def log_message(self, fmt, *args):
        print(f"Сервер: {self.command} {self.path} -> {args[1]}", flush=True)


if __name__ == "__main__":
    server = ThreadingHTTPServer(("127.0.0.1", 8877), Handler)
    print("Сервер слушает http://127.0.0.1:8877", flush=True)
    try:
        server.serve_forever()
    except KeyboardInterrupt:
        pass
    finally:
        server.server_close()

Сервер слушает только loopback-интерфейс. /flaky возвращает 503 дважды, затем 200; /rate-limit — сначала 429 с Retry-After: 1, затем 200. Счётчики общие для всех обращений к этому процессу, поэтому параллельные эксперименты могут менять номер попытки.

/reset предназначен исключительно для сброса учебного стенда. Изменение состояния через GET здесь не является рекомендацией для проектирования рабочего API.

2. Откройте два терминала

В первом запустите сервер:

python flaky_server.py

Во втором сбросьте счётчики и выполните клиент. Для Linux и macOS:

curl --fail --show-error http://127.0.0.1:8877/reset
python httpadapter_demo.py /flaky

В Windows PowerShell используйте:

Invoke-RestMethod -Uri http://127.0.0.1:8877/reset
.\.venv\Scripts\python.exe httpadapter_demo.py /flaky

Ожидаемый результат, не журнал выполненного теста:

История повторов: [503, 503]
Итоговый HTTP-статус: 200
JSON: {'ok': True, 'attempt': 3}

Перед каждым следующим экспериментом снова вызовите /reset. Затем проверьте:

python httpadapter_demo.py /rate-limit
python httpadapter_demo.py /missing
Проверка Критерий ожидаемого поведения
/flaky История [503, 503], итог 200, поле attempt равно 3
/rate-limit История [429], итог 200, поле attempt равно 2; между ответом 429 и повтором проходит не менее секунды
/missing История пустая, итог 404; программа завершает работу с HTTPError

Если /flaky сразу возвращает 200, сначала проверьте сброс счётчика. Если соединение отклонено, убедитесь, что первый терминал продолжает обслуживать порт 8877. Если порт занят, измените его одновременно в сервере, клиенте и командах сброса.

Как исправить ошибки Python requests: определите причину отказа

Чтобы исправить ошибки Python requests, сначала отделите сетевой сбой от HTTP-ошибки и неверного содержимого ответа. Код 503, исключение SSLError и страница авторизации со статусом 200 требуют разных проверок.

Уровень Признак Что проверить Действие и повторная проверка
DNS и сеть ConnectionError, ошибка разрешения имени Домен, DNS, доступность сети Исправить адрес или сеть; повторить один разрешённый запрос
Установление соединения ConnectTimeout Доступность узла, порт, маршрут При временном сбое — ограниченные повторы; сравнить частоту отказов
Чтение ReadTimeout или обёрнутый ConnectionError Скорость ответа, размер тела, стадию сбоя Настроить таймаут и восстановление; проверить получение полного тела
TLS SSLError Цепочку сертификатов, системное время, CA Исправить доверие; подтвердить соединение с включённой проверкой TLS
Прокси-шлюз ProxyError, отказ в соединении Хост, порт, протокол, доступность шлюза Исправить конфигурацию; отдельно проверить выход через прокси
Авторизация прокси 407 Логин, пароль, список разрешённых IP, кодирование Исправить данные доступа; убедиться, что 407 больше не возникает
Запрос или права 400, 401, 403, 404, 422 Параметры, права, URL и контракт API Исправить запрос; не отправлять его повторно без изменений
Ограничение частоты 429 Retry-After, квоты, параллелизм Подождать или перенести задачу; проверить соблюдение лимита
Сбой сервера 500, 502, 503, 504 Доступность сервиса и длительность сбоя Повторить в пределах бюджета; при продолжении отказов приостановить поток
Данные 200 с HTML вместо JSON, пустые поля Content-Type, схему, состояние авторизации Отклонить неверный результат; проверить содержимое после исправления

Почему timeout не ограничивает длительность всей задачи

В timeout=(5, 20) первое значение относится к установлению соединения, второе — к ожиданию данных при чтении. Read timeout ограничивает период ожидания данных, а не суммарную длительность загрузки. Ответ, поступающий небольшими порциями, может загружаться гораздо дольше 20 секунд.

DNS, несколько адресов узла, прокси, повторы и паузы также влияют на полное время. Для задачи с жёстким сроком нужен отдельный механизм отмены или планировщик, а перед новым повтором — проверка остатка бюджета. Дополнительный разбор есть в статье о настройке Python Requests timeout, на английском.

Почему HTTP 200 недостаточно для признания успеха

Проверьте тип ответа, возможность разбора JSON, обязательные поля и бизнес-статус. Для HTML убедитесь, что получена нужная страница, а не форма входа или сообщение об ограничении доступа. Успех соединения и успех бизнес-операции — разные события; оба стоит отражать в мониторинге.

Какие HTTP-запросы безопасно повторять

Операция Рекомендация Ограничение
GET, HEAD, OPTIONS Обычно допустим автоматический повтор Конкретный API не должен неожиданно выполнять запись
PUT, DELETE Проверить контракт API Идемпотентность по HTTP-семантике не заменяет проверку фактических побочных эффектов
POST, создание записей и заданий По умолчанию не повторять Возможны дубликаты
Платёж, заказ, отправка сообщения Не повторять при неопределённом результате Нужны идемпотентность, идентификатор операции и проверка её состояния

Если клиент не получил ответ, сервер всё равно мог завершить запись. Добавление POST в allowed_methods не делает такую операцию безопасной. Используйте Idempotency-Key или эквивалентную дедупликацию, только если API явно поддерживает этот механизм; одинаковая логическая операция должна сохранять один и тот же ключ в допустимый сервером срок.

httpadapter-retry-output

Ручной цикл повторов: когда нужен собственный контроль

Ручной цикл удобен, если решение зависит от логики приложения. Ниже он повторяет GET при выбранных 5xx и части сетевых исключений.429, ошибки TLS и прокси не повторяются автоматически. Если сервер передал Retry-After вместе с повторяемой 5xx-ошибкой, цикл останавливается: ожидание и перенос задачи должен обработать вызывающий код. Таким образом, пример не отправляет запрос раньше указанного срока.

Сохраните как basic_retry.py:

import random
import time

import requests

RETRYABLE_STATUSES = {500, 502, 503, 504}


def get_with_basic_retry(url: str, attempts: int = 3) -> requests.Response:
    if attempts < 1:
        raise ValueError("Число попыток должно быть не меньше 1")

    last_error = None
    with requests.Session() as session:
        session.trust_env = False
        for attempt in range(1, attempts + 1):
            try:
                response = session.get(
                    url, timeout=(5, 20), allow_redirects=False
                )
                if response.status_code not in RETRYABLE_STATUSES:
                    response.raise_for_status()
                    return response

                if response.headers.get("Retry-After") is not None:
                    response.close()
                    raise RuntimeError(
                        "Получен Retry-After: автоматический повтор остановлен. "
                        "Проверьте время ожидания и перенесите задачу."
                    )

                last_error = requests.HTTPError(
                    f"Временная HTTP-ошибка: {response.status_code}",
                    response=response,
                )
                response.close()
            except requests.exceptions.SSLError:
                raise
            except requests.exceptions.ProxyError:
                raise
            except (
                requests.exceptions.ConnectTimeout,
                requests.exceptions.ReadTimeout,
                requests.exceptions.ConnectionError,
            ) as exc:
                last_error = exc

            if attempt < attempts:
                delay = 0.5 * (2 ** (attempt - 1)) + random.uniform(0, 0.1)
                time.sleep(delay)

    raise RuntimeError(
        f"Запрос не выполнен за {attempts} попытки"
    ) from last_error


if __name__ == "__main__":
    response = get_with_basic_retry("http://127.0.0.1:8877/flaky")
    print("Итоговый HTTP-статус:", response.status_code)
    print("JSON:", response.json())

После сброса локального сервера выполните:

python basic_retry.py

Ожидается 200 на третьей попытке. attempts=3 здесь означает три попытки всего, в отличие от Retry(total=3), где разрешены три повтора сверх первоначального запроса. При attempts=0 ожидается ValueError; при двух попытках после сброса /flaky — итоговый RuntimeError.

SSLError и ProxyError перехватываются раньше общего ConnectionError, поскольку относятся к более конкретным типам. Без такого порядка ошибки сертификата или авторизации могут ошибочно попасть в общий повтор.

Цикл не реализует строгий общий deadline, уведомления или восстановление частично загруженного файла. В рабочем журнале не сохраняйте полный текст исключения без фильтрации: он может содержать URL с чувствительными параметрами. Для повторяемых действий задавайте разумное небольшое число попыток, а не произвольно большое значение.

basic-retry-loop-output

Что означают параметры Retry

Параметр Значение в примере Назначение и граница действия
total 4 Общий бюджет повторов; первоначальная попытка в него не входит
connect 4 Ошибки установления соединения, когда запрос обычно ещё не дошёл до сервера
read 4 Подходящие ошибки чтения внутри пути повторов urllib3
status 4 Бюджет повторов по HTTP-статусу
other 0 Не повторять автоматически ошибки других категорий
allowed_methods GET, HEAD, OPTIONS Ограничение методов для повторов по статусам и соответствующим ошибкам чтения
status_forcelist 429, 500, 502, 503, 504 Статусы, выбранные для автоматического повтора
backoff_factor 0.25 Коэффициент экспоненциальной задержки
backoff_jitter 0.1 Добавка случайной задержки, до 0.1 секунды
respect_retry_after_header True Учитывать Retry-After для поддерживаемых ответов
raise_on_status False Вернуть последний ответ после исчерпания бюджета по статусу

Бюджеты connect, read и status не складываются в 12 дополнительных запросов: одновременно действует total. Повторы при наличии Retry-After также могут определяться встроенными правилами urllib3, а не только status_forcelist. Если нужен строго закрытый список статусов, проверьте логику установленной версии.

pool_maxsize=10 не следует считать универсальным ограничителем числа параллельных запросов: при стандартном pool_block=False размер пула не является жёстким лимитом конкуренции. Управляйте числом worker-процессов или потоков и очередью отдельно.

Для наблюдаемости используйте response.raw.retries.history, счётчик приложения и общее время. История последнего ответа не заменяет журнал всей задачи с её внешними повторами и перенаправлениями.

Почему HTTPAdapter не повторяет каждый ReadTimeout

Requests получает ответ от urllib3, а затем может читать тело уже на более высоком уровне. Если ошибка произошла внутри механизма повторов Adapter, бюджет read может примениться. Если соединение оборвалось во время последующего чтения тела, автоматический повтор всей операции может уже не выполняться.

Кроме того, ошибка чтения иногда выходит наружу как ConnectionError, а не отдельный ReadTimeout. Поэтому read=4 не означает «любой оборванный файл будет скачан заново ещё четыре раза».

Если приложение повторяет операцию целиком, учитывайте его попытки вместе с Adapter в общем бюджете. Для больших файлов рассмотрите возобновление через Range, если сервер поддерживает его, и проверяйте идентичность ресурса, например по ETag. Повторная загрузка с начала увеличивает объём трафика и может смешать разные версии файла без дополнительной проверки.

Экспоненциальная задержка, jitter и Retry-After

Экспоненциальная задержка увеличивает паузу после последовательных сбоев. Jitter добавляет случайный сдвиг, чтобы множество worker-процессов не повторяло запросы одновременно.

Для обычной цепочки ошибок в urllib3 2.7.0, без Retry-After и jitter, при backoff_factor=0.25 первая задержка составляет 0 секунд, следующие — примерно 0.5, 1.0 и 2.0 секунды. Ручной цикл выше начинается с 0.5 секунды: это другой алгоритм, его интервалы не нужно приписывать Adapter.

backoff_max ограничивает вычисленный экспоненциальный backoff, включая jitter. Он не является общим deadline и не задаёт универсальный предел ожидания Retry-After. Точные правила параметров приведены в документации urllib3.Retry; сверяйте их с установленной версией.

Что делать, если Retry-After превышает оставшееся время

Остановите задачу или перенесите её. Если осталось 10 секунд, а сервер просит подождать 60, повтор через 10 секунд не соблюдает его указание.

Согласно определению Retry-After в RFC 9110, значение может содержать секунды или HTTP-дату. Для дат важна корректность системного времени. Заголовок встречается не только при 429: сервер может прислать его, например, с 503.

Если ожиданием управляет ваше приложение, сохраните следующий вспомогательный модуль как retry_after.py:

import math
from datetime import datetime, timezone
from email.utils import parsedate_to_datetime


def retry_after_seconds(response) -> float | None:
    value = response.headers.get("Retry-After")
    if value is None or not value.strip():
        return None
    value = value.strip()

    if value.isascii() and value.isdigit():
        seconds = float(value)
        if not math.isfinite(seconds):
            raise ValueError("Retry-After содержит слишком большое число")
        return seconds

    try:
        retry_at = parsedate_to_datetime(value)
        if retry_at.tzinfo is None:
            retry_at = retry_at.replace(tzinfo=timezone.utc)
        return max(
            0.0,
            (retry_at - datetime.now(timezone.utc)).total_seconds(),
        )
    except (TypeError, ValueError, OverflowError):
        return None


if __name__ == "__main__":
    import requests

    example = requests.Response()
    example.headers["Retry-After"] = "60"
    print("Ожидание в секундах:", retry_after_seconds(example))

Команда python retry_after.py должна показать 60.0. Это демонстрация разбора синтетического заголовка, а не обращение к серверу.

Отсутствующее или неразбираемое значение даёт None; числовое значение, превращающееся в бесконечность, вызывает ValueError. Вызывающий код должен явно выбрать остановку, перенос или консервативную локальную политику. Даже конечное, но очень большое число нельзя без проверки передавать в sleep: сначала сравните его с допустимым ожиданием и сроком задачи.

Эта функция не подменяет внутренний разбор заголовка в urllib3. Для полностью собственного управления ожиданием отключите Adapter-повторы через max_retries=0 и задайте одну политику на уровне приложения. В варианте с Adapter ожидание выполняет urllib3.

retry-after-output

Ошибка прокси в Python requests: как найти источник

Ошибка прокси в Python requests не всегда означает проблему с выходным IP. Причина может находиться на участке до шлюза, в авторизации, в TLS-туннеле или на целевом сервере.

Симптом Порядок проверки Что делать дальше
ProxyError или connection refused Адрес→порт→протокол→доступность шлюза Исправить конфигурацию; повторять только подтверждённые временные сбои
407 Полный логин→пароль→IP-allowlist→кодирование спецсимволов Исправить авторизацию; смена выхода обычно не помогает
Проверка выхода успешна, цель возвращает 403 Права→параметры запроса→правила ресурса→условия доступа Исправить запрос или согласовать доступ; не объявлять любой 403 плохим IP
429 Retry-After→квота→частота→число одновременных запросов Снизить нагрузку; не менять выходы ради обхода лимита
502, 503, 504 Доступность шлюза и цели, журналы, стадия сбоя Ограниченные повторы и остановка при длительном отказе

При HTTPS-цели отказ авторизации во время CONNECT может проявиться как ProxyError, без доступного приложению обычного ответа 407. Поэтому отсутствие response.status_code == 407 не исключает ошибку учётных данных.

Прокси не исправляет состояние аккаунта, Cookie, параметры запроса и правила целевого сайта. Для диагностики сначала проверяйте транспорт, затем доступ к разрешённому API и только после этого — содержимое бизнес-ответа.

requests-session-proxy-configuration

Как использовать Rola IP вместе с политикой повторов

Rola IP предоставляет путь соединения через прокси; клиентское приложение управляет повторами, ожиданием и проверкой результата. Для регионального мониторинга или разрешённого сбора открытых данных эти задачи дополняют друг друга. Прокси не устраняет неверные полномочия API или сбой исходного сервера.

1. Получите параметры подключения

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

Если нужны страна, город или сохранение выхода, используйте справочник параметров Rola IP, на английском. Не составляйте формат параметров по догадке: возможности зависят от выбранного продукта.

2. Задайте переменные окружения

Замените все четыре значения собственными данными. proxy.example и 1000 — демонстрационные значения, а не адрес и порт Rola IP.

Linux и macOS:

export ROLA_PROXY_HOST="proxy.example"
export ROLA_PROXY_PORT="1000"
export ROLA_PROXY_USERNAME="replace-with-console-username"
export ROLA_PROXY_PASSWORD="replace-with-console-password"

Windows PowerShell:

$env:ROLA_PROXY_HOST="proxy.example"
$env:ROLA_PROXY_PORT="1000"
$env:ROLA_PROXY_USERNAME="replace-with-console-username"
$env:ROLA_PROXY_PASSWORD="replace-with-console-password"

Не сохраняйте реальные секреты в Git, статье и общих журналах. Учитывайте историю команд терминала; в рабочем окружении используйте принятый в вашей организации механизм передачи секретов.

rola-ip-quick-start

3. Проверьте соединение без автоматических повторов

Сохраните как proxy_check.py рядом с httpadapter_demo.py:

import ipaddress
import os
from urllib.parse import quote

import requests
from requests.adapters import HTTPAdapter


def build_proxy_url() -> str:
    names = (
        "ROLA_PROXY_HOST", "ROLA_PROXY_PORT",
        "ROLA_PROXY_USERNAME", "ROLA_PROXY_PASSWORD",
    )
    if any(not os.environ.get(name) for name in names):
        raise ValueError("Не заданы параметры прокси")
    host = os.environ["ROLA_PROXY_HOST"]
    port = int(os.environ["ROLA_PROXY_PORT"])
    if not 1 <= port <= 65535:
        raise ValueError("Некорректный порт")
    if any(c.isspace() or c in "/@?#" for c in host):
        raise ValueError("Некорректный хост")
    username = quote(os.environ["ROLA_PROXY_USERNAME"], safe="")
    password = quote(os.environ["ROLA_PROXY_PASSWORD"], safe="")
    return f"http://{username}:{password}@{host}:{port}"


def main():
    try:
        proxy_url = build_proxy_url()
        with requests.Session() as session:
            session.trust_env = False
            session.mount("https://", HTTPAdapter(max_retries=0))
            response = session.get(
                "https://api64.ipify.org?format=json",
                proxies={"http": proxy_url, "https": proxy_url},
                timeout=(10, 30),
                allow_redirects=False,
            )
            if response.status_code != 200:
                raise SystemExit(f"Проверка остановлена: HTTP{response.status_code}")
            if "application/json" not in response.headers.get("Content-Type", ""):
                raise ValueError("Ожидался JSON")
            data = response.json()
            ipaddress.ip_address(data["ip"])
            print("Сервис проверки выхода доступен. IP не записан в журнал.")
    except requests.exceptions.ProxyError:
        raise SystemExit("Проверьте шлюз и авторизацию прокси") from None
    except requests.exceptions.SSLError:
        raise SystemExit("Проверьте сертификаты и настройки CA") from None
    except requests.exceptions.Timeout:
        raise SystemExit("Истёк таймаут соединения или чтения") from None
    except requests.RequestException:
        raise SystemExit("Сетевая ошибка: проверьте маршрут и настройки") from None
    except (ValueError, KeyError, TypeError):
        raise SystemExit("Проверьте параметры прокси и формат ответа") from None


if __name__ == "__main__":
    main()

Выполните python proxy_check.py. Критерий успеха — HTTP 200, JSON с полем ip и синтаксически корректный IP-адрес. Это не доказательство нужной страны, неизменности выхода или доступности целевого API. Для этих свойств нужен отдельный тест в закрытом рабочем окружении. Код и реальное подключение в рамках этой статьи не запускались.

При первичной диагностике повторы отключены, чтобы не повторять неверные учётные данные. Код не печатает пароль, URL прокси, полный текст сетевого исключения или полученный IP.

http:// в адресе прокси описывает соединение с самим шлюзом. Для HTTPS-сайта обычно создаётся CONNECT-туннель, однако это не означает, что соединение клиент — прокси и передаваемая шлюзу авторизация защищены TLS. Проверьте поддерживаемый транспорт выбранного продукта. Этот пример предназначен для HTTP-прокси; простая замена схемы не является полной инструкцией по SOCKS5.

rola-ip-proxy-parameters

4. Подключите ограниченные повторы после диагностики

Для разрешённого GET-запроса используйте ранее определённый build_session() и передавайте proxies явно. Сохраните как proxy_retry.py:

import os
from urllib.parse import urlsplit

import requests

from httpadapter_demo import build_session
from proxy_check import build_proxy_url


def main():
    try:
        url = os.environ["TARGET_URL"]
        parsed = urlsplit(url)
        if parsed.scheme != "https" or not parsed.hostname:
            raise ValueError("Нужен HTTPS-адрес разрешённого API")
        proxy_url = build_proxy_url()
        with build_session() as session:
            response = session.get(
                url,
                proxies={"http": proxy_url, "https": proxy_url},
                timeout=(10, 30),
                allow_redirects=False,
            )
            print("HTTP-статус:", response.status_code)
            print("Повторов:", len(response.raw.retries.history))
            response.raise_for_status()
            if response.status_code != 200:
                raise ValueError("Ожидался HTTP 200")
            if "application/json" not in response.headers.get("Content-Type", ""):
                raise ValueError("Ожидался JSON")
            response.json()
            print("JSON разобран. Проверьте поля по контракту вашего API.")
    except requests.RequestException:
        raise SystemExit("Запрос не выполнен; проверьте сеть и доступ к API") from None
    except (ValueError, KeyError):
        raise SystemExit("Проверьте переменные окружения и формат ответа") from None


if __name__ == "__main__":
    main()

Дополнительно задайте TARGET_URL: в PowerShell — $env:TARGET_URL="https://your-api.example/data", в bash — export TARGET_URL="https://your-api.example/data". Замените демонстрационный URL адресом своего API, возвращающего JSON, затем выполните python proxy_retry.py. Код не проверяет специфические бизнес-поля: их условия зависят от контракта вашего API.

Этот вариант также не реализует строгий общий deadline и не является результатом реального теста Rola IP. После изменения настроек проверьте ожидаемые ответы и условия остановки на небольшом разрешённом наборе запросов.

Session повторно использует соединения и Cookie, но не гарантирует постоянный выходной IP. Для связанных действий с авторизацией уточните поддержку sticky-сессий или фиксированного выхода выбранным продуктом. Ротация уместна для независимых запросов, если она нужна задаче и поддерживается условиями сети; она не должна заменять соблюдение лимитов. Дополнительные сценарии описаны на странице прокси Rola IP для сбора веб-данных, на английском.

Как рассчитать число попыток, трафик и параллелизм

Допустим, разрешено проверить три прокси-выхода, а для каждого предусмотрено четыре повтора. Верхняя оценка для одной задачи:

3 выхода × (1 первоначальная попытка + 4 повтора) = 15 попыток
50 задач × 15 попыток = 750 попыток за весь цикл

Это не означает 750 одновременных запросов. Мгновенная нагрузка зависит от числа worker-процессов, очереди и того, сколько задач сейчас ожидает. Не каждая попытка соединения приводит к HTTP-запросу на целевом сервере: часть завершается до его отправки.

Раздельно учитывайте накопленные попытки, запросы в секунду, активные соединения, ответы цели и переданные байты. Объём трафика лучше измерять, а не выводить только из числа HTTP-ответов: повтор может заново передать тело или получить лишь его часть.

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

Если прокси оплачивается по трафику, проверьте единицу тарификации, минимальный объём, срок действия пакета и правила учёта неудачных обращений. Не считайте, что любой сбой бесплатен. Настройте предупреждение о расходе и порог остановки; конкретные цены и правила тарифа в этом примере не предполагаются.

Scrapy RetryMiddleware настраивается отдельно

Scrapy — самостоятельный фреймворк. Его настройки не меняют поведение Requests. В проекте Scrapy можно задать параметры spider:

custom_settings = {
    "RETRY_ENABLED": True,
    "RETRY_TIMES": 2,
    "RETRY_HTTP_CODES": [429, 500, 502, 503, 504],
}

Это фрагмент конфигурации внутри класса spider, не отдельная запускаемая программа. RETRY_TIMES=2 разрешает два повтора после первоначального скачивания. Такое объявление само по себе не реализует backoff, jitter, ожидание Retry-After и восстановление прокси по правилам приведённых выше примеров.

Если включаете 429, отдельно спроектируйте ожидание и ограничение частоты. Поведение зависит от версии и middleware; сверяйтесь с документацией Scrapy RetryMiddleware. Выполнение Scrapy в рамках этой статьи не проверялось.

Частые ошибки при настройке повторов

Ошибка Исправление
Одинаковая реакция на 407, 403, 429 и 503 Классифицировать авторизацию, права, ограничение частоты и временный сбой отдельно
Повторы без timeout Задать connect/read timeout и отдельный общий срок задачи
Session считается фиксированным IP Проверить настройки sticky-сессии или фиксированного выхода у прокси-продукта
POST повторяется после любого обрыва Использовать поддерживаемую API идемпотентность и запрос состояния операции
Ручной цикл окружает Adapter без общего лимита Посчитать произведение попыток и назначить владельца бюджета
Любой HTTP 200 сохраняется как успех Проверять тип, структуру и бизнес-содержимое ответа
Retry-After сокращается до удобного интервала Остановить задачу или перенести её на допустимое время
Для SSLError предлагается verify=False Исправить сертификат, CA или системное время
После исчерпания попыток ошибка теряется Зафиксировать последний статус, стадию сбоя, число попыток и длительность; передать отказ выше

При длительном отказе полезна временная блокировка новых обращений к проблемному сервису — circuit breaker. Её период восстановления и условия пробного запроса задавайте отдельно от повтора одного GET.

С каких параметров начать

Сценарий Число повторов сверх первого запроса Connect/read timeout, секунды Статусы и условия
Редкое чтение API 2–3 (3, 15) 429, 500, 502, 503, 504; учитывать Retry-After
Пакетный сбор разрешённых открытых данных 3–4 (5, 30) Те же кандидаты; ограничить параллелизм и срок задачи
Первичная диагностика прокси 0 (10, 30) Сначала устранить ошибки протокола, шлюза и авторизации
Подтверждённый временный сетевой сбой прокси 1–2 (10, 30) Повторять только подходящий тип ошибки; не 407 и не ошибку сертификата
Операция записи 0 по умолчанию По контракту API Повторы только при явной гарантии идемпотентности

Это исходные ориентиры, а не универсальные оптимальные значения. Выбирайте окончательную политику по распределению задержек и ошибок, серверным квотам, стоимости трафика и допустимому времени завершения задачи.

Заключение

Повторение запросов в Python requests должно иметь конкретные условия: безопасный метод, подходящую ошибку, ограниченное число попыток и согласованное ожидание. Начните с Session, HTTPAdapter и Retry, задайте таймауты и проверьте поведение на локальном сервере. Для Retry-After, ошибок чтения тела и операций записи предусмотрите отдельные решения.

При работе через Rola IP сначала подтвердите корректность соединения и авторизации, затем подключайте повторы для диагностированных временных сбоев. Сохраняйте общий бюджет приложения, очереди и прокси, проверяйте бизнес-содержимое ответа и фиксируйте окончательные отказы. Это делает поведение клиента понятным и управляемым даже во время неполадок.

Материал подготовлен Rola IP. Примеры описывают настройку клиента и не являются измерением производительности или доступности прокси-сети.

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

Готовы начать сбор данных в большом масштабе?

Попробовать бесплатно