Use Cases

Extração de dados financeiros com tratamento CAPTCHA

Seu script parou de trazer cotações do nada? Boa chance de ter esbarrado num reCAPTCHA v2 ou num Cloudflare Turnstile no meio do caminho: screeners de ações, o SEC EDGAR e a maioria dos provedores de dados de mercado ativam um desafio assim que percebem volume de requisições fora do padrão humano. Este guia mostra onde esses CAPTCHAs aparecem nas principais fontes financeiras e como resolvê-los em Python com a API da CaptchaAI, sem precisar reescrever o pipeline toda vez que um provedor muda a defesa.

Se o pipeline também grava histórico de cotações ou dados vinculados a investidores pessoa física, vale lembrar que a LGPD trata isso como dado pessoal quando associado a CPF ou conta — mantenha a coleta focada em dados públicos agregados e documente a finalidade do tratamento.


Onde o CAPTCHA aparece nas fontes de dados financeiros

A tabela abaixo resume os pontos mais comuns onde a coleta trava e o que costuma disparar o desafio:

Fonte Tipo de CAPTCHA Gatilho Valor do dado
SEC EDGAR reCAPTCHA v2 Volume alto de requisições Arquivamentos da empresa
Yahoo Finance reCAPTCHA v2 Detecção de scraping Cotações e histórico de preços
Bloomberg Cloudflare Turnstile Todo acesso automatizado Dados de mercado
Finviz reCAPTCHA v2 Acesso ao stock screener Resultados de triagem
TradingView Cloudflare Challenge Rate limit Gráficos e indicadores
Morningstar reCAPTCHA v3 Páginas de exportação de dados Análise de fundos

Repare que Bloomberg e TradingView usam as duas variantes de proteção da Cloudflare — Turnstile e Challenge —, então o pipeline precisa tratar ambas, não só o reCAPTCHA.


Sinais de que sua coleta está sendo limitada

Antes de sair resolvendo CAPTCHA em tudo, vale confirmar que é isso mesmo que está travando o pipeline:

  • Respostas 403 ou 429 que aumentam junto com o volume de requisições
  • HTML de retorno trazendo data-sitekey ou o script do Turnstile no lugar dos dados esperados
  • JSON válido nos primeiros minutos e, depois, só página de desafio
  • A mesma consulta funciona no navegador comum e falha apenas no script

Como não estourar o rate limit dos provedores financeiros

Sites financeiros costumam ser mais rígidos com acesso automatizado do que o e-commerce médio — o gatilho para CAPTCHA aparece mais cedo:

Prática Recomendação
Intervalo entre requisições 2 a 5 segundos entre páginas
Conexões simultâneas Máximo de 3 a 5 por domínio
Tipo de egress Residencial ou ISP dedicado
Duração da sessão Sessões fixas de 5 a 10 minutos
User-Agent Realista e consistente durante toda a sessão
SEC EDGAR E-mail de contato no User-Agent (obrigatório)
Horário de mercado Colete fora do horário de pico do pregão, quando possível

Solução de problemas comuns

Problema Causa Correção
403 no SEC EDGAR User-Agent sem e-mail de contato Adicionar cabeçalho CompanyName email@domain
CAPTCHA em toda requisição Rate limit excedido Adicionar 3 a 5 segundos de intervalo entre requisições
Preço desatualizado no retorno Resposta em cache Adicionar parâmetro de consulta cache-bust
Erro ao fazer parse do JSON Página de CAPTCHA retornada no lugar dos dados Verificar se veio CAPTCHA antes de tentar o parse
IP bloqueado Muitas requisições do mesmo IP Trocar para egress de rede autorizado com rotação

Coleta de dados de screener de ações em Python

O padrão abaixo detecta a sitekey na resposta, resolve o reCAPTCHA v2 via CaptchaAI e reenvia o token antes de extrair a tabela de resultados:

import requests
import time
from bs4 import BeautifulSoup
import re

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


