С чего начать
Словарь proxies, значения по умолчанию на уровне Session, кодирование учётных данных, SOCKS5, поведение с окружением и исключения, которые Requests выбрасывает при сбое на этапе прокси.
Прокси для запроса через словарь proxies
Requests выбирает прокси из словаря, ключи которого — схемы целевого адреса. Значение для https используется для целевых адресов https://, а значение для http — для адресов http://; обычно оба указывают на один и тот же шлюз. Ключ all применяется к любой схеме, а ключ вида https://example.com привязывает один хост к определённому шлюзу.
Значение — это URL прокси с собственной схемой. http://host:port — обычный HTTP-прокси; для целевого адреса https:// Requests просит его открыть туннель CONNECT. Используйте https://host:port в качестве URL прокси только тогда, когда провайдер терминирует TLS на прокси. Всегда передавайте timeout; без него зависший прокси блокирует вызов бесконечно.
requests.get с proxies · один запрос
import requests
PROXIES = {
"http": "http://proxy.example.invalid:8080",
"https": "http://proxy.example.invalid:8080",
}
response = requests.get(
"https://example.com/",
proxies=PROXIES,
timeout=(10, 20),
)
print(response.status_code)Используйте Session, чтобы все запросы разделяли прокси
Session хранит вместе пул соединений, cookies и настройки прокси. Задайте session.proxies один раз — и каждый запрос через эту Session будет использовать шлюз, при этом аргумент proxies в отдельном вызове по-прежнему его переопределяет. Здесь же решается, может ли окружение процесса влиять на настройки: trust_env=False запрещает Requests читать HTTP_PROXY, HTTPS_PROXY, NO_PROXY, учётные данные netrc и переменную REQUESTS_CA_BUNDLE, так что маршрут определяет только код.
requests.Session · общие настройки прокси
import requests
with requests.Session() as session:
session.trust_env = False
session.proxies.update({
"http": "http://proxy.example.invalid:8080",
"https": "http://proxy.example.invalid:8080",
})
for url in ("https://example.com/", "https://example.com/robots.txt"):
response = session.get(url, timeout=(10, 20))
print(url, response.status_code)Аутентификация на прокси: кодируйте учётные данные в URL
Requests берёт учётные данные прокси из самого URL прокси в виде http://username:password@host:port; заголовок Proxy-Authorization он добавляет за вас. Аргумент auth — это аутентификация на целевом сервере, и до прокси он не доходит.
Кодируйте имя пользователя и пароль по отдельности, чтобы символы вроде @, : или / не могли изменить структуру URL, и держите исходный PROXY_URL свободным от учётных данных. Сохраните скрипт ниже как proxy_check.py и запустите python proxy_check.py после установки Requests в виртуальное окружение командой python -m pip install requests. https://proxy.example.invalid:8443 — лишь нерабочая иллюстрация.
proxy_check.py · Python 3 + Requests
import os
from urllib.parse import quote, urlsplit, urlunsplit
import requests
gateway = urlsplit(os.environ["PROXY_URL"])
if gateway.scheme not in {"http", "https"} or not gateway.hostname:
raise ValueError("Use your provider's HTTP(S) proxy URL")
if gateway.username is not None or gateway.path not in {"", "/"} or gateway.query or gateway.fragment:
raise ValueError("Keep credentials and paths out of PROXY_URL")
username = quote(os.environ["PROXY_USERNAME"], safe="")
password = quote(os.environ["PROXY_PASSWORD"], safe="")
proxy = urlunsplit((gateway.scheme, f"{username}:{password}@{gateway.netloc}", "", "", ""))
with requests.Session() as session:
session.trust_env = False
with session.get(
"https://example.com/",
proxies={"http": proxy, "https": proxy},
timeout=(10, 20),
allow_redirects=False,
stream=True,
) as response:
response.raise_for_status()
print({"status": response.status_code})Переменные окружения и trust_env
Когда trust_env оставлен со значением по умолчанию True, Requests заполняет из окружения через urllib.request.getproxies() любую схему, которую вы не задали. Он принимает HTTP_PROXY, HTTPS_PROXY, ALL_PROXY и NO_PROXY в верхнем или нижнем регистре, а на macOS также читает системную конфигурацию прокси. На рабочей станции это удобно, а в контейнерах — частый источник сюрпризов, когда унаследованный из образа HTTPS_PROXY направляет трафик, который вы считали прямым.
NO_PROXY — список имён хостов, суффиксов доменов или IP-адресов через запятую, которые обходят прокси. Для задания, маршрут которого должен быть воспроизводимым, задайте trust_env=False и передавайте proxies явно; для скрипта на рабочей станции достаточно установить переменные и вызывать Requests без аргумента proxies.
HTTPS_PROXY · подхватывается, когда trust_env равен True
export HTTPS_PROXY='http://proxy.example.invalid:8080'
export NO_PROXY='localhost,127.0.0.1,.internal.example'
python -c 'import requests; print(requests.get("https://example.com/", timeout=(10, 20)).status_code)'SOCKS5-прокси: requests[socks], socks5 против socks5h
Поддержка SOCKS приходит из дополнения PySocks: установите его командой python -m pip install "requests[socks]", затем используйте URL прокси socks5:// или socks5h:// в том же словаре. socks5 резолвит имя целевого хоста локально и отправляет прокси IP-адрес; socks5h отправляет имя хоста, чтобы его резолвил прокси, — обычно именно этого ожидает удалённый шлюз. Имя пользователя и пароль указываются в URL точно так же, как для HTTP-прокси.
socks5h-прокси · requests[socks]
import requests
PROXIES = {
"http": "socks5h://proxy.example.invalid:1080",
"https": "socks5h://proxy.example.invalid:1080",
}
response = requests.get("https://example.com/", proxies=PROXIES, timeout=(10, 20))
print(response.status_code)Ротация между несколькими прокси
Когда заданию нужно распределить запросы по нескольким шлюзам, держите одну Session ради пула соединений и выбирайте аргумент proxies для каждого запроса. Пример перебирает список по кругу; реальное задание брало бы шлюзы из конфигурации, записывало, какой шлюз обслужил каждый запрос, и выводило шлюз из ротации после повторных сбоев вместо немедленного повтора. Должны ли последовательные запросы сохранять один и тот же исходящий адрес — это решение о модели сессии, и многие провайдеры выражают его через имя пользователя или выделенный порт. Следуйте документации провайдера, а не выводите её из одного успешного запроса.
itertools.cycle · одна Session, несколько шлюзов
from itertools import cycle
import requests
GATEWAYS = cycle([
"http://proxy-a.example.invalid:8080",
"http://proxy-b.example.invalid:8080",
])
with requests.Session() as session:
session.trust_env = False
for url in ("https://example.com/", "https://example.com/robots.txt"):
gateway = next(GATEWAYS)
response = session.get(url, proxies={"http": gateway, "https": gateway}, timeout=(10, 20))
print(gateway, url, response.status_code)Понимайте, что измеряет таймаут
Кортеж задаёт отдельные таймауты на подключение и на чтение. Таймаут чтения ограничивает ожидание данных из сокета; это не общий крайний срок загрузки. Если вся задача должна завершиться к фиксированному времени, задайте отдельный бюджет по настенным часам в вашем планировщике заданий. Диагностика выше стримит и закрывает ответ, не загружая тело полностью.
Установка trust_env в False также отключает настройки набора CA-сертификатов, которые Requests берёт из окружения. Если в вашей организации используется собственный набор доверенных сертификатов, передайте одобренный файл явно через verify. Не используйте verify=False, чтобы заставить падающий тест пройти.
Читайте ошибки: ProxyError, 407 и таймауты
То, как проявляется сбой прокси, зависит от схемы целевого адреса. Для целевого адреса https:// сбой происходит во время туннеля CONNECT, и Requests выбрасывает requests.exceptions.ProxyError, в сообщении которого содержится ответ прокси, например Tunnel connection failed: 407 Proxy Authentication Required. Для обычного адреса http:// туннеля нет, поэтому 407 приходит как обычный Response со status_code 407 и вовсе без исключения; проверяйте его явно.
Прежде чем добавлять логику повторов, прогоните тот же шлюз и целевой адрес в curl. Записывайте класс исключения, номер попытки и затраченное время, удалив учётные данные. Воспроизводимая ошибка аутентификации требует изменения конфигурации; её повторение при большей параллельности лишь добавляет шума.
- ProxyError с 407: прокси отклонил учётные данные из URL. Перепроверьте кодирование и формат имени пользователя у провайдера.
- ProxyError с Cannot connect to proxy или сообщением о разрешении имени: хост или порт шлюза неверны, либо эта сеть не может до него добраться.
- ConnectTimeout: часть connect из кортежа таймаутов истекла до того, как ответил прокси или целевой сервер. ReadTimeout: соединение сработало, но данные не пришли в пределах лимита чтения.
- SSLError после успешного туннеля: проверка сертификата целевого сервера не прошла, либо прокси перехватывает TLS. Оставьте проверку включённой и уточните у провайдера.
- Один успешный HTTP-запрос сам по себе не подтверждает местоположение точки выхода.
Перед выпуском
- Используйте виртуальное окружение проекта.
- Кодируйте учётные данные отдельно от шлюза.
- Явно решите, может ли окружение влиять на настройки прокси.
- Помимо сетевых таймаутов держите крайний срок задания.
Источники и дополнительное чтение
Технические материалы, использованные при подготовке руководства. Сверяйтесь с документацией вашей версии и с поддерживаемой конфигурацией вашего провайдера.