fern://programming
← log

Uma temporada de dados do Sofascore, e onde eles mentem

Coletando todas as notas de jogadores do Brasileirão 2025, e as três armadilhas nos dados: datas no fuso errado, zeros que nunca são enviados e rodadas jogadas fora de ordem.

Meu TCC precisava de todas as notas de jogadores do Brasileirão Série A 2025. O Sofascore tem, atrás da mesma API interna que o próprio site usa. Como não é uma API pública oficial, o crawler foi feito pra ser entediante: uma requisição a cada 1,2 a 2,4 segundos, novas tentativas com backoff, um checkpoint ao fim de cada rodada pra que uma queda retome em vez de recomeçar, e validação com Pydantic em cada linha antes de ela chegar no CSV.

Isso rendeu 11.977 registros de jogador por partida, de todos os 380 jogos, disputados entre 29 de março e 7 de dezembro de 2025. Coletar foi a parte fácil. Os dados tinham três armadilhas, e nenhuma delas gerou erro.

Datas no fuso errado

O crawler guardava as datas das partidas em UTC. Os jogos noturnos no Brasil começam às 21h ou mais tarde, o que já é o dia seguinte em UTC, então 67 das 380 partidas estavam com a data errada. A correção foi recalcular a data a partir do timestamp do início do jogo em America/Sao_Paulo, tanto na limpeza quanto no próprio crawler.

Três tipos de ausência

Uma célula vazia significava três coisas diferentes, e cada uma ganhou sua própria regra:

  • Não se aplica. Defesas de um centroavante. Essas chaves nunca aparecem pra jogadores de linha, então recebem -1, um valor que nenhuma contagem real pode ter.
  • Não medido. Estatísticas físicas (distância, sprints, velocidade máxima) existem em só 349 das 380 partidas, conduções de bola em só 83. Essas continuam ausentes. Preencher com zero seria afirmar que o jogador não correu.
  • Zero, omitido. O Sofascore não envia estatísticas que valem 0. Nos dados brutos, goals estava vazio em 11.124 linhas e expected_goals em 6.314, exatamente as linhas em que o jogador não finalizou. Esses viram 0.

Dentro dos grupos de físicas e de conduções, a regra é por jogador: se ele tem qualquer métrica do grupo, as que faltam são zeros omitidos. Se não tem nenhuma, o grupo não foi medido pra ele.

Um caso de borda: goals_prevented pode ser negativo pra goleiros (de -3,79 a 3,12), então ali o sentinela -1 colide com valores reais. A coluna de posição desfaz o empate.

Rodadas fora de ordem

O jeito óbvio de ordenar uma temporada é pelo número da rodada. Está errado. Rodadas são remarcadas por causa das competições continentais, e a maior parte da rodada 34 só foi jogada em novembro, depois de as rodadas 35 a 37 terem sido jogadas em outubro. Ao longo da temporada, 1.795 dos 72.010 pares possíveis de partidas (2,49%) têm números de rodada que contradizem o horário de início.

Isso deixa de ser curiosidade assim que você calcula o histórico de um jogador. Ordenar por rodada em vez de horário muda a ordem das partidas de 335 dos 686 jogadores (48,8%) com dois jogos ou mais. Pra um modelo que só pode olhar partidas passadas, isso é vazamento: um jogo futuro se infiltrando no histórico de um jogo passado. Tudo dali pra frente ordena por kickoff_timestamp, nunca por round.

Depois da limpeza, 163 atuações sem nota foram descartadas, todas de 7 minutos ou menos. Sobraram 11.814 linhas de 732 jogadores, que foi com o que os modelos de Quando o R² é bom demais foram treinados.