Если вы парсите сайты или автоматизируете запросы через прокси на 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% случаев причина найдётся в первых трёх пунктах.
- Проверьте схему в словаре
proxies— почти всегда должна бытьhttp://, даже для HTTPS-запросов. - Протестируйте прокси напрямую через
curl -x, чтобы исключить проблему на стороне кода. - Убедитесь, что логин и пароль не содержат неэкранированных спецсимволов в URL.
- Проверьте, не привязан ли прокси к конкретному IP (whitelist) вместо авторизации по логину/паролю.
- Увеличьте
timeoutдо 15-30 секунд перед тем, как считать прокси неработающим. - Если ошибка появляется только на конкретном сайте — проблема в репутации IP, а не в коде.
- Добавьте retry-логику с экспоненциальной задержкой вместо однократных попыток.
- При массовом парсинге используйте ротацию IP, а не один статичный прокси на все запросы.
Заключение
Большинство ошибок прокси в Python requests сводятся к трём вещам: неверной схеме в URL прокси
(http вместо https),
отсутствию или неверному формату авторизации, и качеству самого IP-пула. ProxyError обычно говорит о проблеме
соединения или бане IP, SSLError WRONG_VERSION_NUMBER почти всегда указывает на неверную схему прокси, а 407 —
на проблему с логином/паролем или whitelist.
Если вы регулярно сталкиваетесь с банами и 407-ошибками при парсинге маркетплейсов или соцсетей, стоит пересмотреть сам источник прокси, а не только код. Для стабильного парсинга с низким процентом блокировок рекомендуем попробовать резидентные прокси — они используют реальные IP-адреса и значительно реже вызывают ProxyError на защищённых сайтах. Если же в приоритете скорость и объём запросов, а не обход сложных антибот-систем, присмотритесь к прокси дата-центров — они дешевле и быстрее для простых задач парсинга.