Use Cases

Monitoramento de tickets de eventos com manipulação de CAPTCHA

Um monitor de ingressos que verifica disponibilidade a cada poucos minutos esbarra cedo ou tarde num CAPTCHA — reCAPTCHA v2 na página do evento, Cloudflare Turnstile na fila virtual, ou os dois ao mesmo tempo. Sem resolver esse desafio, o script simplesmente para de funcionar assim que a plataforma de ingressos aumenta a verificação, o que costuma acontecer justo quando a demanda dispara. Este guia monta, em Python, um monitor que resolve esses CAPTCHAs pela API da CaptchaAI e avisa você assim que um ingresso volta a ficar disponível.

O fluxo é direto: verificar a página, resolver o CAPTCHA quando ele aparecer, reenviar a requisição e comparar o resultado com a última verificação. Tudo roda em segundo plano, agendado com cron.


Como o CAPTCHA entra no fluxo de monitoramento

Configure events → Check availability → CAPTCHA?
                                           ↓ Yes
                                      Solve via CaptchaAI → Retry
                                           ↓ No
                                      Parse availability → Changed?
                                                             ↓ Yes
                                                         Send alert

O laço central é curto: quando não há CAPTCHA, o monitor só compara a disponibilidade lida com a última verificação salva; quando há, ele resolve, reenvia a mesma requisição com o token e só então segue para a comparação. É esse ponto de decisão — "CAPTCHA?" — que o restante do guia implementa.


O que você precisa antes de montar o monitor

  • Chave de API da CaptchaAI — obtenha em captchaai.com
  • Python 3.8+ — com a biblioteca requests
  • Proxy — egress de rede autorizado recomendado, para reduzir bloqueios por volume de requisições

Instale a única dependência externa:

pip install requests

Função para resolver o CAPTCHA pela API da CaptchaAI

O monitor precisa de uma função genérica que envie a tarefa à CaptchaAI, aguarde o processamento e devolva o token — a mesma lógica serve tanto para reCAPTCHA v2 quanto para Cloudflare Turnstile, mudando apenas o method. O initial_wait é menor para o Turnstile porque ele costuma resolver mais rápido que o reCAPTCHA v2.

import requests
import time

API_KEY = "YOUR_API_KEY"


def solve_captcha(method, params):
    """Generic CaptchaAI solver for any supported method."""
    params["key"] = API_KEY
    params["json"] = 1

    submit = requests.post("https://ocr.captchaai.com/in.php", data=params).json()

    if submit.get("status") != 1:
        raise RuntimeError(f"Submit error: {submit.get('request')}")

    task_id = submit["request"]
    initial_wait = 10 if method == "turnstile" else 20
    time.sleep(initial_wait)

    for _ in range(30):
        result = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": API_KEY, "action": "get", "id": task_id, "json": 1
        }).json()
        if result.get("status") == 1:
            return result["request"]
        if result.get("request") != "CAPCHA_NOT_READY":
            raise RuntimeError(f"Solve error: {result['request']}")
        time.sleep(5)
    raise TimeoutError("Solve timed out")

O for de 30 tentativas com time.sleep(5) é um polling simples — funciona, mas trata todo desafio como se levasse o mesmo tempo.

Dica: em produção, prefira um backoff crescente (5 s, 10 s, 20 s…) em vez do intervalo fixo. Isso reduz consultas desnecessárias à API enquanto o CAPTCHA ainda está em fila de resolução, sem atrasar a resposta nos casos que resolvem rápido.


Classe TicketMonitor: verifica, resolve e avisa

A classe abaixo reaproveita a função solve_captcha acima. Ela mantém uma sessão HTTP por execução, detecta qual widget está presente na página (g-recaptcha ou cf-turnstile), resolve o desafio quando aparece e reenvia o token junto com a próxima requisição. Depois compara o resultado com a última verificação salva em memória e dispara um alerta apenas quando o status muda — assim você não recebe notificação repetida a cada execução do cron.

from datetime import datetime
import json


class TicketMonitor:
    def __init__(self, proxy=None):
        self.session = requests.Session()
        self.session.headers.update({
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
        })
        if proxy:
            self.session.proxies = {
                "http": f"http://{proxy}",
                "https": f"http://{proxy}"
            }
        self.last_status = {}

    def check_event(self, event):
        """Check ticket availability for an event, solving CAPTCHAs if needed."""
        url = event["url"]
        response = self.session.get(url)

        # Handle CAPTCHA if detected
        if "g-recaptcha" in response.text or "recaptcha" in response.text:
            sitekey = self._extract_sitekey(response.text)
            if sitekey:
                token = solve_captcha("userrecaptcha", {
                    "method": "userrecaptcha",
                    "googlekey": sitekey,
                    "pageurl": url
                })
                response = self.session.post(url, data={
                    "g-recaptcha-response": token
                })

        elif "cf-turnstile" in response.text:
            sitekey = self._extract_turnstile_key(response.text)
            if sitekey:
                token = solve_captcha("turnstile", {
                    "method": "turnstile",
                    "sitekey": sitekey,
                    "pageurl": url
                })
                response = self.session.post(url, data={
                    "cf-turnstile-response": token
                })

        # Parse availability
        availability = self._parse_availability(response.text, event)

        # Check for changes
        event_key = event["name"]
        if event_key in self.last_status:
            if availability != self.last_status[event_key]:
                self._send_alert(event, availability)

        self.last_status[event_key] = availability
        return availability

    def _extract_sitekey(self, html):
        if 'data-sitekey="' in html:
            start = html.index('data-sitekey="') + 14
            end = html.index('"', start)
            return html[start:end]
        return None

    def _extract_turnstile_key(self, html):
        if 'data-sitekey="' in html:
            start = html.index('data-sitekey="') + 14
            end = html.index('"', start)
            return html[start:end]
        return None

    def _parse_availability(self, html, event):
        """Parse ticket availability. Customize per ticketing site."""
        available = "sold out" not in html.lower()
        return {
            "event": event["name"],
            "available": available,
            "checked_at": datetime.now().isoformat()
        }

    def _send_alert(self, event, availability):
        """Send availability change notification."""
        status = "AVAILABLE" if availability["available"] else "SOLD OUT"
        print(f"[ALERT] {event['name']}: {status}")

    def monitor_all(self, events):
        """Check all events and return results."""
        results = []
        for event in events:
            try:
                result = self.check_event(event)
                results.append(result)
                print(f"[OK] {event['name']}: {'available' if result['available'] else 'sold out'}")
            except Exception as e:
                print(f"[ERROR] {event['name']}: {e}")
        return results


