Pular para o conteúdo
Backend

Web Scraping de Sites de Apostas com Python —

Scraping de odds esportivas é um bicho diferente. Sites de apostas têm anti-bot pesado, dados dinâmicos e termos de uso restritivos. Aqui vai o guia pra fazer isso

Ética primeiro

Web Scraping de Sites de Apostas com Python —. Scraping de odds esportivas é um bicho diferente. Sites de apostas têm anti-bot pesado, dados dinâmicos e termos de uso restritivos. Aqui vai o guia pra fazer isso direito — sem ser bloqueado e sem pisar na ética.

Galera, vou ser honesto: scraping de sites de apostas é provavelmente o tipo mais difícil de web scraping que existe. Sites de apostas gastam milhões em anti-bot. Os dados são carregados dinamicamente via JavaScript e WebSocket. E se você fizer errado, sua IP vai parar numa blacklist rapidinho.

Mas tem situações onde scraping é a única opção. A casa não tem API. A API não cobre o mercado que você precisa. Ou você quer dados de comparação que nenhum provider oferece. Nesses casos, aqui está como fazer da forma mais limpa e ética possível.

Por que scraping de odds é diferente de scraping normal

Quando você faz scraping de um e-commerce ou blog, os dados estão ali no HTML. Faz um request GET, parseia com BeautifulSoup, pronto. Sites de apostas são outra história completamente.

O que torna sites de apostas especiais

  • Dados dinâmicos: odds mudam a cada segundo. O HTML inicial muitas vezes não tem os dados — eles chegam via JavaScript depois da página carregar
  • Anti-bot agressivo: Cloudflare, DataDome, PerimeterX. Detectam headless browsers, fingerprinting e padrões de navegação robóticos
  • WebSockets: muitos sites usam conexões WebSocket pra atualizar odds em tempo real. Request/response tradicional não captura isso
  • Geo-blocking: vários sites restringem acesso por região. Você pode precisar de proxies específicos
  • Rate limiting severo: muitos requests em pouco tempo e você tá bloqueado. Às vezes permanentemente
  • SPA (Single Page Application): a maioria usa React/Angular/Vue. O conteúdo é renderizado no cliente, não no servidor

Setup Python — Selenium vs Playwright vs Requests

A primeira decisão é qual ferramenta usar. Cada uma tem seu espaço. Vou ser direto sobre quando usar qual.

Requests + BeautifulSoup

Abordagem clássica e leve. Funciona quando os dados estão no HTML estático.

+ Prós

  • • Mais rápido (sem browser)
  • • Menos recursos de máquina
  • • Simples de debugar

− Contras

  • • Não renderiza JavaScript
  • • Não funciona com SPAs
  • • Mais fácil de detectar como bot

Selenium

Controla um browser real. O padrão da indústria pra scraping de sites dinâmicos.

+ Prós

  • • Renderiza JavaScript completamente
  • • Grande comunidade e documentação
  • • Funciona com qualquer site

− Contras

  • • Lento (precisa abrir browser real)
  • • Consome muita RAM
  • • Detecção de WebDriver é comum

Playwright

O substituto moderno do Selenium. Mais rápido, mais confiável e com melhor suporte a anti-detecção.

+ Prós

  • • Mais rápido que Selenium
  • • API moderna e intuitiva
  • • Suporte nativo a interceptação de requests
  • • Melhor contra anti-bot com stealth plugins

− Contras

  • • Comunidade menor que Selenium
  • • Ainda consome recursos de browser
  • • Precisa de instalação separada dos browsers

Minha recomendação: Playwright. Sem pensar duas vezes. É mais moderno, mais rápido e tem capacidades de intercepção de rede que facilitam muito o trabalho com sites de apostas. Selenium funciona, mas pra projetos novos não faz sentido.

Instalação e setup básico

bash
pip install playwright beautifulsoup4 pandas
playwright install chromium
python
from playwright.sync_api import sync_playwright
import json

