IP не меняется при ротации: виноват keep-alive, а не прокси
Самая частая претензия к ротационному прокси звучит так: «ротация на каждый запрос, а адрес один и тот же». В подавляющем большинстве случаев прокси работает правильно, а адрес не меняется из-за того, как ваш HTTP-клиент переиспользует соединения. Это лечится одной строкой, но сначала стоит понять механизм — иначе починка выглядит как суеверие.
Когда именно выбирается адрес выхода
Ключевой момент: при ротации на каждый запрос выход выбирается в момент установки TCP-соединения, а для HTTPS — в момент открытия CONNECT-туннеля. Не в момент, когда вы записываете в это соединение запрос. Дальше туннель уже открыт, и всё, что уйдёт по нему, уйдёт с того же адреса.
Отсюда следствие: клиент, который держит пул соединений, переиспользует один и тот же выход столько, сколько живёт соединение в пуле. Со стороны это выглядит буквально как сломанная ротация, хотя ни одна из сторон ничего не нарушила.
Для HTTPS это видно особенно наглядно. Клиент один раз отправляет прокси команду CONNECT, прокси открывает туннель до целевого хоста, и дальше внутри туннеля идёт зашифрованный поток, в который прокси уже не вмешивается. Он физически не может подменить адрес выхода посреди туннеля — не потому, что не хочет, а потому, что туннель к этому моменту установлен и привязан к конкретному выходу.
requests: сессия переиспользует соединения
Session держит пул через HTTPAdapter, и это её главное преимущество в обычной работе. Чтобы получать новый выход, нужно либо не переиспользовать соединение, либо не переиспользовать саму сессию.
import requests
proxies = {"http": PROXY, "https": PROXY}
# Вариант 1: попросить закрывать соединение после ответа
with requests.Session() as s:
s.headers["Connection"] = "close"
for url in urls:
s.get(url, proxies=proxies)
# Вариант 2: новая сессия — новый туннель, новый выход
for url in urls:
with requests.Session() as s:
s.get(url, proxies=proxies)httpx: обнулить пул простаивающих соединений
У httpx лимиты пула задаются объектом Limits. Значение max_keepalive_connections=0 означает, что простаивающие соединения не удерживаются, и следующий запрос откроет новый туннель. Когда задан прокси, лимиты применяются к пулу соединений с прокси — то есть ровно туда, куда нужно.
import httpx
# max_keepalive_connections=0 -> ни одно соединение не остаётся про запас
limits = httpx.Limits(max_keepalive_connections=0)
with httpx.Client(proxy=PROXY, limits=limits) as client:
for url in urls:
client.get(url)aiohttp: force_close и таймаут переиспользования
У TCPConnector есть параметр force_close, который в документации описан как «close underlying sockets after connection releasing»; по умолчанию он False. Рядом стоит keepalive_timeout со значением 15 секунд — столько соединение ждёт переиспользования после освобождения. Именно эти пятнадцать секунд и объясняют, почему в асинхронном парсере адрес держится сериями.
import aiohttp
# force_close=True: сокет закрывается сразу после освобождения соединения,
# поэтому следующий запрос открывает новый туннель и получает новый выход.
connector = aiohttp.TCPConnector(force_close=True)
async with aiohttp.ClientSession(connector=connector) as session:
async with session.get(url, proxy=PROXY,
proxy_auth=aiohttp.BasicAuth(LOGIN, PASSWORD)) as r:
await r.text()Что не является причиной
- Размер пула адресов: адрес не меняется не потому, что их мало, а потому, что новое соединение не открывается.
- Настройка ротации на нашей стороне: она применяется при установке соединения и на уже открытый туннель повлиять не может — по устройству протокола.
- Кеш DNS: он определяет, к какому шлюзу вы подключитесь, а не какой выход шлюз подставит.
- HTTP/2: здесь он делает хуже, а не лучше — мультиплексирование существует именно для того, чтобы держать одно соединение открытым.
Почему это не лечится на стороне прокси
Иногда просят «сделайте, чтобы адрес менялся всё равно». Этого не может сделать ни один поставщик: смена выхода посреди установленного TCP-соединения означала бы разрыв соединения, потерю TLS-сессии и обрыв запроса. То, что для вас выглядит как одно логическое обращение к сайту, для сети — соединение с состоянием, и адрес — часть этого состояния.
Поэтому решение всегда на стороне клиента: попросить его открыть новое соединение. Это не обходной путь и не костыль, а единственное место, где такое решение вообще принимается.
Когда чинить не нужно
Стоит остановиться и спросить, зачем вам новый адрес на каждый запрос. Если задача — цепочка шагов внутри одной сессии, авторизация, пагинация через cookie или многошаговая форма, то стабильный адрес вам нужен, а не мешает. Для этого есть режим ротации по таймеру: адрес держится заданное время осознанно, а не как побочный эффект пула соединений.
Разница принципиальная: пул даёт вам стабильный адрес случайно и на непредсказуемый срок, таймер — намеренно и на заданный. Если стабильность нужна, надёжнее просить её явно.
Ротация на запрос или по таймеру
Оба режима доступны на ротационных тарифах и переключаются в кабинете. IPv6 — от 650 ₽ за 50 потоков, IPv4 — от 1375 ₽ за 100.
Смотреть тарифыПрокси под эту задачу
Проверить нашими инструментами
Читайте также
- Ротация на каждый запрос или по таймеру: что выбратьДва режима закрывают разные задачи. Разбираем, где ротация на каждый запрос ломает сессии, как выбрать интервал и когда нужны два тарифа.
- Как подключить прокси в Python: requests, Scrapy и SeleniumРабочие примеры для трёх самых частых случаев, разница между socks5 и socks5h и почему в браузере удобнее авторизация по IP.
- Один шлюз вместо списка прокси: как настроить софт, который ждёт списокПарсеры считают масштаб числом прокси, а ротационный шлюз — это одна строка. Разбор по программам: где переключить режим, где продублировать строку и где честный ответ — купить список.
