Исправление ProxyError, SSLError и ConnectTimeout в Requests
requests.exceptions.ProxyError — это симптом, а не причина. Здесь расшифрована иерархия исключений, каждое сообщение об ошибке сопоставлено с его реальным исправлением, и представлена функция проверки состояния, которая классифицирует сбои и переключается между неработающими выходами.
requests.exceptions.ProxyError — одно из наименее полезных сообщений об ошибках в Python: оно возникает из-за неработающего прокси, неправильных учетных данных, неверной схемы, перегруженного выхода и блокировки брандмауэром, все с почти идентичными сообщениями об ошибках. Секрет быстрого исправления заключается в понимании, что ProxyError — это не коренная причина, а категория. В исходном коде Requests ProxyError, SSLError и ConnectTimeout являются подклассами ConnectionError, и каждое из них возникает на определенном этапе жизненного цикла запроса. Прочитайте этап, и вы узнаете причину. Это руководство расшифровывает иерархию исключений, сопоставляет каждое распространенное сообщение об ошибке с его реальным исправлением и предоставляет функцию проверки состояния, которая автоматически классифицирует сбои и переключается между умирающими выходами.
Иерархия исключений requests
Каждая ошибка, связанная с прокси в Requests, происходит от RequestException. Полезная ветвь для отладки — это ConnectionError, потому что три исключения, с которыми вы действительно сталкиваетесь, находятся под ним:
- ProxyError — возникает, когда соединение с самим прокси не удается: недоступный хост, неправильный порт, отказ в соединении или отклоненный
407. Сообщение часто вкладываетCannot connect to proxyв оберткуHTTPSConnectionPool(...). - SSLError — прокси подключился, но TLS рукопожатие с целью не удалось. В прокси-настройках обычной причиной является
https://, написанный внутри ключаhttpsвместоhttp://. - ConnectTimeout — прокси не ответил в течение окна подключения. Он является подклассом как
ConnectionError, так иTimeout, и явно задокументирован как безопасный для повторной попытки. - ReadTimeout — прокси подключился и перенаправил запрос, но цель была слишком медленной для ответа. Это проблема цели или качества выхода, а не ошибка конфигурации.
- MissingSchema / InvalidProxyURL — возникает до любого сетевого вызова, когда URL прокси неправильно сформирован. Это чистые опечатки.
Поскольку первые три имеют общего родителя, один except requests.exceptions.ConnectionError ловит их всех для логики повторной попытки — в то время как ловля подклассов по отдельности позволяет вам записать почему каждый из них не удался. Чистая настройка, которая избегает большинства из них, описана в нашем руководстве по прокси в Python Requests; этот пост о том, что делать, когда сообщение об ошибке уже на экране.
Одна привычка экономит больше времени, чем любое отдельное исправление: читайте сообщение об ошибке снизу вверх. Requests оборачивает основную ошибку urllib3, поэтому верхние фреймы описывают где был сделан вызов, а нижние фреймы описывают что пошло не так. Строка, которую вы хотите, — это самая внутренняя часть Caused by — она называет конкретный сбой (отказ в соединении, несоответствие сертификата, ошибка парсинга порта), который внешний ProxyError или ConnectionError просто повторно вызывает. Как только вы сможете прочитать эту строку, остальная часть этого руководства — это таблица поиска.
Классическая ValueError перед ProxyError
Самое часто искомое сообщение об ошибке прокси даже не является ProxyError — это ValueError: invalid literal for int() with base 10, выбрасываемое глубоко внутри urllib3. Это происходит, когда вы встраиваете учетные данные в значение прокси без схемы, поэтому парсер читает текст после двоеточия как номер порта:
# Broken — no scheme, so 'pass@host' is parsed as host:port
proxies = {"https": "user:pass@45.11.22.33:8000"}
# -> ValueError: invalid literal for int() with base 10: 'pass@45.11.22.33'
# Fixed — scheme in front, password URL-encoded if it has @ : or /
from urllib.parse import quote
pw = quote("p@ss:word", safe="")
proxies = {
"http": f"http://user:{pw}@gate.quantumproxies.io:PORT",
"https": f"http://user:{pw}@gate.quantumproxies.io:PORT",
}

