Web Scraping de Odds com Python: APIs em Tempo
Como fazer scraping de odds em tempo real de casas de apostas usando Python. APIs públicas e boas práticas.
Por que isso é importante
Web Scraping de Odds com Python: APIs em Tempo. Como fazer scraping de odds em tempo real de casas de apostas usando Python. APIs públicas e boas práticas.
Já passei uns dias quebrando a cabeça tentando fazer scraping de casas de apostas antes de descobrir que a maioria tem APIs semi-públicas muito mais fáceis de usar. Vou te poupar esse tempo. A ordem de prioridade é sempre: API oficial primeiro, dados no JSON da página segundo, scraping de HTML como último recurso.
É legal fazer scraping de casas de apostas?
A resposta curta: depende. A coleta de dados públicos disponíveis a qualquer usuário geralmente é legal do ponto de vista do código. O que as casas proíbem nos termos de serviço é o uso desses dados pra fins comerciais ou criar serviços derivados sem autorização. Scraping pra uso pessoal e análise própria fica numa zona cinza — tecnicamente pode violar os termos, raramente viola a lei.
O risco prático não é legal — é técnico. Casas de apostas são muito boas em detectar e bloquear bots. Se você usar o IP da sua casa, sua conta pessoal pode ser suspensa. A abordagem mais segura: use APIs públicas quando disponíveis e nunca scrapeie em conta que você usa pra apostar de verdade.
Aviso legal e sobre apostas
Este tutorial tem propósito educacional sobre web scraping e Python. Antes de coletar dados de qualquer site, leia os Termos de Serviço do site em questão. Apostas esportivas envolvem risco financeiro real. Nunca aposte mais do que pode perder. Em alguns países, apostas online têm restrições legais — verifique a legislação do seu país.
APIs oficiais vs scraping (prós e contras)
APIs Oficiais
Use a API quando o site oferece acesso estruturado a dados
+ Prós
- • Dados estruturados e confiáveis
- • Suporte e documentação disponíveis
- • Não viola termos de serviço
- • Rate limits claros e previsíveis
− Contras
- • Nem sempre gratuito
- • Pode não ter todos os mercados
- • Dependência de terceiro
Web Scraping
Extrai dados diretamente do HTML ou requisições de rede
+ Prós
- • Acesso a dados sem API disponível
- • Geralmente gratuito
- • Mais flexível
− Contras
- • Quebra quando o site muda layout
- • Pode violar termos de serviço
- • Mais complexo (anti-bot, JavaScript)
- • Manutenção constante necessária
Tutorial com Python: coletando odds com requests
Antes do scraping de HTML, sempre inspeciona as requisições de rede do site (F12 → Network no Chrome). A maioria das casas modernas faz requisições Ajax pra uma API interna que retorna JSON. Capturar essa URL é muito mais fácil do que parsear HTML e muito mais estável.
import requests
import time
import random
from typing import Optional
# Simula um browser real — sem isso a maioria dos sites bloqueia imediatamente
HEADERS = {
'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',
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7',
'Accept-Encoding': 'gzip, deflate, br',
'Connection': 'keep-alive',
'Referer': 'https://www.example-sportsbook.com/',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-origin'
}
class OddsCollector:
"""
Coletor genérico de odds via HTTP.
Adapte a URL e o parsing pra cada casa de apostas.
"""
def __init__(self, base_url: str, delay_min: float = 2.0,
delay_max: float = 5.0):
self.base_url = base_url
self.delay_min = delay_min
self.delay_max = delay_max
self.session = requests.Session()
self.session.headers.update(HEADERS)
def _delay_aleatorio(self):
"""Aguarda tempo aleatório entre requisições (comportamento humano)."""
delay = random.uniform(self.delay_min, self.delay_max)
time.sleep(delay)
def get_json(self, endpoint: str,
params: Optional[dict] = None) -> Optional[dict]:
"""Faz requisição GET e retorna JSON."""
url = f'{self.base_url}{endpoint}'
try:
self._delay_aleatorio()
response = self.session.get(url, params=params, timeout=15)
response.raise_for_status()
return response.json()
except requests.HTTPError as e:
if e.response.status_code == 403:
print('Bloqueado! Tente rotacionar User-Agent ou usar proxy.')
elif e.response.status_code == 429:
print('Rate limit atingido. Aguarde antes de continuar.')
time.sleep(60) # Espera 1 minuto
return None
except Exception as e:
print(f'Erro: {e}')
return None
# Exemplo usando The Odds API (API oficial, sem scraping)
class TheOddsAPICollector:
"""
Wrapper pra The Odds API — use isso em vez de scraping direto.
API gratuita com 500 requests/mês no plano free.
"""
BASE_URL = 'https://api.the-odds-api.com/v4'
def __init__(self, api_key: str):
self.api_key = api_key
self.session = requests.Session()
self.requests_remaining = None
def listar_esportes(self) -> list:
"""Lista esportes disponíveis na API."""
url = f'{self.BASE_URL}/sports'
r = self.session.get(url, params={'apiKey': self.api_key})
self.requests_remaining = r.headers.get('x-requests-remaining')
return r.json()
def buscar_odds(self, esporte: str, mercados: str = 'h2h,totals') -> list:
"""Busca odds de um esporte específico."""
url = f'{self.BASE_URL}/sports/{esporte}/odds'
params = {
'apiKey': self.api_key,
'regions': 'eu,uk',
'markets': mercados,
'oddsFormat': 'decimal'
}
r = self.session.get(url, params=params)
self.requests_remaining = r.headers.get('x-requests-remaining')
print(f'Requests restantes: {self.requests_remaining}')
return r.json()
# Uso
api = TheOddsAPICollector(api_key='sua_chave')
esportes = api.listar_esportes()
esportes_ativos = [e['key'] for e in esportes if e['active']]
print('Esportes disponíveis:', esportes_ativos[:10])Armazenando dados em SQLite para análise
Coletar dados sem armazenar bem é tempo perdido. SQLite é perfeito pra começar — zero configuração, arquivo único, suporta consultas SQL completas. Quando a base crescer além de uns 50GB ou precisar de acesso concorrente, migra pra PostgreSQL com SQLAlchemy.
import sqlite3
import json
from datetime import datetime
from contextlib import contextmanager
@contextmanager
def get_db(path: str = 'odds.db'):
"""Context manager pra conexão com SQLite."""
conn = sqlite3.connect(path)
conn.row_factory = sqlite3.Row # Retorna dict-like rows
try:
yield conn
conn.commit()
except Exception:
conn.rollback()
raise
finally:
conn.close()
def setup_schema():
"""Cria o schema do banco."""
with get_db() as conn:
conn.executescript('''
CREATE TABLE IF NOT EXISTS snapshots_odds (
id INTEGER PRIMARY KEY AUTOINCREMENT,
jogo_id TEXT NOT NULL,
esporte TEXT NOT NULL,
time_casa TEXT NOT NULL,
time_fora TEXT NOT NULL,
data_jogo TEXT NOT NULL,
bookmaker TEXT NOT NULL,
mercado TEXT NOT NULL,
selecao TEXT NOT NULL,
odd REAL NOT NULL,
capturado_em TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_jogo_data
ON snapshots_odds (jogo_id, capturado_em);
CREATE INDEX IF NOT EXISTS idx_bookmaker
ON snapshots_odds (bookmaker, capturado_em);
CREATE VIEW IF NOT EXISTS odds_mais_recentes AS
SELECT s.*
FROM snapshots_odds s
INNER JOIN (
SELECT jogo_id, bookmaker, mercado, selecao,
MAX(capturado_em) as ultima_captura
FROM snapshots_odds
GROUP BY jogo_id, bookmaker, mercado, selecao
) m ON s.jogo_id = m.jogo_id
AND s.bookmaker = m.bookmaker
AND s.mercado = m.mercado
AND s.selecao = m.selecao
AND s.capturado_em = m.ultima_captura;
''')
print('Schema criado com sucesso')
def salvar_snapshot_odds(dados_jogos: list):
"""Salva snapshot de odds coletadas."""
registros = []
agora = datetime.utcnow().isoformat()
for jogo in dados_jogos:
for bm in jogo.get('bookmakers', []):
for market in bm.get('markets', []):
for outcome in market.get('outcomes', []):
registros.append((
jogo['id'],
jogo.get('sport_key', ''),
jogo['home_team'],
jogo['away_team'],
jogo['commence_time'],
bm['key'],
market['key'],
outcome['name'],
outcome['price'],
agora
))
with get_db() as conn:
conn.executemany('''
INSERT INTO snapshots_odds
(jogo_id, esporte, time_casa, time_fora, data_jogo,
bookmaker, mercado, selecao, odd, capturado_em)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', registros)
print(f'{len(registros)} registros salvos em {agora}')
# Consulta útil: ver evolução de odd ao longo do tempo
def historico_odd(jogo_id: str, bookmaker: str, selecao: str) -> list:
"""Mostra como a odd mudou ao longo do tempo."""
with get_db() as conn:
rows = conn.execute('''
SELECT odd, capturado_em
FROM snapshots_odds
WHERE jogo_id = ? AND bookmaker = ? AND selecao = ?
ORDER BY capturado_em ASC
''', (jogo_id, bookmaker, selecao)).fetchall()
return [dict(r) for r in rows]Automatizando com cron jobs
Com os módulos de coleta e armazenamento prontos, precisa de automação pra rodar de hora em hora sem você precisar ficar na frente do computador. Duas opções principais: cron do sistema operacional ou APScheduler dentro do Python.
# scheduler.py — Usando APScheduler pra automatizar coleta
from apscheduler.schedulers.blocking import BlockingScheduler
from apscheduler.triggers.cron import CronTrigger
from loguru import logger
import sys
# Configure loguru pra arquivo + console
logger.remove()
logger.add(sys.stdout, level='INFO')
logger.add('logs/coleta_{time}.log', rotation='1 day',
retention='7 days', level='DEBUG')
def tarefa_coleta_horaria():
"""Roda a cada hora pra capturar snapshot de odds."""
logger.info('Iniciando coleta horária')
try:
from main_coleta import coletar_todos_esportes
coletar_todos_esportes()
logger.info('Coleta horária concluída')
except Exception as e:
logger.error(f'Erro na coleta: {e}')
def tarefa_coleta_pre_jogo():
"""Coleta mais frequente (15min) pra jogos nas próximas 2h."""
logger.info('Coleta pré-jogo (15min)')
try:
from main_coleta import coletar_jogos_iminentes
coletar_jogos_iminentes(horas=2)
except Exception as e:
logger.error(f'Erro na coleta pré-jogo: {e}')
if __name__ == '__main__':
scheduler = BlockingScheduler(timezone='America/Sao_Paulo')
# Coleta a cada hora
scheduler.add_job(
tarefa_coleta_horaria,
CronTrigger(minute=0), # No início de cada hora
id='coleta_horaria',
name='Coleta horária de odds'
)
# Coleta a cada 15 minutos (pra jogos próximos)
scheduler.add_job(
tarefa_coleta_pre_jogo,
CronTrigger(minute='*/15'),
id='coleta_pre_jogo',
name='Coleta pré-jogo 15min'
)
logger.info('Scheduler iniciado. Ctrl+C para parar.')
try:
scheduler.start()
except KeyboardInterrupt:
logger.info('Scheduler encerrado pelo usuário')
# Alternativa: cron do sistema (mais simples)
# Adicione ao crontab com: crontab -e
# 0 * * * * /usr/bin/python3 /home/user/bot/coleta.py >> /home/user/bot/logs/coleta.log 2>&1
# */15 * * * * /usr/bin/python3 /home/user/bot/coleta_pre_jogo.pyBoas práticas (rate limiting, user agents, ética)
Dá pra ser técnico e ético ao mesmo tempo. Scraping agressivo derruba servidores e prejudica outros usuários. Rate limiting adequado, delays aleatórios e respeito a robots.txt são práticas básicas que todo scraper decente implementa.
Boas práticas de scraping que você deve seguir
Sempre use delays aleatórios entre requisições (2-5 segundos mínimo)
Respeite o arquivo robots.txt do site — se proibir bots, não scrapeie
Use APIs oficiais quando disponíveis — é mais confiável e mais ético
Não faça mais de uma requisição por segundo em média
Identifique seu bot no User-Agent com seu email se possível
Armazene dados localmente — não re-scrapeie o que já tem
Nunca use proxies pra burlar bloqueios em sites que proibem bots explicitamente
Checklist antes de publicar seu scraper
- Leu o robots.txt e os Termos de Serviço do site
- Implementou delays aleatórios entre requisições
- Tem tratamento de erro pra status 429 (rate limit) e 403 (bloqueado)
- Loga todas as requisições com timestamp pra debugging
- Tem mecanismo de retry com backoff exponencial
- Armazena dados localmente pra não repetir coletas desnecessárias
- Tem alerta quando a estrutura do site muda e quebra o parser
httpx
Alternativa moderna ao requests. Suporte a async/await pra coleta paralela mais rápida.
Playwright (Python)
Pra sites com muito JavaScript. Controla um browser real, passa pela maioria dos anti-bots.
Scrapy
Framework completo de scraping. Tem spider, pipelines e middleware pra rotação de headers.
rotating-proxies
Middleware Scrapy pra rotação de proxies quando você precisa escalar a coleta.
Com os dados coletados e armazenados, o próximo passo é análise. O artigo sobre como usar Python pra analisar odds mostra como processar esses dados com pandas pra encontrar discrepâncias entre casas. E se quiser o pipeline completo de análise a apostas, o tutorial do bot de apostas com IA integra tudo isso num sistema automatizado.