Библиотека requests — самый популярный HTTP-клиент в экосистеме Python. Объединение её с ротацией мобильных прокси — это разница между парсером, который работает часами, и тем, который блокируется за минуты.
IP-адреса датацентров блокируются на уровне ASN до отправки первого запроса. Резидентные прокси лучше, но быстро сгорают под интенсивной нагрузкой. Мобильные прокси на реальных 4G/5G модемах несут ASN мобильных операторов, которые практически невозможно заблокировать в масштабе — блокировка означает блокировку тысяч реальных мобильных пользователей, делящих один и тот же IP-диапазон CGNAT.
Этот туториал охватывает полную настройку: от первого pip install до production-парсера с автоматической ротацией IP, логикой повторных попыток и антидетект-заголовками.
Get Rotating Mobile Proxies
Real 4G/5G IPs with API rotation — Ukraine, Romania, Latvia. From $50/mo dedicated.
Необходимые условия
Установите необходимые пакеты. Расширение requests[socks] добавляет поддержку SOCKS5 через urllib3, а PySocks отвечает за низкоуровневый протокол:
pip install requests "requests[socks]" python-socks
Проверьте, что установка прошла успешно:
import requests
import socks # from PySocks
print(requests.__version__)
Вам также понадобятся учётные данные ProxyGrow: хост, порт, имя пользователя, пароль и URL ротационного API (доступны в личном кабинете ProxyGrow).
Базовая настройка прокси с requests
Самый простой способ направить запрос через SOCKS5-прокси — параметр proxies:
import requests
proxies = {
'http': 'socks5h://username:password@host:port',
'https': 'socks5h://username:password@host:port',
}
response = requests.get('https://httpbin.org/ip', proxies=proxies)
print(response.json())
Запустите это — в ответе будет украинский, румынский или латвийский IP мобильного оператора вместо вашего реального адреса.
Почему socks5h, а не socks5
Это самая важная деталь в конфигурации прокси для Python:
socks5: DNS-разрешение происходит на вашем компьютере, затем уже разрешённый IP передаётся через прокси. Ваши DNS-запросы видны.socks5h: DNS-разрешение происходит через прокси, на удалённой стороне. Ваша машина никогда не разрешает имя хоста напрямую.
Буква h означает «host-name» — имя хоста передаётся через туннель и разрешается на сервере прокси. Это критично для анонимности: утечки DNS могут раскрыть ваше реальное местоположение даже когда HTTP-трафик корректно проксируется.
Всегда используйте socks5h для парсинга. Единственная причина использовать обычный socks5 — если сервер прокси не поддерживает удалённое DNS-разрешение, но серверы ProxyGrow поддерживают.
Сессионные прокси (постоянная SOCKS5 сессия)
Создание объекта Session — почти всегда правильный подход. Сессии переиспользуют базовое TCP-соединение, автоматически хранят cookies и позволяют задать прокси и заголовки один раз вместо каждого вызова:
import requests
session = requests.Session()
session.proxies = {
'http': 'socks5h://user:pass@host:port',
'https': 'socks5h://user:pass@host:port',
}
# All requests through this session use the proxy
r = session.get('https://example.com')
print(r.status_code)
Для рабочих процессов парсинга с десятками или сотнями запросов сессия экономит накладные расходы на соединение и сохраняет аутентификацию прокси. Вы настраиваете прокси один раз и забываете о нём.
Ротация IP через ProxyGrow API
Ротация IP — это не переключение между разными прокси-серверами, а запуск переподключения на физическом модеме, чтобы оператор назначил новый IP-адрес. ProxyGrow предоставляет это как простой API-вызов.
Вот полный рабочий процесс ротации:
import requests
import time
PROXY_HOST = "your-proxy-host"
PROXY_PORT = 1080
PROXY_USER = "username"
PROXY_PASS = "password"
ROTATION_URL = "https://api.proxygrow.com/rotate?key=YOUR_API_KEY"
def rotate_ip():
requests.get(ROTATION_URL)
time.sleep(5) # wait for modem to reconnect
def get_current_ip(session):
r = session.get('https://httpbin.org/ip')
return r.json()['origin']
session = requests.Session()
session.proxies = {
'http': f'socks5h://{PROXY_USER}:{PROXY_PASS}@{PROXY_HOST}:{PROXY_PORT}',
'https': f'socks5h://{PROXY_USER}:{PROXY_PASS}@{PROXY_HOST}:{PROXY_PORT}',
}
urls_to_scrape = [
'https://example.com/page/1',
'https://example.com/page/2',
# ... more URLs
]
# Scrape with rotation every 10 requests
for i, url in enumerate(urls_to_scrape):
if i > 0 and i % 10 == 0:
rotate_ip()
print(f"Rotated IP. New IP: {get_current_ip(session)}")
r = session.get(url)
# process r.text
Почему ротация каждые 10 запросов, а не каждый запрос? Слишком частая ротация тратит время (каждое переподключение занимает 3-6 секунд) и может вызвать ограничения скорости на самом ротационном API. Слишком редкая ротация позволяет целевому сайту формировать поведенческий профиль на одном IP. Каждые 10-20 запросов — практический баланс для большинства целей.
Почему time.sleep(5) после ротации? Модем отключается от сети оператора, проводит повторное согласование и получает новый IP через DHCP или назначение CGNAT. Если ваш следующий запрос уйдёт до полного переподключения модема — получите ошибку соединения. Пять секунд надёжно покрывают окно переподключения.
Обработка ошибок и логика повторных попыток
Сетевые ошибки неизбежны при работе с прокси. Модемы переподключаются, операторы ограничивают скорость, цели возвращают 429. Механизм повторных попыток urllib3 автоматически обрабатывает временные сбои:
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
retry = Retry(
total=3,
backoff_factor=1,
status_forcelist=[429, 500, 502, 503],
)
adapter = HTTPAdapter(max_retries=retry)
session.mount('http://', adapter)
session.mount('https://', adapter)
Что делает каждый параметр:
total=3: повторять до 3 раз перед отказомbackoff_factor=1: ждать 1с, затем 2с, затем 4с между попытками (экспоненциальный откат)status_forcelist: считать эти HTTP-коды повторяемыми ошибками
Монтируйте адаптер на http:// и https:// для охвата всех запросов. Делайте это один раз после создания сессии, до выполнения запросов.
Настройка реалистичных заголовков для обхода детектирования
IP мобильного оператора с заголовками десктопного браузера — статистическая аномалия. Реальный трафик с Kyivstar или Orange Romania преимущественно исходит с мобильных устройств. Антибот-системы знают это и помечают несоответствия.
Задайте заголовки, совместимые с происхождением прокси:
session.headers.update({
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15',
'Accept-Language': 'uk-UA,uk;q=0.9,en;q=0.8', # match proxy country
'Accept-Encoding': 'gzip, deflate, br',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Connection': 'keep-alive',
})
Ключевые моменты:
- User-Agent: используйте мобильный UA. Для украинских прокси подходят iOS или Android. Для румынских — то же самое: большинство мобильных пользователей в Румынии и Украине используют iOS или Android.
- Accept-Language: соответствуйте стране прокси.
uk-UAдля Украины,ro-ROдля Румынии,lv-LVдля Латвии. Некоторые сайты проверяют это как сигнал согласованности. - Accept-Encoding: всегда включайте
br(Brotli). Реальные браузеры отправляют его. Парсеры, которые его опускают, легче идентифицировать.
Ограничение скорости между запросами
Запросы со скоростью машины — моментальный сигнал детектирования. Добавляйте случайные задержки между запросами для имитации паттернов просмотра человека:
import time
import random
for url in urls_to_scrape:
r = session.get(url)
# process r.text
time.sleep(random.uniform(1, 3))
random.uniform(1, 3) возвращает число с плавающей точкой от 1.0 до 3.0 секунд — ваш парсер работает со скоростью 20-60 запросов в минуту. Это в диапазоне быстрого пользователя-человека и значительно ниже порогов, которые запускают автоматическое ограничение скорости на большинстве сайтов.
Для более защищённых целей увеличьте диапазон: random.uniform(2, 6). Для внутренних API или целей без антибот-защиты можно уменьшить.
Проверка работы прокси
Перед запуском полноценного парсинга всегда проверяйте, что прокси активен и ротация действительно меняет IP:
import requests
import time
PROXY_HOST = "your-proxy-host"
PROXY_PORT = 1080
PROXY_USER = "username"
PROXY_PASS = "password"
ROTATION_URL = "https://api.proxygrow.com/rotate?key=YOUR_API_KEY"
session = requests.Session()
session.proxies = {
'http': f'socks5h://{PROXY_USER}:{PROXY_PASS}@{PROXY_HOST}:{PROXY_PORT}',
'https': f'socks5h://{PROXY_USER}:{PROXY_PASS}@{PROXY_HOST}:{PROXY_PORT}',
}
# Check IP before rotation
ip_before = session.get('https://httpbin.org/ip', timeout=10).json()['origin']
print(f"IP before rotation: {ip_before}")
# Trigger rotation
requests.get(ROTATION_URL)
time.sleep(5)
# Check IP after rotation
ip_after = session.get('https://httpbin.org/ip', timeout=10).json()['origin']
print(f"IP after rotation: {ip_after}")
if ip_before != ip_after:
print("Rotation successful.")
else:
print("Warning: IP did not change. Check rotation URL or wait longer.")
Запускайте это перед каждой новой сессией парсинга. Если IP одинаковые — либо неправильный URL ротации, либо модем не завершил переподключение. Увеличьте время ожидания и попробуйте снова.
Интеграция со Scrapy
Для масштабных проектов парсинга система middleware Scrapy позволяет подключить ротацию прокси на уровне паука. Паттерн — кастомный DownloaderMiddleware, который устанавливает request.meta['proxy'] для каждого исходящего запроса и запускает ротацию на основе счётчика или кода ответа.
Основная идея:
class ProxyRotationMiddleware:
def process_request(self, request, spider):
request.meta['proxy'] = 'socks5h://user:pass@host:port'
# Rotation logic: call the API every N requests
Для более простых настроек Scrapy пакет scrapy-rotating-proxies управляет пулом, но не поддерживает ротацию модема через API, которую предоставляет ProxyGrow. Кастомный middleware даёт полный контроль над временем и способом ротации.
Частые ошибки и способы их устранения
requests.exceptions.ConnectionError
Прокси-сервер недоступен. Причины:
- Неправильный хост или порт в URL прокси
- Модем в процессе переподключения после ротации
- Проблема сети между вашей машиной и прокси-сервером
Решение: проверьте учётные данные, подождите 5-10 секунд после ротации, убедитесь что хост разрешается корректно.
requests.exceptions.ProxyError
Прокси-сервер отклонил соединение — обычно из-за ошибки аутентификации. Причины:
- Неправильное имя пользователя или пароль
- Учётные данные истекли или отозваны
- Ваш IP не в белом списке, если прокси использует аутентификацию по IP
Решение: перепроверьте имя пользователя и пароль в личном кабинете ProxyGrow. Убедитесь, что используете формат socks5h://user:pass@host:port (не http://).
requests.exceptions.Timeout
Запрос не завершился в течение периода тайм-аута. Наиболее распространённые причины в настройках прокси:
- Запрос сразу после ротации (модем ещё переподключается)
- Целевой сайт медленно отвечает
- Модем временно потерял сигнал
Решение: всегда задавайте явные тайм-ауты и всегда выжидайте после ротации:
try:
r = session.get(url, timeout=(10, 30)) # (connect timeout, read timeout)
except requests.exceptions.Timeout:
print(f"Request to {url} timed out — retrying after delay")
time.sleep(10)
Форма кортежа (connect_timeout, read_timeout) полезнее одиночного значения, потому что соединение с прокси и ответ сервера имеют разные характеристики отказа.
Полный рабочий пример
Всё вместе — парсер с сессией, ротацией, повторными попытками, заголовками и обработкой ошибок:
import requests
import time
import random
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
PROXY_HOST = "your-proxy-host"
PROXY_PORT = 1080
PROXY_USER = "username"
PROXY_PASS = "password"
ROTATION_URL = "https://api.proxygrow.com/rotate?key=YOUR_API_KEY"
def build_session():
session = requests.Session()
session.proxies = {
'http': f'socks5h://{PROXY_USER}:{PROXY_PASS}@{PROXY_HOST}:{PROXY_PORT}',
'https': f'socks5h://{PROXY_USER}:{PROXY_PASS}@{PROXY_HOST}:{PROXY_PORT}',
}
session.headers.update({
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15',
'Accept-Language': 'uk-UA,uk;q=0.9,en;q=0.8',
'Accept-Encoding': 'gzip, deflate, br',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
})
retry = Retry(total=3, backoff_factor=1, status_forcelist=[429, 500, 502, 503])
adapter = HTTPAdapter(max_retries=retry)
session.mount('http://', adapter)
session.mount('https://', adapter)
return session
def rotate_ip():
try:
requests.get(ROTATION_URL, timeout=10)
time.sleep(5)
except Exception as e:
print(f"Rotation error: {e}")
time.sleep(10)
def scrape(urls):
session = build_session()
for i, url in enumerate(urls):
if i > 0 and i % 10 == 0:
rotate_ip()
try:
r = session.get(url, timeout=(10, 30))
r.raise_for_status()
print(f"[{i}] {url} — {len(r.text)} bytes")
# process r.text here
except requests.exceptions.ProxyError:
print(f"[{i}] Proxy auth error — check credentials")
except requests.exceptions.Timeout:
print(f"[{i}] Timeout — skipping {url}")
except requests.exceptions.HTTPError as e:
print(f"[{i}] HTTP {e.response.status_code} — {url}")
time.sleep(random.uniform(1, 3))
urls = [f"https://example.com/page/{n}" for n in range(1, 51)]
scrape(urls)
Этот код обрабатывает 50 URL, выполняет ротацию каждые 10, автоматически повторяет временные сбои и выводит понятные сообщения об ошибках для каждого типа отказа.
Get Rotating Mobile Proxies
Real 4G/5G IPs with API rotation — Ukraine, Romania, Latvia. From $50/mo dedicated.
Итоги
Ключевые тезисы этого руководства:
- Используйте
socks5h://вместоsocks5://— удалённое DNS-разрешение обязательно для анонимности - Используйте
requests.Sessionдля всех многозапросных рабочих процессов - Ротируйте IP через ProxyGrow API, а не путём переключения прокси-серверов
- Всегда ждите 5 секунд после запуска ротации перед следующим запросом
- Задавайте заголовки, соответствующие стране прокси и типу IP (мобильный UA для мобильных прокси)
- Добавляйте задержки
random.uniform(1, 3)между запросами - Используйте
Retryсbackoff_factorдля обработки временных сбоев без ручных циклов повторов - Соответствуйте
Accept-Languageгео-локации прокси — это сигнал согласованности, который проверяют антибот-системы