Integrations

Smartproxy + CaptchaAI: configuração de egress de rede autorizado para resolução de CAPTCHA

O token voltou válido da CaptchaAI e o formulário recusou o envio mesmo assim. Em integrações com gateway de proxy a causa costuma ser uma só: o IP mudou entre o carregamento da página e o POST. Quem resolve o desafio e quem envia a requisição precisam sair pelo mesmo endereço. Este guia amarra as duas pontas — o Smartproxy cuidando do egress de rede autorizado e a CaptchaAI resolvendo reCAPTCHA v2 e Turnstile — em Python, Selenium e Node.js.


Como os dois serviços se dividem no fluxo

Dois serviços, dois papéis — confundi-los é o erro mais comum:

  • Smartproxy define de onde sai o tráfego: carrega a página, sustenta a sessão e envia o formulário.
  • A CaptchaAI recebe só os dados do desafio (pageurl e googlekey ou sitekey) e devolve o token, sem precisar do seu gateway.

O roteiro não muda: carregue a página, extraia a sitekey, envie a tarefa e devolva o token ao formulário pela mesma sessão de saída. O reCAPTCHA v2 fica pronto em menos de 60 s e o Turnstile em menos de 10 s; a sessão precisa cobrir essa janela.


Qual pool do Smartproxy usar em cada cenário

Tipo Pool Indicado para Frequência de CAPTCHA
Residencial mais de 55 milhões de IPs coleta autorizada em geral baixa
Datacenter mais de 100 mil IPs volume alto, latência baixa média a alta
Mobile mais de 10 milhões de IPs páginas servidas a celulares muito baixa
ISP estáticos de nível residencial fluxos longos com sessão baixa

Para QA e monitoramento autorizado, o pool residencial cobre quase tudo. O datacenter custa menos por GB, mas é onde o desafio aparece mais — e cada desafio é uma tarefa a mais.


Passo 1: gateway e chave de API em Python

import requests
import time

SMARTPROXY_USER = "spuser"
SMARTPROXY_PASS = "sppassword"
SMARTPROXY_HOST = "gate.smartproxy.com"
SMARTPROXY_PORT = 10001

CAPTCHAAI_KEY = "YOUR_API_KEY"
CAPTCHAAI_URL = "https://ocr.captchaai.com"

proxies = {
    "http": f"http://{SMARTPROXY_USER}:{SMARTPROXY_PASS}@{SMARTPROXY_HOST}:{SMARTPROXY_PORT}",
    "https": f"http://{SMARTPROXY_USER}:{SMARTPROXY_PASS}@{SMARTPROXY_HOST}:{SMARTPROXY_PORT}",
}


def fetch_page(url):
    return requests.get(url, proxies=proxies, timeout=30)


def solve_captcha(site_url, sitekey, captcha_type="recaptcha_v2"):
    submit_data = {
        "key": CAPTCHAAI_KEY,
        "pageurl": site_url,
        "json": 1,
    }

    if captcha_type == "turnstile":
        submit_data["method"] = "turnstile"
        submit_data["sitekey"] = sitekey
    else:
        submit_data["method"] = "userrecaptcha"
        submit_data["googlekey"] = sitekey

    resp = requests.post(f"{CAPTCHAAI_URL}/in.php", data=submit_data)
    data = resp.json()
    if data["status"] != 1:
        raise Exception(f"Submit failed: {data['request']}")

    task_id = data["request"]

    for _ in range(60):
        time.sleep(5)
        resp = requests.get(f"{CAPTCHAAI_URL}/res.php", params={
            "key": CAPTCHAAI_KEY,
            "action": "get",
            "id": task_id,
            "json": 1,
        })
        data = resp.json()
        if data["request"] == "CAPCHA_NOT_READY":
            continue
        if data["status"] == 1:
            return data["request"]
        raise Exception(f"Solve: {data['request']}")

    raise TimeoutError("Timeout")

Duas coisas economizam depuração: mantenha SMARTPROXY_USER, SMARTPROXY_PASS e CAPTCHAAI_KEY em variáveis de ambiente e trate CAPCHA_NOT_READY como estado normal do polling, não como erro.

O parâmetro method decide o tipo: userrecaptcha com googlekey para reCAPTCHA v2, turnstile com sitekey para o Turnstile. O valor devolvido é o token que o formulário espera em g-recaptcha-response (ou em cf-turnstile-response).


Passo 2: prender o IP durante todo o fluxo

import random
import string


def get_sticky_proxy(session_duration_minutes=10):
    """Create a sticky session proxy (same IP for duration)."""
    session_id = "".join(random.choices(string.ascii_lowercase + string.digits, k=8))

    proxy_url = (
        f"http://{SMARTPROXY_USER}"
        f"-session-{session_id}"
        f"-sessionduration-{session_duration_minutes}"
        f":{SMARTPROXY_PASS}@{SMARTPROXY_HOST}:{SMARTPROXY_PORT}"
    )

    return {"http": proxy_url, "https": proxy_url}


