← Назад к блогу

Python requests: ProxyError, SSLError WRONG_VERSION_NUMBER и 407 — как исправить на практике

Подробный разбор частых ошибок при работе с прокси в Python requests: ProxyError, SSLError WRONG_VERSION_NUMBER, 407 Proxy Authentication Required. Рабочие примеры кода для каждой ситуации.

📅11 ноября 2026 г.

Если вы парсите сайты или автоматизируете запросы через прокси на Python, рано или поздно столкнётесь с тремя классическими ошибками: ProxyError, SSLError: WRONG_VERSION_NUMBER и HTTP 407 Proxy Authentication Required. В этой статье разберём причину каждой из них и покажем рабочий код для исправления.

Как requests работает с прокси: базовый синтаксис

Библиотека requests принимает прокси через словарь proxies, где ключи — это схема запроса (http и https), а значения — URL прокси-сервера с опциональной авторизацией.

import requests

proxies = {
    "http": "http://user:password@proxy_host:port",
    "https": "http://user:password@proxy_host:port",
}

response = requests.get("https://httpbin.org/ip", proxies=proxies, timeout=10)
print(response.json())

Обратите внимание: даже для HTTPS-запросов схема прокси обычно указывается как http://, потому что большинство прокси-серверов принимают CONNECT-туннель через HTTP-протокол, а не оборачивают его в SSL. Если указать https:// для прокси, который этого не поддерживает — вы получите именно SSLError, разбор которого будет ниже.

Если вы работаете с SOCKS5-прокси, понадобится дополнительный пакет:

pip install requests[socks]

# И синтаксис proxies меняется:
proxies = {
    "http": "socks5://user:password@proxy_host:port",
    "https": "socks5://user:password@proxy_host:port",
}

ProxyError: причины и решения

requests.exceptions.ProxyError — это обёртка над низкоуровневыми сетевыми исключениями, возникающими при попытке установить соединение с прокси-сервером. Чаще всего встречаются четыре сценария.

1. Прокси недоступен или адрес/порт указаны неверно

requests.exceptions.ProxyError: HTTPSConnectionPool(host='example.com', port=443):
Max retries exceeded with url: / (Caused by ProxyError('Cannot connect to proxy.',
ConnectionResetError(104, 'Connection reset by peer')))

Проверьте, что хост и порт введены корректно, а сервер прокси реально поднят. Самый быстрый способ диагностики — попробовать подключиться через curl:

curl -x http://user:password@proxy_host:port https://httpbin.org/ip -v

2. Прокси заблокировал ваш запрос (бан по IP или User-Agent)

Дата-центровые прокси часто банятся целевыми сайтами из-за того, что их диапазоны IP известны антифрод-системам. Если ошибка появляется стабильно при обращении к конкретному сайту (например, Wildberries, Ozon или соцсетям), а не ко всем сайтам сразу — дело не в самом прокси, а в его репутации. В этом случае помогает переход на резидентные прокси — они используют IP реальных пользователей и реже попадают под блокировки антифрод-систем.

3. Лимит одновременных соединений превышен

Если вы запускаете многопоточный парсинг и открываете сотни соединений через один и тот же прокси, сервер может начать сбрасывать лишние подключения. Решение — использовать пул соединений через requests.adapters.HTTPAdapter с ограничением pool_maxsize и max_retries:

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

session = requests.Session()
retries = Retry(total=3, backoff_factor=1, status_forcelist=[502, 503, 504])
adapter = HTTPAdapter(max_retries=retries, pool_connections=20, pool_maxsize=20)

session.mount("http://", adapter)
session.mount("https://", adapter)

response = session.get("https://httpbin.org/ip", proxies=proxies, timeout=10)

4. Таймаут на стороне прокси-сервера

Если прокси "висит" дольше установленного таймаута, вы увидите ProxyError, завёрнутый в ReadTimeoutError. Увеличьте таймаут до разумного значения (обычно 15-30 секунд достаточно) и добавьте обработку исключения отдельно:

try:
    response = requests.get(url, proxies=proxies, timeout=20)
except requests.exceptions.ProxyError as e:
    print(f"Проблема с прокси: {e}")
except requests.exceptions.Timeout:
    print("Прокси не ответил за отведённое время")

SSLError WRONG_VERSION_NUMBER: разбор проблемы

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

