Прокси в Python requests: настройка, авторизация и ошибки

10 мин чтения

Как задать прокси в Python requests, пройти CONNECT, передать пароль без утечки, разобрать Session и окружение, SOCKS5 и ошибки timeout.

Текст для того, кто гоняет Python Requests через прокси и хочет увидеть маршрут, а не «просто 200». Примеры с dummy-данными. Top Proxy не продаёт IP и не раздаёт бесплатные списки. Ниже сравнение сервисов, которые это делают.

01Как задать HTTP-прокси в requests

Официальный раздел Proxies в Requests на 14 октября 2026 требует URL прокси со схемой. Лаборатория на этой машине крутилась на Python 3.14.6, Requests 2.34.2, urllib3 2.8.0 и PySocks 1.7.1. TLS не отключали. Этот прогон показывает протокол. Он не меряет скорость, страну и «качество» чужого IP.

Словарь proxies ключами смотрит на схему цели. Значения это URL прокси:

import requests
from urllib.parse import quote

proxies = {
    "http": "http://203.0.113.10:8080",
    "https": "http://203.0.113.10:8080",
}

session = requests.Session()
session.trust_env = False
r = session.get("https://example.com/", proxies=proxies, timeout=(3.05, 27), verify=True)
print(r.status_code)
  1. Создайте Session и сразу поставьте trust_env = False, пока выбираете прокси в коде, а не из оболочки.
  2. Для обычного HTTP-прокси с CONNECT пропишите и http, и https на один и тот же http://хост:порт.
  3. Всегда передавайте кортеж timeout. Без него вызов может висеть.
  4. Сделайте короткий GET на endpoint, которым вы владеете.
  5. Сверьте ответ с логом прокси. Страница Мой IP в браузере показывает выход браузера, не маршрут Python.

В loopback прямой GET на origin вернул HTTP 200 и не оставил следа на прокси. Тот же GET через открытый прокси тоже 200. Прокси увидел absolute-form GET http://127.0.0.1:…/ HTTP/1.1. Без этой строки вы знаете только то, что кто-то ответил 200.

Requests 2.34.2 принял 127.0.0.1:порт без схемы и всё равно прошёл с 200. Внутри адаптер дописывает http://. Схему всё равно пишите сами. Как только в деле SOCKS, голый хост:порт уже двусмысленный. Документация прямо говорит, что URL прокси должен включать схему.

Ключ вида http://api.внутренняя.пример действует только на этот хост, не на поддомены.

02Как HTTPS проходит через CONNECT

Ключ "https" почти всегда указывает на URL прокси http://. Это нормально. Цель HTTPS не требует, чтобы сам прокси слушал TLS. Сначала клиент шлёт CONNECT хост:порт, прокси отвечает 200 Connection Established, и уже внутри туннеля начинается TLS к origin.

В лаборатории событие прокси было CONNECT localhost:порт HTTP/1.1, а целевая URL оставалась https://localhost:порт/. После CONNECT байты TLS реально дошли до origin. Поддельный ответ CONNECT без туннеля сюда не подходит.

Лабораторный origin подписан короткоживущим CA с SAN на localhost и 127.0.0.1. С verify=путь_к_ca.pem HTTPS через CONNECT дал 200. С системным хранилищем без этой CA цепочка была SSLError → SSLCertVerificationError. verify=False в рабочем пути нет. Если прокси перехватывает TLS, вам нужна его корневая CA, а не выключенная проверка.

Отдельный случай. https:// в URL прокси это уже TLS до самого прокси, другой продукт. Не путайте его с классическим forward proxy.

Одна и та же отсутствующая учётка выглядит по-разному на двух путях.

Симптом в Requests 2.34.2СлойЧто смотреть
Статус 407, тело Proxy Authentication Required, без exceptionHTTP-прокси, GET в absolute-formЛогин, пароль, percent-encoding. Это не падение Python.
ProxyError с Tunnel connection failed: 407CONNECT к HTTPS-целиТа же учётка, уже на туннеле. HTTPProxyAuth здесь не спас.
Статус 401 после верного логина на проксиСервер назначенияПрокси пропустил. Приложение на origin отказало.
ProxyError с ConnectTimeoutErrorTCP до проксиХост, порт, файрвол. Это connect, не весь вызов.
ReadTimeoutError при чтении телаЧтение после connectТело задержалось. Одно число не закрывает всю загрузку.
SSLError / CERTIFICATE_VERIFY_FAILEDTLS цели внутри туннеляCA origin или перехватывающего прокси. Не выключайте verify.
ConnectionError на socks5:// с нерезолвящимся именемDNS на клиентеИмя не резолвится локально. socks5h:// отдаёт имя прокси.

