Как настроить ротацию прокси для решения капчи в Python и Node

captcha proxy rotation - How to Set Up CAPTCHA Proxy Rotation in Python and Node

Ротацию прокси для капчи вы строите в собственном коде. Решатель берёт один прокси на задание и использует его только для этого задания, поэтому пул, политика ротации и логика повторов живут на вашей стороне. Два правила делают большую часть работы: закрепляйте один IP за одной сессией вместо смены прокси на каждом вызове и меняйте прокси только после сбоя. В этом руководстве разобраны точный формат прокси в каждом SDK, пара сырых API-запросов за ним и тот единственный тип капчи, где прокси молча игнорируется.

Прокси работают с тремя семействами капчи, а не со всеми четырьмя

Начнём с ограничения: оно экономит полдня отладки. CapSkip решает четыре семейства капчи, и прокси работают с тремя из них: reCAPTCHA, Turnstile и GeeTest. Капчи-картинки прокси не принимают.

reCAPTCHA здесь считается один раз. v2 с флажком, Invisible, Enterprise и v3 задаются параметрами одного и того же вызова решения, а не отдельными типами, поэтому с прокси они ведут себя одинаково.

ТипПоддержка проксиЗачем
reCAPTCHA v2 и v3, включая EnterpriseДаПри решении выполняется запрос к Google через прокси
Cloudflare TurnstileДаТо же самое, но к Cloudflare
GeeTest v3ДаТо же самое, но к серверу API GeeTest
Изображение или OCRНетИзображение уже на руках, поэтому исходящий запрос не выполняется

Передача прокси в задание с картинкой ошибкой не считается. Он принимается и игнорируется, и это хуже, потому что баг в ротации там не даёт вообще никакого сигнала. Если вы направляете капчи-картинки через ту же обёртку, что и всё остальное, не передавайте аргумент с прокси на этом пути.

Формат прокси в каждом SDK

Каждый SDK принимает один и тот же объект из двух полей: тип и URI. URI содержит либо хост и порт, либо учётные данные, а за ними хост и порт.

# pip install capskip
from capskip import CapSkip

solver = CapSkip(host="127.0.0.1", port=8080)

# The proxy applies to this one solve, nothing is remembered.
result = solver.recaptcha(
    sitekey="YOUR_SITEKEY",
    url="https://example.com/page-with-recaptcha",
    proxy={"type": "HTTPS", "uri": "user:[email protected]:3128"},
)

print(result["code"])   # token, solved through that IP

Node принимает тот же объект в аргументе options.

// npm install capskip
const { CapSkip } = require('capskip');

const solver = new CapSkip({ host: '127.0.0.1', port: 8080 });

const result = await solver.turnstile(
  'YOUR_SITEKEY',
  'https://example.com/page-with-turnstile',
  { proxy: { type: 'SOCKS5', uri: '1.2.3.4:1080' } },
);

console.log(result.code, result.userAgent);  // send both back

PHP вкладывает его в массив options под ключом proxy, а .NET передаёт объект Proxy, собранный из тех же двух значений. Четыре допустимых значения типа везде одинаковы: HTTP, HTTPS, SOCKS5 и SOCKS5H. SOCKS5H разрешает DNS на стороне прокси, а не локально, и именно это нужно, когда целевой адрес резолвится по-разному в зависимости от региона.

Закрепляйте один прокси за сессией, а не за запросом

Первое побуждение состоит в том, чтобы брать новый IP на каждое решение. Это неверное поведение по умолчанию, и именно из-за него сессии попадают под подозрение.

Защищённая страница привязывает токен к тому контексту, в котором он был выдан. Если ваш краулер загружает страницу с одного IP, а токен решается с другого, сайт видит несоответствие между сессией просмотра и проверкой. Некоторые сайты на это не смотрят. Cloudflare и reCAPTCHA Enterprise смотрят, и токен либо признаётся низкокачественным, либо не проходит вовсе.

Значит, единицей ротации служит сессия, а не вызов. Один прокси загружает страницу, решает капчу и отправляет форму. Следующая сессия берёт следующий прокси.

# pip install capskip
import itertools

# One entry per exit IP. Round-robin, not random:
# random picks repeat, and a repeat is what a rate limiter sees.
POOL = [
    {"type": "HTTPS", "uri": "user:[email protected]:3128"},
    {"type": "HTTPS", "uri": "user:[email protected]:3128"},
    {"type": "SOCKS5H", "uri": "user:[email protected]:1080"},
]

pool = itertools.cycle(POOL)

def new_session():
    """Hand the same proxy to the HTTP client and the solver."""
    return next(pool)

Круговой перебор (round-robin) выигрывает здесь у случайного выбора по одной причине: случайная выборка из небольшого пула достаточно часто повторяет один и тот же IP подряд, а идущие подряд запросы с одного выходного узла образуют как раз тот шаблон, за которым следят ограничители частоты запросов.

Меняйте прокси по факту сбоя, а не по таймеру

Второе правило касается того, когда переходить дальше. Ротация каждые N запросов выбрасывает рабочие IP и держит сломанные в обороте ещё до N вызовов. Меняйте прокси по фактическим признакам сбоя.

# pip install capskip
from capskip import CapSkip
from capskip import ApiException, NetworkException, TimeoutException

solver = CapSkip(host="127.0.0.1", port=8080)

def solve_with_retry(sitekey, url, attempts=3):
    last = None
    for _ in range(attempts):
        proxy = new_session()
        try:
            return solver.recaptcha(sitekey=sitekey, url=url, proxy=proxy)
        except (ApiException, NetworkException, TimeoutException) as err:
            # Burn this IP for the run and take the next one.
            last = err
    raise last