def solve_captcha(method, sitekey, pageurl, **kwargs):
    data = {
        "key": CAPTCHAAI_KEY,
        "method": method,
        "googlekey": sitekey,
        "pageurl": pageurl,
        "json": 1,
    }
    data.update(kwargs)

    resp = requests.post(f"{CAPTCHAAI_URL}/in.php", data=data)
    task_id = resp.json()["request"]

    for _ in range(60):
        time.sleep(5)
        result = requests.get(f"{CAPTCHAAI_URL}/res.php", params={
            "key": CAPTCHAAI_KEY, "action": "get",
            "id": task_id, "json": 1,
        })
        r = result.json()
        if r["request"] != "CAPCHA_NOT_READY":
            return r["request"]

    raise TimeoutError("Solve timeout")


class FinancialScraper:
    def __init__(self, proxy=None):
        self.session = requests.Session()
        if proxy:
            self.session.proxies = {"http": proxy, "https": proxy}
        self.session.headers.update({
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
            "AppleWebKit/537.36 Chrome/126.0.0.0 Safari/537.36",
            "Accept-Language": "en-US,en;q=0.9",
        })

    def scrape_screener(self, url):
        """Scrape stock screener, handling CAPTCHA if triggered."""
        resp = self.session.get(url, timeout=30)

        # Check for CAPTCHA
        sitekey_match = re.search(r'data-sitekey="([^"]+)"', resp.text)
        if sitekey_match:
            sitekey = sitekey_match.group(1)
            token = solve_captcha("userrecaptcha", sitekey, url)

            # Resubmit with token
            resp = self.session.post(url, data={
                "g-recaptcha-response": token,
            })

        return self._parse_stocks(resp.text)

    def _parse_stocks(self, html):
        soup = BeautifulSoup(html, "html.parser")
        stocks = []
        for row in soup.select("table.screener-table tr")[1:]:
            cols = row.select("td")
            if len(cols) >= 8:
                stocks.append({
                    "ticker": cols[1].get_text(strip=True),
                    "company": cols[2].get_text(strip=True),
                    "sector": cols[3].get_text(strip=True),
                    "price": cols[6].get_text(strip=True),
                    "change": cols[7].get_text(strip=True),
                })
        return stocks


# Usage
scraper = FinancialScraper(
    proxy="http://user:pass@residential.proxy.com:5000"
)
stocks = scraper.scrape_screener("https://screener.example.com/screener.ashx?v=111")
for stock in stocks[:5]:
    print(f"{stock['ticker']}: {stock['price']} ({stock['change']})")

A classe FinancialScraper guarda a sessão e o proxy configurado, então reutilize a mesma instância entre chamadas em vez de recriar a sessão a cada ticker — isso reduz o número de vezes que o site pede um novo desafio.


Extração de arquivamentos do SEC EDGAR sem bloqueio

O SEC EDGAR aplica rate limit e CAPTCHA agressivamente em acesso de alto volume, e exige um User-Agent identificável — sem isso, a maioria das requisições recebe 403 antes mesmo de chegar ao CAPTCHA:

import json


class SECFilingScraper:
    BASE_URL = "https://efts.sec.gov/LATEST"

    def __init__(self, user_agent_email, proxy=None):
        self.session = requests.Session()
        if proxy:
            self.session.proxies = {"http": proxy, "https": proxy}
        # SEC requires identifying User-Agent
        self.session.headers.update({
            "User-Agent": f"CompanyName admin@{user_agent_email}",
            "Accept": "application/json",
        })

    def search_filings(self, company, filing_type="10-K"):
        """Search EDGAR for specific filing types."""
        url = f"{self.BASE_URL}/search-index"
        params = {
            "q": company,
            "dateRange": "custom",
            "forms": filing_type,
        }

        resp = self.session.get(url, params=params, timeout=30)

        # Handle CAPTCHA if triggered
        if "captcha" in resp.text.lower() or resp.status_code == 403:
            sitekey = self._extract_sitekey(resp.text)
            if sitekey:
                token = solve_captcha("userrecaptcha", sitekey, url)
                resp = self.session.post(url, data={
                    **params,
                    "g-recaptcha-response": token,
                })

        return resp.json() if resp.status_code == 200 else {}

    def download_filing(self, filing_url):
        """Download individual filing document."""
        resp = self.session.get(filing_url, timeout=60)
        if resp.status_code == 200:
            return resp.text
        return None

    def _extract_sitekey(self, html):
        match = re.search(r'data-sitekey="([^"]+)"', html)
        return match.group(1) if match else None


# Usage
sec = SECFilingScraper(
    user_agent_email="example.com",
    proxy="http://user:pass@proxy.example.com:5000",
)
filings = sec.search_filings("Apple Inc", "10-K")