def setup_browser():
    """Configura browser com stealth settings."""
    pw = sync_playwright().start()
    browser = pw.chromium.launch(
        headless=True,
        args=[
            "--disable-blink-features=AutomationControlled",
            "--disable-features=IsolateOrigins,site-per-process"
        ]
    )
    context = browser.new_context(
        viewport={"width": 1920, "height": 1080},
        user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
        locale="pt-BR",
        timezone_id="America/Sao_Paulo"
    )
    
    # Remove sinais de automação
    context.add_init_script("""
        Object.defineProperty(navigator, 'webdriver', {get: () => undefined});
        window.chrome = { runtime: {} };
    """)
    
    return pw, browser, context

Abordagens de scraping por tipo de site

Abordagem 1: Intercepção de API interna

Essa é a melhor abordagem e a que quase ninguém fala. Sites de apostas são SPAs — eles fazem requests pra suas próprias APIs internas pra carregar os dados. Se você interceptar essas requests, pega os dados já estruturados em JSON, sem precisar parsear HTML.

python
def scrape_via_network_intercept(url: str, api_pattern: str):
    """Captura dados interceptando requests de API interna."""
    pw, browser, context = setup_browser()
    captured_data = []
    
    def handle_response(response):
        """Callback pra cada response de rede."""
        if api_pattern in response.url and response.status == 200:
            try:
                data = response.json()
                captured_data.append(data)
            except Exception:
                pass
    
    page = context.new_page()
    page.on("response", handle_response)
    
    page.goto(url, wait_until="networkidle")
    page.wait_for_timeout(5000)  # espera dados carregarem
    
    browser.close()
    pw.stop()
    
    return captured_data

# Uso: intercepta a API interna do site
# dados = scrape_via_network_intercept(
#     "https://site-de-apostas.com/futebol",
#     "/api/odds"  # padrão da URL da API interna
# )

Pra descobrir o padrão da API interna, abre o DevTools do Chrome (F12), vai na aba Network, e navega pelo site. Você vai ver as requests de API passando. Filtra por XHR/Fetch e procura as que retornam dados de odds em JSON. Anota o padrão da URL.

Abordagem 2: Parsing de HTML renderizado

Quando não dá pra interceptar a API (alguns sites ofuscam as requests), volta pro método tradicional: deixa o browser renderizar a página e parseia o HTML resultante.

python
from bs4 import BeautifulSoup

def scrape_via_html(url: str, odds_selector: str):
    """Coleta odds parseando o HTML renderizado."""
    pw, browser, context = setup_browser()
    page = context.new_page()
    
    page.goto(url, wait_until="networkidle")
    page.wait_for_selector(odds_selector, timeout=15000)
    
    html = page.content()
    soup = BeautifulSoup(html, "html.parser")
    
    # Exemplo genérico — seletores dependem do site
    games = []
    for event in soup.select(".event-row"):
        teams = event.select(".team-name")
        odds = event.select(".odds-value")
        
        if len(teams) >= 2 and len(odds) >= 3:
            games.append({
                "home": teams[0].text.strip(),
                "away": teams[1].text.strip(),
                "home_odds": float(odds[0].text.strip()),
                "draw_odds": float(odds[1].text.strip()),
                "away_odds": float(odds[2].text.strip())
            })
    
    browser.close()
    pw.stop()
    return games

Abordagem 3: WebSocket pra dados em tempo real

Sites de apostas ao vivo (in-play) usam WebSocket pra atualizar odds sem recarregar a página. Se você precisa de odds ao vivo em tempo real, interceptar o WebSocket é o caminho.