# Use same IP for entire CAPTCHA workflow
sticky = get_sticky_proxy(session_duration_minutes=10)

# Page load
resp = requests.get("https://staging.example.com/qa-form", proxies=sticky)

# Solve CAPTCHA
token = solve_captcha("https://staging.example.com/qa-form", "SITEKEY_HERE")

# Submit with same IP
resp = requests.post(
    "https://target.com/submit",
    data={"g-recaptcha-response": token},
    proxies=sticky,
)

O sufixo -session-ID amarra a sessão a um IP; -sessionduration-N define por quantos minutos ele fica reservado. Dez minutos cobrem o ciclo carregar → resolver → enviar. Em fluxos de várias etapas, suba para 30 minutos: trocar de IP no meio do caminho invalida o token que você acabou de pagar.


Passo 3: escolher o país de saída

# Smartproxy country targeting via username
def get_country_proxy(country_code):
    proxy_url = (
        f"http://{SMARTPROXY_USER}"
        f"-country-{country_code}"
        f":{SMARTPROXY_PASS}@{SMARTPROXY_HOST}:{SMARTPROXY_PORT}"
    )
    return {"http": proxy_url, "https": proxy_url}

# US proxy
us_proxy = get_country_proxy("us")

# UK proxy
uk_proxy = get_country_proxy("gb")

# Germany proxy
de_proxy = get_country_proxy("de")

A segmentação por país também entra pelo nome de usuário: -country-us, -country-gb e assim por diante. Isso pesa quando a aplicação responde diferente por região.

Caso comum em times brasileiros: os workers rodam em sa-east-1 (São Paulo) e a suíte valida a mesma página com saída no Brasil e nos Estados Unidos. Fixe o país por execução e registre o código ao lado do task_id — assim dá para comparar tempo de resolução por região.


Selenium: o mesmo endereço dentro do navegador

from selenium import webdriver
from selenium.webdriver.common.by import By


def create_smartproxy_driver(country=None, sticky_session=None):
    proxy_user = SMARTPROXY_USER
    if country:
        proxy_user += f"-country-{country}"
    if sticky_session:
        proxy_user += f"-session-{sticky_session}"

    proxy_url = f"{proxy_user}:{SMARTPROXY_PASS}@{SMARTPROXY_HOST}:{SMARTPROXY_PORT}"

    options = webdriver.ChromeOptions()
    options.add_argument(f"--proxy-server=http://{SMARTPROXY_HOST}:{SMARTPROXY_PORT}")
    options.add_argument("--disable-blink-features=AutomationControlled")
    options.add_argument("--window-size=1920,1080")

    # For authenticated proxies, use seleniumwire or extension
    return webdriver.Chrome(options=options)


def scrape_with_captcha(url, country="us"):
    session_id = "".join(random.choices(string.ascii_lowercase, k=8))
    driver = create_smartproxy_driver(country=country, sticky_session=session_id)

    try:
        driver.get(url)
        time.sleep(3)

        sitekey = driver.execute_script(
            "return document.querySelector('[data-sitekey]')?.getAttribute('data-sitekey')"
        )

        if sitekey:
            token = solve_captcha(url, sitekey)
            driver.execute_script(f"""
                document.querySelector('#g-recaptcha-response').value = '{token}';
            """)
            driver.find_element(By.CSS_SELECTOR, "form").submit()
            time.sleep(3)

        return driver.page_source

    finally:
        driver.quit()

Uma ressalva: o --proxy-server do Chrome não aceita usuário e senha na URL. Para gateways autenticados, use o Selenium Wire ou uma extensão que injete as credenciais. Sem isso, o navegador devolve 407. Como o driver nasce amarrado a uma sessão, o envio sai do mesmo IP.


Node.js: a mesma lógica com axios

const axios = require("axios");
const HttpsProxyAgent = require("https-proxy-agent");

const CAPTCHAAI_KEY = "YOUR_API_KEY";

function getSmartproxyAgent(options = {}) {
  let user = "spuser";
  if (options.country) user += `-country-${options.country}`;
  if (options.session) user += `-session-${options.session}`;

  return new HttpsProxyAgent(
    `http://${user}:[email protected]:10001`
  );
}

async function scrapeWithCaptcha(url, sitekey) {
  const agent = getSmartproxyAgent({
    country: "us",
    session: `sess-${Date.now()}`,
  });

  // Fetch page through proxy
  const pageResp = await axios.get(url, { httpsAgent: agent });

  // Solve CAPTCHA via CaptchaAI (no proxy needed)
  const submitResp = await axios.post(
    "https://ocr.captchaai.com/in.php",
    null,
    {
      params: {
        key: CAPTCHAAI_KEY,
        method: "userrecaptcha",
        googlekey: sitekey,
        pageurl: url,
        json: 1,
      },
    }
  );

  const taskId = submitResp.data.request;

  // Poll for result
  for (let i = 0; i < 60; i++) {
    await new Promise((r) => setTimeout(r, 5000));

    const result = await axios.get("https://ocr.captchaai.com/res.php", {
      params: {
        key: CAPTCHAAI_KEY,
        action: "get",
        id: taskId,
        json: 1,
      },
    });

    if (result.data.request === "CAPCHA_NOT_READY") continue;
    if (result.data.status === 1) return result.data.request;
  }

  throw new Error("Timeout");
}