Если нужно только щёлкнуть endpoint руками, хватит проверки прокси. Она не заменяет трассу Python. Протокол не равен происхождению IP. GET 200 не доказывает анонимность, страну, допуск к аккаунту и обход любой цели.

03Как передать логин и пароль без утечки

Документация просит Basic Auth в URL прокси и percent-encoding по компонентам. Спецсимволы пароля в сыром виде в URL не кладут. Пароль это не логин origin. auth=(user, pass) это другое.

from urllib.parse import quote

user = quote("demo", safe="")
password = quote("p@ss:/#%", safe="")
proxies = {
    "http": f"http://{user}:{password}@203.0.113.10:8080",
    "https": f"http://{user}:{password}@203.0.113.10:8080",
}

Без логина лабораторный прокси с авторизацией вернул 407 как обычный HTTP-ответ на GET. Тот же отсутствующий заголовок на HTTPS стал requests.exceptions.ProxyError с OSError('Tunnel connection failed: 407 Proxy Authentication Required'). Это два разных объекта. Страница «что такое 407» в браузере сюда не относится.

Неверный dummy-пароль на HTTP тоже остался 407 без exception. На CONNECT снова ProxyError с 407. Верные данные после quote(..., safe="") дали HTTP 200 и HTTPS 200. Origin не видел Proxy-Authorization. Прокси снял заголовок до форварда. Если бы origin его увидел, это была бы утечка учётки прокси на цель.

HTTPProxyAuth в Requests 2.34.2 для HTTP-цели сработал и дал 200. Для HTTPS CONNECT тот же объект дал ProxyError. Для туннеля кладите учётку в URL прокси, не в auth=.

RFC 7617 не разрешает двоеточие внутри user-id после декодирования. Двоеточие в пароле после декодирования допустимо. Кодируйте компоненты раздельно. Не собирайте user:pass одной строкой и потом не квотируйте целиком.

04Как Session и переменные окружения выбирают маршрут

Стабильная документация предупреждает. session.proxies может быть перезаписан прокси из окружения, которые возвращает urllib.request.getproxies. Чтобы выбрать маршрут явно, передавайте proxies= на конкретный вызов.

Лаборатория это проверила двумя прокси с разными метками. У Session.proxies стоял open-прокси. В HTTP_PROXY и HTTPS_PROXY стоял env-прокси. Вызов без proxies= попал в env-прокси. Победителя смотрели по трассе, не по коду ответа. Оба умеют отдать 200.

Тот же session с proxies=open на вызове ударил open-прокси. trust_env=False на другой session с Session.proxies=open тоже ходил в open и не трогал env.

Это не лозунг «Session всегда сильнее окружения». На этой версии окружение перебило Session.proxies. Пер-запросный аргумент и trust_env=False окружение обошли.

Переменные. Requests читает http_proxy, https_proxy, no_proxy, all_proxy и верхний регистр. В CGI, когда задан REQUEST_METHOD, CPython выкидывает верхний HTTP_PROXY для схемы http. В прогоне с REQUEST_METHOD=GET верхний HTTP_PROXY env-прокси не получил. Нижний http_proxy при том же CGI-флаге env-прокси получил. Это не правило curl «только нижний HTTP_PROXY». Это поведение getproxies в этом Python.

NO_PROXY=127.0.0.1,localhost обошёл env-прокси. Прямой GET дал 200 без события на env. Явный proxies= на тот же хост всё равно пошёл через open-прокси. Для проверки, что loopback не спрятался за системный bypass, в прогоне с обязательным прокси ставили пустую строку NO_PROXY. На Darwin без какой-либо proxy-переменной getproxies() может уехать в SystemConfiguration. Лабораторный процесс держал сторожевой ftp_proxy на 127.0.0.1:9 и не менял HOME.