python
def scrape_via_websocket(url: str):
    """Captura dados de WebSocket pra odds ao vivo."""
    pw, browser, context = setup_browser()
    ws_messages = []
    
    def handle_ws(ws):
        def on_message(msg):
            try:
                data = json.loads(msg)
                ws_messages.append(data)
            except (json.JSONDecodeError, TypeError):
                pass
        ws.on("framereceived", lambda payload: on_message(payload))
    
    page = context.new_page()
    page.on("websocket", handle_ws)
    
    page.goto(url, wait_until="networkidle")
    page.wait_for_timeout(30000)  # coleta 30 segundos de dados
    
    browser.close()
    pw.stop()
    
    return ws_messages

Lidando com anti-bot

Essa é a parte mais chata e mais importante. Sites de apostas usam soluções como Cloudflare, DataDome e PerimeterX que são muito boas em detectar automação. Aqui vão as técnicas que funcionam — e as que não funcionam.

O que funciona contra anti-bot

Playwright com playwright-stealth: remove indicadores de automação do browser. Funciona contra 70% das proteções básicas.

Proxies residenciais rotativos: IPs de datacenters são bloqueados imediatamente. Proxies residenciais (tipo BrightData ou SmartProxy) passam muito melhor. Custo: $10-50/mês.

Delays humanos: adicione waits aleatórios entre 2-8 segundos entre ações. Bots que clicam instantaneamente são óbvios.

Sessions persistentes: reutilize cookies e sessions entre requests. Criar sessão nova cada vez é comportamento típico de bot.

python
import random
import time

def human_delay(min_sec=1.5, max_sec=5.0):
    """Simula delay humano entre ações."""
    delay = random.uniform(min_sec, max_sec)
    time.sleep(delay)

def random_scroll(page):
    """Simula scroll humano — anti-bots verificam isso."""
    scroll_amount = random.randint(200, 600)
    page.mouse.wheel(0, scroll_amount)
    human_delay(0.5, 1.5)

def navigate_humanly(page, url: str):
    """Navega pro URL simulando comportamento humano."""
    page.goto(url, wait_until="domcontentloaded")
    human_delay(2, 4)
    random_scroll(page)
    human_delay(1, 3)
    random_scroll(page)
    page.wait_for_load_state("networkidle")

Pipeline de dados — limpeza e armazenamento

Dados de scraping vêm sujos. Muito mais sujos que dados de API. Nomes de times com espaços extras, odds formatadas como string, valores faltando. Você precisa de um pipeline de limpeza robusto.

  1. Normalize nomes de times: 'São Paulo FC', 'Sao Paulo', 'SPFC' precisam virar a mesma coisa. Crie um dicionário de mapeamento
  2. Converta odds pra formato decimal: se o site mostra odds fracionárias ou americanas, converta antes de salvar
  3. Valide ranges: odds abaixo de 1.01 ou acima de 100 são provavelmente erros. Descarte ou flagge pra revisão
  4. Adicione timestamps precisos: cada coleta precisa de data/hora exata. Odds de 5 minutos atrás já podem ser irrelevantes
  5. Deduplique: se rodou o scraper duas vezes no mesmo minuto, não grave duplicatas
  6. Salve em formato estruturado: SQLite pra começar, PostgreSQL se crescer. CSV pra export e análise rápida
python
import re
from datetime import datetime

TEAM_ALIASES = {
    "São Paulo FC": "São Paulo",
    "SPFC": "São Paulo",
    "Sao Paulo": "São Paulo",
    "SE Palmeiras": "Palmeiras",
    "CR Flamengo": "Flamengo",
    # ... adicione conforme necessário
}

def clean_team_name(raw: str) -> str:
    """Normaliza nome do time."""
    cleaned = re.sub(r'\s+', ' ', raw).strip()
    return TEAM_ALIASES.get(cleaned, cleaned)

def clean_odds(raw: str) -> float | None:
    """Converte odds de string pra float com validação."""
    try:
        odds = float(raw.replace(',', '.'))
        if 1.01 <= odds <= 100:
            return odds
        return None  # fora do range válido
    except (ValueError, AttributeError):
        return None