Aqui o HttpsProxyAgent carrega o gateway e a chamada à CaptchaAI vai sem agente. O session derivado de Date.now() serve para testes; em produção, prefira um identificador ligado ao job, para correlacionar sessão, task_id e resultado no log.


Execução paralela e o limite de threads

from concurrent.futures import ThreadPoolExecutor, as_completed


def process_url(url):
    session_id = "".join(random.choices(string.ascii_lowercase, k=8))
    proxy = get_sticky_proxy(10)

    try:
        resp = requests.get(url, proxies=proxy, timeout=30)

        # Check if CAPTCHA is present (simplified detection)
        if "data-sitekey" in resp.text:
            import re
            match = re.search(r'data-sitekey="([^"]+)"', resp.text)
            if match:
                sitekey = match.group(1)
                token = solve_captcha(url, sitekey)
                return {"url": url, "status": "solved", "token": token[:30]}

        return {"url": url, "status": "no_captcha"}

    except Exception as e:
        return {"url": url, "status": "error", "error": str(e)}


urls = [
    "https://site1.com/page",
    "https://site2.com/page",
    "https://site3.com/page",
]

with ThreadPoolExecutor(max_workers=5) as executor:
    futures = {executor.submit(process_url, u): u for u in urls}

    for future in as_completed(futures):
        result = future.result()
        print(f"[{result['status']}] {result['url']}")

O max_workers do exemplo precisa conversar com o seu plano. A CaptchaAI cobra por thread simultânea, não por resolução: cada plano inclui resoluções ilimitadas dentro das threads contratadas. Cinco workers casam com o BASIC (US$ 15/mês, 5 threads); para cinquenta em paralelo, o plano correspondente é o ADVANCE (US$ 90/mês, 50 threads).

Abrir mais workers do que threads não gera erro — só empilha fila e alonga o tempo total.


Checklist antes de culpar o solver

  1. Carregamento e envio usam a mesma sessão fixa.
  2. A sessão dura mais que o tempo de resolução observado.
  3. O pageurl enviado é idêntico à URL carregada.
  4. A sitekey veio do DOM renderizado, não de um HTML em cache.
  5. O token foi usado uma única vez.

Solução de problemas

Sintoma Causa provável Correção
407 Proxy Authentication Required credenciais fora do formato confira o painel do Smartproxy
IP muda no meio da sessão sessão fixa não configurada acrescente -session-ID ao usuário
Desafio em quase toda requisição saída pelo endpoint de datacenter use o gateway residencial
Token recusado após a resolução IP mudou entre carregar e enviar aumente a duração da sessão
ERROR_ZERO_BALANCE em in.php saldo esgotado recarregue antes de reiniciar a fila

Escopo autorizado e LGPD

O fluxo serve a ambientes que você tem permissão para acessar: staging próprio, endpoints internos, monitoramento contratado. Havendo dados pessoais, considere as obrigações da LGPD (RGPD, em Portugal): guarde só o necessário e defina retenção. Nos exemplos de QA, use dados fictícios e URLs de staging.


Perguntas frequentes

Por que o token é recusado se a resolução deu certo?

Quase sempre porque o IP de saída mudou entre carregar e enviar. Prenda a sessão com -session-ID e confirme que o pageurl enviado é o da página carregada.

Quantas threads eu preciso para rodar 20 workers em paralelo?

Pelo menos 20 — cada tarefa em voo ocupa uma thread. O STANDARD (US$ 30/mês, 15 threads) fica curto nessa carga; o degrau seguinte é o ADVANCE (US$ 90/mês, 50 threads). Confira a fila nos logs antes de trocar de plano.

Em quanto tempo o desafio costuma ser resolvido?

Nos tipos suportados, o reCAPTCHA v2 fica pronto em menos de 60 s e o Turnstile em menos de 10 s, com alta taxa de sucesso. Dimensione a sessão por esses tetos, não pelo caso médio.

E o hCaptcha, entra nesse fluxo?

Não. O hCaptcha e o FunCaptcha não estão entre os tipos atendidos pela CaptchaAI. A lista suportada é reCAPTCHA v2 e v3, Cloudflare Turnstile e Cloudflare Challenge, GeeTest v3 e CAPTCHAs de imagem, grade e BLS.

A chamada da CaptchaAI precisa passar pelo gateway?

Não. A API funciona de qualquer origem; o que precisa ser consistente é o IP do cliente que carrega a página e envia o formulário.


Guias relacionados


Ligue o gateway do Smartproxy à API da CaptchaAI — crie sua chave de API e resolva o primeiro reCAPTCHA v2 pelo mesmo IP de saída.

Os comentários estão desativados para este artigo.