requests.exceptions.SSLError: HTTPSConnectionPool(host='example.com', port=443):
Max retries exceeded with url: / (Caused by SSLError(SSLError(1, '[SSL:
WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1123)')))

WRONG_VERSION_NUMBER означает, что клиент пытался выполнить SSL/TLS handshake там, где сервер ждёт обычный HTTP-трафик (без шифрования). Чаще всего причина кроется в неверно указанной схеме прокси в словаре proxies.

Частая ошибка: указание https:// вместо http:// в адресе прокси-сервера. Большинство HTTP(S)-прокси принимают соединение по обычному HTTP, даже когда проксируют HTTPS-трафик через механизм CONNECT.

# НЕПРАВИЛЬНО — вызовет WRONG_VERSION_NUMBER
proxies = {
    "http": "https://user:password@proxy_host:port",
    "https": "https://user:password@proxy_host:port",
}

# ПРАВИЛЬНО
proxies = {
    "http": "http://user:password@proxy_host:port",
    "https": "http://user:password@proxy_host:port",
}

Вторая частая причина — порт, который физически не поддерживает TLS. Например, если прокси-провайдер выделил порт 8080 только для HTTP-трафика, а вы пытаетесь установить на него TLS-соединение напрямую (минуя CONNECT), сервер ответит обычным текстом вместо TLS-рукопожатия, и клиентская библиотека OpenSSL выдаст именно эту ошибку. Проверьте в документации вашего прокси-провайдера, какие порты и протоколы (HTTP/HTTPS/SOCKS5) доступны на каждом отдельном порту.

Третий вариант — вы используете socket или сторонние библиотеки для прямого TCP-соединения с прокси, минуя стандартный механизм requests. В этом случае убедитесь, что TLS-обёртка применяется только для реального целевого сайта, а не для самого соединения с прокси.

407 Proxy Authentication Required: авторизация

Код ответа 407 — аналог стандартного 401, но для прокси-серверов. Он означает, что сервер требует авторизацию, а вы её либо не передали, либо передали в неверном формате.

Способ 1: логин и пароль прямо в URL

proxies = {
    "http": "http://login:password@proxy_host:port",
    "https": "http://login:password@proxy_host:port",
}

response = requests.get("https://httpbin.org/ip", proxies=proxies)

Этот метод самый простой, но если в логине или пароле есть спецсимволы (например, @, :, /), их нужно экранировать через urllib.parse.quote:

from urllib.parse import quote

login = quote("user@domain")
password = quote("p@ssw:ord!")

proxy_url = f"http://{login}:{password}@proxy_host:port"
proxies = {"http": proxy_url, "https": proxy_url}

Способ 2: заголовок Proxy-Authorization вручную

Если провайдер прокси использует нестандартную схему авторизации или вам нужно больше контроля, можно собрать заголовок вручную через base64-кодирование:

import base64
import requests

login = "user"
password = "password"
credentials = base64.b64encode(f"{login}:{password}".encode()).decode()

headers = {
    "Proxy-Authorization": f"Basic {credentials}"
}

proxies = {
    "http": "http://proxy_host:port",
    "https": "http://proxy_host:port",
}

response = requests.get(
    "https://httpbin.org/ip",
    proxies=proxies,
    headers=headers,
    timeout=10
)

Важно: некоторые прокси-провайдеры привязывают авторизацию не к логину/паролю, а к конкретному статичному IP клиента (whitelist). Если вы меняете IP (например, работаете через VPN или динамический IP провайдера), 407-ошибка может появиться даже при верных кредах — потому что ваш текущий внешний IP не занесён в белый список на стороне прокси. Решение — зайти в личный кабинет провайдера и обновить список разрешённых IP, либо переключиться на авторизацию по логину/паролю вместо IP-whitelist.

Retry-логика и обработка исключений

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

import time
import requests
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def fetch_with_retry(url, proxies, max_retries=3, backoff=2):
    for attempt in range(1, max_retries + 1):
        try:
            response = requests.get(url, proxies=proxies, timeout=15)
            response.raise_for_status()
            return response
        except requests.exceptions.ProxyError as e:
            logger.warning(f"[Попытка {attempt}] ProxyError: {e}")
        except requests.exceptions.SSLError as e:
            logger.warning(f"[Попытка {attempt}] SSLError: {e}")
        except requests.exceptions.HTTPError as e:
            if response.status_code == 407:
                logger.error("407: проверьте логин/пароль или whitelist IP прокси")
                break
            logger.warning(f"[Попытка {attempt}] HTTPError: {e}")
        except requests.exceptions.Timeout:
            logger.warning(f"[Попытка {attempt}] Таймаут соединения")

        time.sleep(backoff * attempt)

    raise RuntimeError(f"Не удалось получить {url} после {max_retries} попыток")

Такой подход экономит часы отладки: вместо падения скрипта на первой же сетевой заминке вы получаете понятный лог с указанием конкретного типа ошибки на каждой попытке.

Ротация прокси и борьба с банами

Если ProxyError и 407 чаще всего связаны с настройками кода, то массовые баны и стабильные отказы соединения на конкретных доменах почти всегда указывают на проблему с качеством самого IP-пула. Для таких задач, как парсинг маркетплейсов (Wildberries, Ozon, Авито) или работа с соцсетями, рекомендуется ротация IP на каждый запрос или через заданный интервал.

import random

proxy_pool = [
    "http://user:[email protected]:8000",
    "http://user:[email protected]:8000",
    "http://user:[email protected]:8000",
]

def get_random_proxy():
    proxy = random.choice(proxy_pool)
    return {"http": proxy, "https": proxy}

response = requests.get(url, proxies=get_random_proxy(), timeout=15)

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

Тип прокси Скорость Риск бана Задачи
Дата-центровые Высокая Средний-высокий Скоростной парсинг открытых API
Резидентные Средняя Низкий Парсинг маркетплейсов, соцсетей
Мобильные Средняя-низкая Минимальный Работа с жёсткими антибот-системами

Session, таймауты и верификация SSL

Для долгоживущих скриптов (например, циклических парсеров) стоит использовать requests.Session() вместо отдельных вызовов requests.get(). Session переиспользует TCP-соединения, что снижает нагрузку на прокси-сервер и уменьшает количество ProxyError на больших объёмах запросов.

session = requests.Session()
session.proxies.update({
    "http": "http://user:password@proxy_host:port",
    "https": "http://user:password@proxy_host:port",
})

for url in urls_list:
    try:
        resp = session.get(url, timeout=15)
        print(resp.status_code, url)
    except requests.exceptions.RequestException as e:
        print(f"Ошибка на {url}: {e}")

Отдельно стоит упомянуть параметр verify. Если вы работаете с прокси, который сам подменяет SSL-сертификат (MITM-прокси для отладки или некоторые корпоративные прокси), requests может ругаться на несовпадение сертификата. Отключать верификацию полностью (verify=False) стоит только в тестовой среде — в продакшене это создаёт риск атак "человек посередине":

# Только для локальной отладки!
response = requests.get(url, proxies=proxies, verify=False, timeout=15)

# Правильный способ — указать путь к доверенному сертификату прокси
response = requests.get(url, proxies=proxies, verify="/path/to/proxy_cert.pem")

Чек-лист диагностики ошибок

Если скрипт падает с ошибкой прокси, пройдитесь по этому списку сверху вниз — в 90% случаев причина найдётся в первых трёх пунктах.

  1. Проверьте схему в словаре proxies — почти всегда должна быть http://, даже для HTTPS-запросов.
  2. Протестируйте прокси напрямую через curl -x, чтобы исключить проблему на стороне кода.
  3. Убедитесь, что логин и пароль не содержат неэкранированных спецсимволов в URL.
  4. Проверьте, не привязан ли прокси к конкретному IP (whitelist) вместо авторизации по логину/паролю.
  5. Увеличьте timeout до 15-30 секунд перед тем, как считать прокси неработающим.
  6. Если ошибка появляется только на конкретном сайте — проблема в репутации IP, а не в коде.
  7. Добавьте retry-логику с экспоненциальной задержкой вместо однократных попыток.
  8. При массовом парсинге используйте ротацию IP, а не один статичный прокси на все запросы.

Заключение

Большинство ошибок прокси в Python requests сводятся к трём вещам: неверной схеме в URL прокси (http вместо https), отсутствию или неверному формату авторизации, и качеству самого IP-пула. ProxyError обычно говорит о проблеме соединения или бане IP, SSLError WRONG_VERSION_NUMBER почти всегда указывает на неверную схему прокси, а 407 — на проблему с логином/паролем или whitelist.

Если вы регулярно сталкиваетесь с банами и 407-ошибками при парсинге маркетплейсов или соцсетей, стоит пересмотреть сам источник прокси, а не только код. Для стабильного парсинга с низким процентом блокировок рекомендуем попробовать резидентные прокси — они используют реальные IP-адреса и значительно реже вызывают ProxyError на защищённых сайтах. Если же в приоритете скорость и объём запросов, а не обход сложных антибот-систем, присмотритесь к прокси дата-центров — они дешевле и быстрее для простых задач парсинга.