def clean_scraped_data(raw_games: list) -> list:
    """Pipeline de limpeza completo."""
    cleaned = []
    for game in raw_games:
        home = clean_team_name(game.get("home", ""))
        away = clean_team_name(game.get("away", ""))
        home_odds = clean_odds(str(game.get("home_odds", "")))
        draw_odds = clean_odds(str(game.get("draw_odds", "")))
        away_odds = clean_odds(str(game.get("away_odds", "")))
        
        if home and away and home_odds and away_odds:
            cleaned.append({
                "home": home,
                "away": away,
                "home_odds": home_odds,
                "draw_odds": draw_odds,
                "away_odds": away_odds,
                "scraped_at": datetime.now().isoformat()
            })
    
    return cleaned

Aspectos legais e éticos

Vamos falar sério sobre isso porque muita gente ignora. Scraping não é ilegal por si só na maioria dos países. Mas existem linhas que você não deve cruzar.

Regras de ouro do scraping ético

Respeite o robots.txt: se o site diz que /odds não pode ser acessado por bots, não acesse. Ponto final.

Não sobrecarregue o servidor: mantenha seus requests em frequência baixa. Um request a cada 30-60 segundos é razoável. Dez requests por segundo é ataque DoS.

Não redistribua dados: coletar pra uso pessoal é uma coisa. Revender dados coletados por scraping é outra completamente e provavelmente viola direitos autorais.

Não contorne autenticação: se o site exige login pra ver os dados, scraping sem estar logado é aceitável. Criar contas falsas pra acessar dados protegidos não é.

Leia os termos de uso: muitos sites proíbem explicitamente coleta automatizada. Você pode tecnicamente fazer, mas assume o risco de ter conta/IP banidos.

Na real, a melhor abordagem é sempre verificar se existe uma API oficial antes de partir pro scraping. Confira nosso guia de APIs de odds esportivas — pode ser que o dado que você precisa já esteja disponível de forma limpa e legal.

Quando scraping faz sentido e quando não faz

Faz sentido quando...

  • A casa não tem API e nenhum provider terceiro cobre ela
  • Você precisa de um mercado específico (tipo handicap asiático de uma liga obscura) que APIs não oferecem
  • É pra um projeto pessoal de estudo sem intenção comercial
  • Você precisa de dados visuais (tipo screenshots de odds pra análise manual)

Não faz sentido quando...

  • Uma API oficial ou de terceiro atende sua necessidade — sempre prefira APIs
  • Você precisa de dados em tempo real com latência sub-segundo — scraping é lento demais
  • O projeto é comercial e depende da coleta contínua — qualquer mudança no site quebra tudo
  • Você não tem disposição pra manter o scraper — sites mudam layout toda semana

Perguntas frequentes

Scraping de sites de apostas dá cadeia?

No Brasil, não existe jurisprudência clara sobre web scraping pra uso pessoal. A LGPD protege dados pessoais, mas odds de apostas são dados públicos. O risco prático é ter seu IP bloqueado e, no pior caso, receber uma notificação extrajudicial. Ninguém foi preso por fazer scraping de odds até hoje, que eu saiba.

Playwright ou Selenium?

Playwright, sem dúvida pra projetos novos. É mais rápido, a API é mais limpa, e a capacidade de interceptar requests de rede nativamente é um game-changer pra sites de apostas. Selenium ainda funciona, mas é tecnologia de uma geração anterior.

Preciso de proxies?

Se vai fazer mais do que alguns requests por dia, sim. Proxies residenciais rotativos (não de datacenter) são praticamente obrigatórios pra sites de apostas. BrightData e SmartProxy são os providers mais confiáveis. O custo começa em torno de $10/mês pra uso leve.

Dá pra fazer scraping de odds ao vivo durante o jogo?

Dá, via WebSocket intercept. Mas a complexidade sobe muito. Odds ao vivo mudam várias vezes por segundo, então seu pipeline precisa ser assíncrono e performático. Pra maioria dos projetos, focar em odds pré-jogo é mais prático e já oferece dados suficientes pra modelos de ML.