trust_env=False отключает не только прокси из env. Он же отрезает REQUESTS_CA_BUNDLE и netrc. Это видно на HTTPS. Когда REQUESTS_CA_BUNDLE указывал на лабораторную CA и trust_env=True, CONNECT прошёл с 200 без verify=путь. Когда trust_env=False и verify=True, тот же бандл игнорируется, цепочка снова SSLCertVerificationError. Явный verify=ca.pem при trust_env=False вернул 200. Пустой private NETRC задавали через переменную NETRC, не через подмену домашнего каталога.

Для PreparedRequest картина уже. Session.send в Requests 2.34.2 без аргумента proxies= всё равно берёт прокси из окружения. Это не «ручной merge обязателен, иначе env молчит». Явный merge_environment_settings нужен в первую очередь для CA. Он подмешивает REQUESTS_CA_BUNDLE в verify, а не только словарь прокси. В лаборатории после пустого NO_PROXY и send без merge, и send с merge ударили только env-прокси и вернули 200. HTTPS с merge и лабораторной CA в бандле тоже дал 200. Если оставить NO_PROXY=127.0.0.1,localhost, loopback уйдёт напрямую, и трасса прокси будет пустой даже при 200.

05Чем socks5 отличается от socks5h

SOCKS в Requests с 2.10. Ставится так: python -m pip install 'requests[socks]'. В лаборатории стояли Requests 2.34.2 и PySocks 1.7.1. Схема socks5 резолвит имя на клиенте. Схема socks5h отдаёт имя прокси, по смыслу как curl.

Контрольный хост lab-origin.invalid в системе не резолвится. Для socks5:// GET на http://lab-origin.invalid:…/ упал с ConnectionError ещё до рукопожатия с полезной целью. Для socks5h:// прокси записал ATYP hostname и имя lab-origin.invalid, затем сам сопоставил его с 127.0.0.1. Это лабораторный map, не измеренная утечка DNS наружу. GET вернул 200. GET на IPv4 127.0.0.1 через socks5 тоже 200, в рукопожатии dest_kind был ipv4. HTTPS через socks5h на localhost с лабораторной CA дал 200.

Имя в SOCKS URL кодируйте так же по компонентам. Лабораторный SOCKS требовал username/password. Без пакета requests[socks] схема просто не поднимется. Не переносите семантику socks5h из curl или HTTPX, пока не проверите эту связку версий.

06Как читать timeout и ошибки

Документация timeouts на 14 октября 2026 говорит прямо. timeout это не лимит на всю загрузку. Это пауза без байт на сокете. Кортеж (connect, read) разделяет фазы. Одно число закрывает обе. Без timeout Requests не отвалится сам.

Отказ TCP и read timeout это разные ветки. В лаборатории сокет занял эфемерный порт на 127.0.0.1 и не слушал. На Darwin 24.6 подключение к такому порту не дало быстрый ECONNREFUSED. При timeout=(0.4, 5) сверху оказался requests.exceptions.ProxyError, дальше urllib3.exceptions.ProxyError, ConnectTimeoutError и TimeoutError. Это connect-timeout на этой ОС, не доказанный отказ RST. Публичные blackhole и 192.0.2.1 не использовались.