Правильный потолок для большинства пулов составляет три попытки. Если sitekey не проходит на трёх разных IP, дело не в прокси: sitekey устарел, URL страницы неверный или сайт сменил тип задачи. Четвёртая попытка лишь потратит ресурс решения на подтверждение этого.

Какое исключение вы поймали, то и подсказывает, что произошло. NetworkException означает, что решатель был недоступен или задание опросили раньше, чем оно было готово. TimeoutException означает, что окно опроса истекло. ApiException несёт возвращённый код ошибки, и именно его стоит логировать вместе с прокси, потому что так вы находите тот самый мёртвый выходной узел в пуле из сорока.

Сырой API: proxy и proxytype

Без SDK то же самое делается двумя дополнительными полями формы в запросе на отправку. Объект из SDK ложится на них напрямую.

ПолеЗначениеПо умолчанию
proxyIP:PORT или login:pass@IP:PORTНет
proxytypeHTTP, HTTPS, SOCKS5 или SOCKS5HHTTP
# No install step. curl is already on your machine.
curl -X POST http://127.0.0.1:8080/in.php \
  -d "key=YOUR_API_KEY" \
  -d "method=userrecaptcha" \
  -d "googlekey=YOUR_SITEKEY" \
  -d "pageurl=https://example.com/page-with-recaptcha" \
  -d "proxy=user:[email protected]:3128" \
  -d "proxytype=HTTPS"

OK|2122988149   # submitted, and it will solve through that IP

По умолчанию proxytype равен HTTP, поэтому HTTPS- или SOCKS5-прокси, отправленный без этого поля, будет вызываться как обычный HTTP и не сработает. Отправляйте поле каждый раз, а не полагайтесь на то, что значение по умолчанию совпадёт с вашим пулом.

Замените хост, и тот же самый запрос будет работать с общим экземпляром. По умолчанию CapSkip в режиме Local слушает 127.0.0.1, а Настройки подключения также предлагают режим Server, в котором он слушает вашу сеть или публичный IP, чтобы другие машины могли обращаться к API. Разместите его на VPS, и один решатель будет обслуживать всех воркеров вашего парка парсеров. Работа с прокси не меняется: прокси по-прежнему задаётся для каждой задачи, а пул по-прежнему живёт в вашем собственном коде.

Частые ошибки и что они означают

СимптомПричинаИсправить
Капчи решаются, но токены отклоняютсяСтраница была загружена с другого IP, чем выполнено решениеЗакрепите один прокси на загрузку, решение и отправку
ERROR_CAPTCHA_UNSOLVABLE только на одном IPЭтот выходной узел заблокирован целевым сайтомУберите его из пула и повторите на следующем
Таймауты на каждом задании с проксиproxytype не соответствует проксиОтправляйте поле явно, а не полагайтесь на HTTP по умолчанию
Кажется, что прокси ничего не делаетЗадание представляет собой капчу-картинкуТак и должно быть. Прокси работают с тремя другими семействами
Локально работает, а в контейнере падаетВнутри сети DNS разрешается иначеИспользуйте SOCKS5H, чтобы имя хоста разрешал прокси

Полные сведения о параметрах для каждого типа приведены в разделе Документация по API.

FAQ

Нужен ли вообще прокси, чтобы решать капчи?

Нет. Решение без прокси работает на большинстве сайтов, и это на одну движущуюся деталь меньше. Добавляйте прокси, когда цель ограничена по регионам, когда она лимитирует частоту запросов по IP или когда токены начинают отклоняться, хотя решаются чисто. Начните без прокси и добавьте его как исправление уже замеченной проблемы.

Резидентные или серверные прокси?

Серверные IP быстрее и дешевле, и их вполне хватает на сайтах, которые только ограничивают частоту запросов. Резидентные IP важны, когда цель оценивает саму сеть, а это частая история на страницах под Cloudflare и в reCAPTCHA Enterprise. Смешивать оба типа в одном пуле можно: сначала пробуйте серверные, при сбое переходите на резидентные.

В чём разница между SOCKS5 и SOCKS5H?

SOCKS5 разрешает имя хоста на вашей машине и отправляет прокси уже готовый IP. SOCKS5H отправляет само имя хоста и даёт прокси разрешить его. Используйте SOCKS5H, когда сайт возвращает разные адреса в зависимости от региона или когда ваш локальный резолвер вообще не видит цель.

Замедляет ли прокси решение капчи?

Немного, и зависит это скорее от выходного узла, чем от самого решателя. На сам опрос это не влияет: SDK начинают с 250 миллисекунд и увеличивают паузу до потолка pollingInterval, так что быстрое решение возвращается так же быстро. Медленный прокси проявляется как более долгий путь до первого успешного опроса, а не как лишние накладные расходы на опрос.

Коротко

Закрепляйте один прокси за сессией, меняйте его при сбое решения, а не по расписанию, всегда отправляйте proxytype и полностью пропускайте прокси в заданиях с картинками. Поскольку само решение выполняется локально, как безлимитный сервис распознавания капчи, повторная попытка не стоит ничего, кроме трафика прокси, и это делает ротацию по сбоям достаточно дешёвой, чтобы включить её по умолчанию. О более широком конвейере, частью которого это является, читайте решение капчи для парсинга, где работа с сессиями разобрана от начала до конца. А руководстве по интеграции с Python описывает настройку на стороне клиента.