Проверка состояния прокси, которая классифицирует сбои
Вместо угадывания ловите каждый тип исключения и превращайте его в вердикт на простом английском. Эта функция возвращает IP выхода при успехе и помеченную причину при неудаче — разместите ее перед любым скрейпом, чтобы подтвердить, что прокси жив, прежде чем вы потратите на него запросы. Она работает с любым аутентифицированным шлюзом, включая резидентные прокси:
import requests
def check_proxy(proxies, url="https://httpbin.org/ip", timeout=(5, 20)):
try:
r = requests.get(url, proxies=proxies, timeout=timeout)
r.raise_for_status()
return True, r.json().get("origin")
except requests.exceptions.ProxyError as e:
return False, f"proxy unreachable or auth rejected: {e}"
except requests.exceptions.SSLError as e:
return False, f"TLS failed (https:// in the https key?): {e}"
except requests.exceptions.ConnectTimeout:
return False, "proxy did not answer within the connect window"
except requests.exceptions.ReadTimeout:
return False, "target too slow after connect (exit quality)"
except requests.exceptions.RequestException as e:
return False, f"other request error: {e}"
ok, detail = check_proxy(proxies)
print("OK" if ok else "FAIL", detail)
Когда curl работает, но Python выдает ProxyError
Если те же учетные данные успешны в curl, но вызывают ProxyError в Python, почти всегда переменная окружения переопределяет ваш словарь. Requests читает HTTP_PROXY, HTTPS_PROXY и NO_PROXY из оболочки, и устаревшее корпоративное значение тихо перенаправляет каждый вызов. Выведите session.proxies, чтобы увидеть, что действительно используется, затем полностью отключите поиск в окружении:
import requests
session = requests.Session()
session.trust_env = False # ignore HTTP_PROXY / HTTPS_PROXY from the shell
session.proxies = {
"http": "http://USER:PASS@gate.quantumproxies.io:PORT",
"https": "http://USER:PASS@gate.quantumproxies.io:PORT",
}
print(session.get("https://httpbin.org/ip", timeout=(5, 20)).json())
407, который сохраняется при правильных учетных данных, указывает на план белого списка IP, вызываемый с незарегистрированного адреса — полный список причин в нашем руководстве по 407 Proxy Authentication Required.
Периодический ProxyError: переключайтесь, не перезапускайте
Раздражающий случай — это код, который работает двадцать минут, затем выдает ProxyError, а затем снова работает. Это не ошибка в вашем скрипте — это один выходной IP умирает или получает ограничение скорости в середине выполнения. Исправление — это повторная попытка с ротацией: оберните вызов, поймайте ConnectionError и позвольте вращающемуся шлюзу предоставить вам новый IP при следующей попытке. Через вращающиеся прокси каждая повторная попытка проходит через другой выход, так что один неработающий адрес никогда не сможет провалить запрос дважды:
import requests
def get_with_rotation(url, proxies, attempts=4):
last = None
for _ in range(attempts):
try:
r = requests.get(url, proxies=proxies, timeout=(5, 20))
if r.status_code not in (429, 500, 502, 503, 504):
return r
last = r.status_code
except requests.exceptions.ConnectionError as e: # Proxy/SSL/ConnectTimeout
last = e
raise RuntimeError(f"failed after {attempts} attempts: {last}")
Если ProxyError сохраняется на многих новых IP, проблема переместилась из вашего пула к цели: вас блокируют, а не разъединяют. Это другая борьба — смотрите чек-лист по предотвращению бана IP для темпа, заголовков и гигиены сессий.
Есть еще одно различие, которое стоит усвоить, потому что оно меняет ваш ответ. ProxyError или ConnectTimeout означает, что запрос никогда не был завершен, поэтому его повторная попытка безопасна даже для POST — на другой стороне ничего не произошло. ReadTimeout, напротив, означает, что цель получила ваш запрос и просто слишком долго отвечала; повторная попытка неидемпотентной записи может привести к двойной отправке. Когда вы строите цикл повторных попыток, рассматривайте сбои на этапе соединения как свободно повторяемые, а сбои на этапе чтения как повторяемые только для GET и HEAD. Это одно правило предотвращает тонкую ошибку, когда ненадежный прокси превращает одну покупку в три.

Часто задаваемые вопросы
Что вызывает requests.exceptions.ProxyError: невозможно подключиться к прокси?
Хост или порт прокси неверны, прокси не работает, или брандмауэр блокирует соединение до того, как любой запрос покинет. Проверьте конечную точку с помощью curl -x с теми же учетными данными; если curl также не удается, прокси недоступен, а если curl успешен, виновата переменная окружения или неправильно сформированный словарь в вашем Python.
Почему ProxyError обернут в HTTPSConnectionPool?
Эта обертка просто называет пул соединений, который urllib3 использовал для достижения цели — это шум вокруг настоящего сообщения, вложенного внутри. Прочитайте самую внутреннюю часть Caused by: Cannot connect to proxy означает сбой соединения, в то время как сообщение 407 или SSL внутри того же пула указывает на проблему аутентификации или TLS/схемы.
Как остановить чтение прокси из окружения в requests?
Установите session.trust_env = False в вашей сессии или передайте trust_env=False эквивалентно, чтобы Requests игнорировал HTTP_PROXY и HTTPS_PROXY. Это исправление, когда прокси работает в одной оболочке, но выдает ProxyError в другой, или когда корпоративная переменная перехватывает скрейпер, который вы не настроили для использования прокси.
Безопасно ли повторять ConnectTimeout?
Да — документация Requests отмечает ConnectTimeout как безопасный для повторной попытки, потому что запрос никогда не достиг сервера, поэтому никакого побочного эффекта не могло произойти. Повторите его, желательно через вращающийся шлюз, чтобы следующая попытка использовала другой, более быстрый выход. ReadTimeout более рискован для повторной попытки вслепую на неидемпотентных методах, таких как POST.
Как только вы перестанете рассматривать ProxyError как единую ошибку и начнете читать его по этапам жизненного цикла, исправления становятся механическими: опечатки в схеме возникают до сети, сбои соединения указывают на неработающий прокси, ошибки SSL указывают на неправильный ключ, а периодические сбои требуют ротации, а не перезапуска. Чистые выходы устраняют большинство из них полностью.