Тело. Origin /slow-body сразу отдал заголовки, записал и сбросил первый байт {, выставил событие, и только потом ждал дольше read-timeout. Это не задержка до заголовков. При stream=True и raw.read() с timeout=(3.05, 0.45) цепочка была urllib3.exceptions.ReadTimeoutError → TimeoutError. Событие первого байта сработало. Это не requests.exceptions.ReadTimeout. Если читать буфер через .content без stream, обёртка может быть другой. Смотрите фактическую цепочку своей версии, не универсальную фразу из блога.

Retry. У HTTPAdapter по умолчанию max_retries.total равен 0. Класс urllib3.util.retry.Retry в 2.8.0 по умолчанию разрешает DELETE, GET, HEAD, OPTIONS, PUT, TRACE. POST в этом наборе нет. status_forcelist по умолчанию пуст. respect_retry_after_header по умолчанию True. Коды 413, 429, 503 при наличии Retry-After могут стать кандидатами на retry, если метод вообще retryable.

В прогоне GET на /status/503 с Retry(total=3, status=3, status_forcelist={503}) ударил origin 4 раза. Retry.sleep подменили. Три вызова sleep это injected ветка, не измеренное ожидание секунды из Retry-After. POST с тем же status-набором и allowed_methods={"GET"} ударил origin один раз, injected sleep не было. Это status/read-ветка. Ошибка connect до прокси для POST по-прежнему может ретраиться. В прогоне POST на неслушающий порт дал ProxyError, не «один выстрел навсегда». max_retries не универсальная политика для запросов с побочным эффектом.

07Как выбрать совместимый endpoint

Сначала протокол, потом тип IP. HTTP CONNECT, SOCKS5 и резидентский выход это разные оси. Каталог резидентских и мобильных прокси отвечает на вопрос «какой выход», не на вопрос «как писать словарь в Requests».

Карточки Oxylabs и Decodo в русском каталоге открываются. Это не прогон оплаченного шлюза.

14 октября 2026 документация Oxylabs Residential Proxies пишет поддержку HTTP, HTTPS, HTTP/3 MASQUE и SOCKS5, вход pr.oxylabs.io:7777, логин вида customer-USERNAME либо IP whitelist. Quick Start Decodo показывает шлюз gate.decodo.com:7000 и пример Requests, где и http, и https смотрят на один http://USERNAME:PASSWORD@gate.decodo.com:7000. В русском каталоге карточка Decodo живёт под историческим именем Smartproxy. Oxylabs там же. Эти абзацы про заявленный endpoint, не про измеренный RTT и не про то, что лаборатория ходила в их сеть.

Подставьте свои значения в тот же словарь proxies, что выше. Проверьте CONNECT и 407 на контрольном URL. Не обещайте себе страну, логин в соцсеть и обход любых фильтров только потому, что локальный GET вернул 200.

Oxylabs — резидентские и датацентровые прокси
Oxylabs — резидентские и датацентровые прокси4.0770

Oxylabs продаёт резидентские, датацентровые и мобильные прокси. Резидентские планы от $6/GB (5 GB / мес). Оплата: карты, банковский перевод, AliPay, PayPal.

10 стран
0,59 $
за 1 GB
Decodo (Smartproxy) — резидентские и датацентровые прокси
Decodo (Smartproxy) — резидентские и датацентровые прокси4.22107

Decodo (ранее Smartproxy) продаёт резидентские и датацентровые прокси. Резидентские: $4/GB без месячного плана, подписки от $3.75/GB за 3 GB. Оплата: карты, PayPal, Alipay, Google/Apple Pay, крипто (с оговорками).

83 страны
0,38 $
за 1 GB
Результаты loopback-прогона Requests 2.34.2, учётки скрыты

Как задать HTTP-прокси для requests?

Словарь proxies с ключами http и https и URL прокси со схемой. Для классического HTTP-прокси оба ключа часто указывают на http://хост:порт. Передайте timeout. Пока отлаживаете код, удобнее Session с trust_env=False, чтобы оболочка не подмешала другой маршрут.

Почему HTTPS URL может использовать HTTP-прокси?

Ключ https выбирает прокси для HTTPS-цели. Сам прокси при этом часто http://. Клиент делает CONNECT, затем TLS внутри туннеля. В лаборатории CONNECT localhost предшествовал ответу origin. Это не требование, чтобы прокси слушал TLS.

Как передать пароль со специальными символами?

Кодируйте логин и пароль по отдельности через urllib.parse.quote с safe пустым. Затем соберите URL прокси. Не путайте это с auth= для origin. HTTPProxyAuth в 2.34.2 закрыл HTTP-цель и не закрыл CONNECT.

Что меняет Session.trust_env=False?

Сессия перестаёт брать прокси, CA-бандл и netrc из окружения. В прогоне trust_env=False игнорировал REQUESTS_CA_BUNDLE и требовал явный verify=путь к CA. Он же оставил Session.proxies в силе, когда HTTP_PROXY указывал на другой прокси.

Чем socks5 отличается от socks5h?

socks5 резолвит имя на клиенте. socks5h отдаёт имя прокси. На lab-origin.invalid схема socks5 упала с ConnectionError, socks5h показала ATYP hostname и дошла до origin после лабораторного map на 127.0.0.1. Нужен пакет requests[socks].

Почему timeout не ограничивает всю загрузку?

Официально timeout это пауза без байт на сокете, не потолок wall-clock. Кортеж connect, read режет фазы. В /slow-body заголовки и первый байт ушли сразу, затем raw.read поймал urllib3 ReadTimeoutError. Одно число не заменяет контроль тела.