# Usage
events = [
    {
        "name": "Concert - Madison Square Garden - Aug 15",
        "url": "https://example-tickets.com/event/12345"
    },
    {
        "name": "Basketball Finals - Game 7",
        "url": "https://example-tickets.com/event/67890"
    }
]

monitor = TicketMonitor(proxy="user:pass@proxy.example.com:8080")
results = monitor.monitor_all(events)

for r in results:
    print(json.dumps(r, indent=2))

Saída esperada:

[OK] Concert - Madison Square Garden - Aug 15: available
[OK] Basketball Finals - Game 7: sold out

Erros comuns e como corrigir

Antes de agendar o monitor para rodar sozinho, vale conhecer os quatro problemas mais comuns:

  • CAPTCHA aparece em quase toda verificação — geralmente por falta de cookies persistentes entre requisições, com o mesmo IP repetido. Reaproveite a requests.Session(), preserve cookies e faça rotação de proxy periodicamente.
  • Bloqueado após poucas verificações — sintoma de rate limit do site de ingressos. Aumente o intervalo entre checagens e distribua as requisições entre proxies.
  • Status de disponibilidade sempre errado — a estrutura HTML da página mudou. Revise o método _parse_availability com o HTML atual do site.
  • Resolução de CAPTCHA demorando muito — fila alta no solucionador em horário de pico. Implemente retentativa com backoff e aumente o tempo de espera antes da primeira consulta.

Rodando em produção: agendamento com cron

Para monitoramento contínuo, agende a execução do script em vez de deixá-lo rodando em loop dentro de um único processo:

# Check every 15 minutes
*/15 * * * * cd /path/to/project && python ticket_monitor.py >> /var/log/tickets.log 2>&1

A latência até o site monitorado importa mais do que parece. Se o worker roda numa região distante da plataforma de ingressos — por exemplo, uma instância na Europa verificando um site hospedado nos EUA —, o RTT extra se soma ao tempo de resolução do CAPTCHA e pode custar a janela de disponibilidade num evento concorrido. Para quem opera a partir do Brasil, uma região como AWS sa-east-1 (São Paulo) reduz a latência para sites hospedados na América do Sul, mas para plataformas de ticketing americanas normalmente ainda compensa rodar o worker mais perto do próprio alvo. Meça o RTT real antes de decidir onde hospedar.


Perguntas frequentes

As dúvidas mais comuns de quem já colocou esse monitor para rodar em produção:

Quantas threads da CaptchaAI eu preciso para monitorar vários eventos?

Depende de quantos eventos você verifica ao mesmo tempo e com que frequência. Como referência:

  • BASIC (US$ 15/mês, 5 threads) — cobre bem um punhado de eventos a cada 15 minutos.
  • ADVANCE (US$ 90/mês, 50 threads) — dá margem para dezenas de eventos em alta frequência.

Cada CAPTCHA em resolução ocupa uma thread; assim que termina, ela fica livre para o próximo.

Com que frequência posso verificar sem levar bloqueio?

Depende do tipo de evento:

  • Monitoramento geral — a cada 10 a 30 minutos.
  • Eventos de alta demanda — a cada 2 a 5 minutos, aceitando mais CAPTCHAs nessa frequência.

O CaptchaAI resolve o CAPTCHA que aparece depois da sala de espera virtual?

Sim. A sala de espera em si não é um CAPTCHA.

É uma página de fila que seu monitor deve reconhecer e aguardar (ou tentar de novo depois) — o CAPTCHA aparece só ao sair dela, e é resolvido normalmente pela função solve_captcha.

Preciso trocar de proxy a cada verificação?

Não necessariamente:

  • O que mais reduz CAPTCHA é manter cookies e sessão entre requisições do mesmo evento.
  • A rotação de proxy ajuda principalmente quando o mesmo IP começa a levar bloqueio por volume, não a cada checagem isolada.

Como sei se o site mudou o tipo de CAPTCHA que está usando?

Se check_event parar de encontrar g-recaptcha ou cf-turnstile no HTML e a análise de disponibilidade começar a falhar, é sinal de que a página mudou — vale logar esses casos separadamente para revisar o _parse_availability e o trecho de detecção do widget.


Comece a monitorar seus ingressos agora

Pegue sua chave de API em captchaai.com e coloque este monitor resolvendo CAPTCHA automaticamente. Os próximos passos, na ordem:

  • Gere sua chave de API na CaptchaAI.
  • Rode solve_captcha isolado contra um evento de teste antes de agendar.
  • Coloque o TicketMonitor no cron e acompanhe o log por um dia antes de aumentar a frequência.

Guias relacionados

Para continuar construindo em cima deste monitor:

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