Resolvendo o Cloudflare Turnstile em provedores de dados de mercado

Bloomberg e outros provedores de nível institucional usam o Cloudflare Turnstile em vez do reCAPTCHA — o fluxo muda de token (cf-turnstile-response) mas a lógica de resolução via CaptchaAI é a mesma:

def scrape_turnstile_market_data(url, sitekey):
    """Handle Cloudflare Turnstile on financial data sites."""
    token = solve_captcha("turnstile", sitekey, url)

    session = requests.Session()
    session.headers.update({
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
        "AppleWebKit/537.36 Chrome/126.0.0.0 Safari/537.36",
    })

    resp = session.post(url, data={
        "cf-turnstile-response": token,
    }, timeout=30)

    return resp.json() if resp.status_code == 200 else None

Use Turnstile em vez de reCAPTCHA quando:

  • O provedor for Bloomberg ou outra plataforma de nível institucional
  • A resposta trouxer um script challenges.cloudflare.com no lugar do data-sitekey do Google
  • O erro 403 vier acompanhado de um cabeçalho cf-mitigated

Automatizando a coleta diária de cotações

Para monitorar uma carteira de tickers todo dia sem intervenção manual, agende a função abaixo — ela reaproveita FinancialScraper e já grava o resultado em CSV:

import csv
from datetime import datetime


def daily_market_snapshot(tickers, output_dir="data"):
    """Collect daily stock data, handling CAPTCHAs automatically."""
    scraper = FinancialScraper(
        proxy="http://user:pass@residential.proxy.com:5000"
    )

    date_str = datetime.now().strftime("%Y-%m-%d")
    results = []

    for ticker in tickers:
        url = f"https://screener.example.com/quote.ashx?t={ticker}"
        try:
            data = scraper.scrape_screener(url)
            if data:
                results.extend(data)
            time.sleep(2)  # Rate limit
        except Exception as e:
            print(f"Error on {ticker}: {e}")

    # Save to CSV
    filepath = f"{output_dir}/market_{date_str}.csv"
    with open(filepath, "w", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=["ticker", "company", "sector", "price", "change"])
        writer.writeheader()
        writer.writerows(results)

    print(f"Saved {len(results)} records to {filepath}")
    return results


# Run daily
tickers = ["AAPL", "GOOGL", "MSFT", "AMZN", "TSLA"]
daily_market_snapshot(tickers)

Uma carteira pequena, consultada a cada minuto durante o pregão, roda tranquilamente num plano BASIC (US$ 15/mês, 5 threads). Para acompanhar centenas de tickers em paralelo ou combinar screener, SEC EDGAR e Turnstile ao mesmo tempo, o ADVANCE (US$ 90/mês, 50 threads) dá mais fôlego sem precisar reescrever a lógica de resolução.


Perguntas frequentes

Sim, dentro de limites claros. Arquivamentos públicos e cotações de ações são geralmente permitidos — o próprio SEC EDGAR disponibiliza acesso explicitamente para fins de pesquisa. Respeite os termos de serviço de cada provedor e, se o dado for vinculado a CPF ou conta de investidor, trate-o como dado pessoal sob a LGPD.

A CaptchaAI resolve o Cloudflare Turnstile usado por provedores como Bloomberg?

Sim. O mesmo endpoint que resolve reCAPTCHA v2 resolve Turnstile — só muda o method enviado (turnstile) e o campo do token (cf-turnstile-response), como no exemplo acima.

Com que frequência posso consultar cotações sem disparar um novo CAPTCHA?

Para preços de ações, uma consulta por minuto durante o pregão costuma ser o teto seguro. Para arquivamentos, uma vez ao dia é o padrão do mercado. Passar disso tende a acionar CAPTCHA com mais frequência.

Faz diferença rodar os workers numa região específica da AWS?

Sim, para latência. Rodar os workers em sa-east-1 (São Paulo) reduz o RTT ao consultar Yahoo Finance e o SEC EDGAR a partir do Brasil, mas não muda a taxa de CAPTCHA em si — isso depende do volume de requisições, não da região.


Guias relacionados


Mantenha a coleta de dados financeiros rodando sem travar em CAPTCHA — obtenha sua chave CaptchaAI e automatize a pesquisa de mercado.

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