[LV] lucasvieira.tech

Projeto de coleta e limpeza de dados

Elencos da Série A

Como montei uma base com os elencos do Brasileirão de 2016 a 2025, da coleta nas páginas da ESPN até a busca, em outras quatro fontes, pelas alturas que faltavam.

Outubro de 2026 · Web Scraping · Python · SQL · Wikidata (SPARQL)

Elencos

200

Jogadores

3.736

Linhas de elenco

8.588

Cobertura de altura

96,3%

20 clubes por temporada, 10 temporadas. A cobertura é a parte das linhas de elenco com altura conhecida: era 89,9% só com a ESPN.

01

O objetivo

Este projeto nasceu como a fase inicial de outro: um ambiente de treino de SQL com dados de futebol, no mesmo formato do SQLtaco. Para que os exercícios fizessem sentido, eu precisava de uma base com perguntas interessantes, e escolhi como fio condutor a altura dos jogadores: quem são os mais altos, como a altura varia por posição, por clube e ao longo das temporadas.

No meio do caminho, percebi que a construção da base merecia um relatório próprio, visto que coletar, organizar e conferir dados é uma parte grande da rotina de quem trabalha com análise e costuma ficar escondida atrás do resultado final. Sendo assim, esta página conta o processo na ordem em que ele aconteceu, com as decisões, os erros encontrados e o raciocínio por trás de cada correção.

A base completa não é publicada aqui, por causa dos termos de uso das fontes (passo 10). A página mostra apenas números agregados e poucos exemplos.

02

A coleta

A fonte principal são as páginas de elenco da ESPN Brasil, uma por clube e por temporada, coletadas em outubro de 2026 com scripts em Python, usando as bibliotecas requests, para as requisições às páginas, e Selenium, para as páginas que dependem do navegador. Antes da coleta, fiz um mapeamento simples das requisições que o site faz, para entender onde estavam os dados de cada elenco. O endereço segue um padrão, o que permitiu montar a lista das 200 páginas (20 clubes em 10 temporadas) antes de visitar qualquer uma delas:

espn.com.br/futebol/time/elenco/_/id/<time_id>/liga/BRA.1/temporada/<ano>

A lista de páginas também se mostrou útil por outro motivo: em cada temporada, os clubes aparecem na ordem da classificação final. Por tal razão, ela também virou a tabela de classificação, com uma conferência automática de que o primeiro clube de cada ano é o campeão conhecido (Palmeiras em 2016, Corinthians em 2017, e assim por diante até o Flamengo em 2025).

  1. coleta200 páginaselencos da ESPN, 2016 a 2025
  2. consolidação1 arquivo8.588 linhas, uma por jogador em cada elenco
  3. modelagem4 tabelasligadas por ID, com conferências (versão 1)
  4. complementos3 arquivos à mãoestados, sedes dos clubes e alturas de outras fontes
  5. versão 25 tabelas96,3% das linhas com altura

Cinco elencos incompletos

Cinco páginas vieram com elencos claramente menores do que deveriam: Palmeiras, Grêmio, Bahia e São Paulo em 2020 e Chapecoense em 2016. No caso da Chapecoense, 2016 foi o ano do acidente aéreo que vitimou grande parte do elenco, em novembro, o que pode ter influenciado o registro desse elenco. Com essa evidência, completei esses elencos com outras páginas da própria ESPN, as escalações das partidas (os quatro de 2020) e o elenco da Copa do Brasil (Chapecoense). São 196 linhas, e todas carregam a coluna fonte_elenco, para que qualquer análise possa separá-las.

fonte_elencolinhasde onde vem
elenco_brasileirao8.392página de elenco da Série A
escalacoes_partidas173escalações dos jogos de 2020 (4 clubes)
elenco_copa_do_brasil23elenco da Chapecoense na Copa do Brasil de 2016

Estar no elenco não quer dizer ter jogado. A ESPN lista os inscritos de cada clube, e essa lista corresponde, sobretudo, aos inscritos no começo da temporada, que no calendário brasileiro coincide com o começo do ano. A coleta também trouxe dados das partidas, mas eles se mostraram inconsistentes e, por isso, decidi não usá-los; a base não tem minutos nem escalações, fora dos quatro elencos completados acima.

03

Do arquivo único às tabelas

A coleta gera um arquivo só, em que cada linha repete o nome do clube e o cadastro inteiro do jogador. Isso é prático para ler, mas ruim para consultar: se a altura de um jogador mudasse, ela teria de ser corrigida em todas as linhas dele. Separei o arquivo em tabelas ligadas por ID, que é como os dados costumam estar organizados num banco de verdade e o que torna os JOINs necessários nos exercícios.

elencos

temporada · time_id · jogador_id · fonte_elenco

8.588 linhas, uma por jogador em cada elenco

jogadores

jogador_id · nome · posição · altura · fonte_altura · nacionalidade

3.736 linhas

clubes

time_id · nome · cidade · uf

34 clubes que passaram pela Série A

classificacao

temporada · time_id · posicao_final

200 linhas, da ordem das páginas

estados

uf · estado · região

27 UFs, inclusive as 15 que não tiveram clube
jogadoresjogador_idPKjogador_nomeposicaoaltura_mfonte_alturanacionalidadeelencostemporadaPKtime_idPK FKjogador_idPK FKmulti_clube_temporadafonte_elencoclubestime_idPKtime_nometime_slugcidadeufFKestadosufPKestadoregiaoclassificacaotemporadaPKtime_idPK FKposicao_final1N1N1N1NPK chave primária · FK chave estrangeira · 1 e N: um registro de um lado, vários do outro
O modelo relacional da versão 2. Cada linha de elencos liga um jogador a um clube numa temporada; clubes aponta para o estado da sede, e classificacao guarda a posição final de cada clube em cada ano.

Escolhas de modelagem

  • Um banco relacional. Os dados ficam em tabelas ligadas por chaves primárias (PK, que identificam cada linha) e estrangeiras (FK, que apontam para a linha de outra tabela). A base sai como arquivo SQLite, com as chaves declaradas, e o app roda as mesmas tabelas no DuckDB, ambos bancos relacionais.
  • Transações ACID. SQLite e DuckDB seguem o modelo ACID (atomicidade, consistência, isolamento e durabilidade), ou seja, uma gravação acontece por inteiro ou não acontece. O script aproveita isso ao gravar todas as tabelas numa única transação, confirmada só no fim, de modo que o arquivo nunca fica pela metade. Como a base é só de leitura no app, a propriedade que mais pesa aqui é a consistência; por tal razão, o script também confere as ligações por conta própria, visto que o SQLite só aplica as chaves estrangeiras quando essa verificação é ativada.
  • Os IDs são os da ESPN. Em vez de criar uma numeração própria, mantive os IDs da fonte, o que permite voltar à página de origem de cada jogador e clube. O custo é que o ID também não resolve tudo: quando dois cadastros têm o mesmo nome, só o contexto diz se são a mesma pessoa (passo 08).
  • Chaves compostas. Uma linha de elenco só é única pela combinação de temporada, clube e jogador, e a classificação, pela de temporada e clube. Nenhuma coluna sozinha basta.
  • O cadastro do jogador fica num lugar só. Posição, altura e nacionalidade ficam em jogadores, e não repetidas em cada elenco. A contrapartida é que esses atributos são os atuais, e não os da época. Jogadores mais novos, que subiram da base dos clubes, podem ter alturas diferentes entre os anos e, por isso, decidi usar os dados mais atuais.
  • As 27 UFs, e não só as 12 com clube. Assim, perguntas como "quais estados nunca tiveram clube na Série A?" podem ser respondidas com um LEFT JOIN.

O nome não identifica o jogador

Uma das primeiras consultas mostrou por que as ligações têm de ser feitas pelo ID: 353 nomes aparecem em mais de um jogador.

SELECT jogador_nome, COUNT(*) AS jogadores
FROM jogadores
GROUP BY jogador_nome
HAVING COUNT(*) > 1
ORDER BY jogadores DESC;
  • Vitinho · 14
  • Gabriel · 12
  • João Pedro · 11
  • Pedrinho · 10
  • Paulinho · 9
  • Pablo · 9
  • Felipe · 9
  • Yago · 8

Ou seja, uma consulta que agrupasse por nome juntaria 14 jogadores diferentes num só Vitinho. Esse detalhe voltou a aparecer, de forma mais sutil, na busca pelas alturas (passo 08).

04

Limpeza e conferências

O script que monta as tabelas usa só a biblioteca padrão do Python e para na primeira conferência que falha. Preferi assim a corrigir problemas em silêncio, visto que um erro que interrompe o processo é encontrado na hora, enquanto um erro corrigido sem aviso pode passar despercebido até a análise.

Espaços no fim

Dois nomes vinham da ESPN com um espaço no fim ("Wanderson ", "Júnior "). Num GROUP BY, o mesmo texto viraria dois nomes diferentes. Todo texto passa por um strip().

Vazios viram NULL

687 jogadores sem altura e 120 sem nacionalidade. Um texto vazio não é o mesmo que um dado ausente, e só o NULL é tratado como ausente pelo SQL.

Cadastro único

Posição, altura e nacionalidade aparecem em cada linha do arquivo original. O script confere que são iguais em todas as linhas do jogador antes de guardá-las uma só vez.

# trechos de scripts/preparar_app.py
assert cadastro == espn, f"atributo diferente entre tabelas: {jid}"
assert len(regs) == 20, f"{ano}: {len(regs)} clubes"
assert regs[0]["time_slug"] == CAMPEOES[ano], f"{ano}: 1º colocado {regs[0]['time_slug']}"
assert jogadores[jid][3] is None, f"alturas.csv: a ESPN já informa a altura de {jid}"
assert 1.50 <= altura <= 2.10, f"alturas.csv: altura fora do esperado para {jid}: {altura}"

Além disso, o script confere que as chaves não se repetem e que todo ID de uma tabela existe na tabela a que ele aponta. A base sai em três formatos: CSV, um arquivo SQLite com as chaves declaradas e os dados embutidos no app.

05

A altura vem em polegadas

Ao olhar a distribuição das alturas, algo não parecia natural: entre 1,57 m e 2,03 m, a ESPN tem apenas 19 valores diferentes, e alturas comuns como 1,79 m ou 1,81 m simplesmente não existem. A explicação é que a ESPN guarda a altura em polegadas inteiras (1 polegada = 2,54 cm) e converte para metros na hora de exibir. Entre 1,78 m (70 polegadas) e 1,80 m (71 polegadas), não há nada. A ESPN é uma empresa americana, e esse é o padrão dos esportes nos Estados Unidos, que medem a altura em pés e polegadas; quem já assistiu a um jogo da NBA provavelmente já tentou converter um 6'8" (2,03 m) para entender a altura de algum jogador.

Na prática, isso tem três consequências. Médias, medianas e faixas funcionam bem, mas comparações de valor exato não fazem sentido. Cada altura da ESPN carrega uma incerteza de cerca de 1,3 cm para cada lado, já que não se sabe se o valor real foi arredondado para cima ou para baixo. E, ao misturar fontes, uma altura em centímetros traz uma precisão, em teoria, que a ESPN não tem; por isso cada altura da base guarda a sua fonte_altura.

-- alturas fora da grade de polegadas: só podem vir de outra fonte
SELECT fonte_altura, COUNT(*) AS jogadores
FROM jogadores
WHERE altura_m IS NOT NULL
  AND ABS(altura_m - ROUND(ROUND(altura_m / 0.0254) * 0.0254, 2)) > 0.001
GROUP BY fonte_altura;

Resultado: nenhuma da ESPN, e 227 das 403 alturas de outras fontes.

06

Onde faltam alturas

Na primeira versão da base, só com dados da ESPN, 687 dos 3.736 jogadores (18%) não tinham altura. A falta não era uniforme: de 2016 a 2023, ficava entre 3% e 9% das linhas de elenco, e subia para 19% em 2024 e 23% em 2025. Esse detalhe importa, porque uma comparação entre temporadas poderia confundir a queda de cobertura com uma mudança na altura dos jogadores.

Versão 1 (só ESPN)Versão 2 (com outras fontes)
Parte das linhas de elenco com altura conhecida, por temporada. O eixo começa em 70%.
Ver os números por temporada

Os elencos com mais vazios eram recentes: Cuiabá 2024 (28 de 49 jogadores sem altura), Red Bull Bragantino 2024 (21 de 53), Bahia 2025 (17 de 47) e Vitória 2025 (16 de 49). Por posição, a falta era parecida, de 16% entre os atacantes a 20% entre os defensores, o que sugere que o problema estava no cadastro de jogadores recentes, e não num tipo de jogador.

07

Completando a altura com outras fontes

Antes de buscar, defini as regras: uma altura de outra fonte só preenche um vazio e nunca corrige a ESPN; toda altura nova vai para um arquivo à parte, com a fonte, o link consultado e a data; a altura tem de estar entre 1,50 m e 2,10 m; e nenhuma altura é estimada. Sem uma fonte citável, o jogador fica sem altura. O script recusa qualquer linha que fuja dessas regras.

A ordem das fontes seguiu o custo de cada consulta, da mais barata para a mais trabalhosa:

  1. Wikidata, base de conhecimento aberta da Wikimedia, consultada pelo serviço oficial de consultas SPARQL, e não por raspagem. Primeiro pelo próprio ID da ESPN, que o Wikidata registra para alguns jogadores (propriedade P3681), e depois por nome, clube e período no clube, aceitando só quando havia um único candidato.
  2. Wikipedia, em inglês e depois em português, dentro da categoria de jogadores de cada clube. Para aceitar, o nome tinha de bater e a ficha tinha de mostrar o clube nos anos da temporada.
  3. Transfermarkt, site especializado em elencos e transferências, pela página de elenco de cada clube em cada temporada (140 elencos), para os jogadores que ainda faltavam.
# Wikidata: jogadores com o ID da ESPN e a altura (simplificado)
SELECT ?jogador ?espn_id ?altura WHERE {
  VALUES ?espn_id { "162409" "304699" ... }
  ?jogador wdt:P3681 ?espn_id .           # ESPN FC player ID
  OPTIONAL { ?jogador wdt:P2048 ?altura } # altura
}

Jogadores por fonte da altura na versão final. Os outros 3.049 têm a altura da ESPN.

O Wikidata resolveu poucos casos (25), mas foi a fonte mais segura e a mais rápida, porque poucas consultas cobriram a fila inteira. A maior parte veio da Wikipedia em inglês e do Transfermarkt. Ao todo, 403 jogadores ganharam altura, e a cobertura das linhas de elenco subiu de 89,9% para 96,3%; em 2025, de 76,7% para 90,4%.

As fontes concordam com a ESPN?

Uma forma de conferir se as alturas novas são plausíveis é compará-las, por posição, com as da ESPN. As médias das outras fontes ficam de 0,5 a 2,1 cm acima, sempre na mesma ordem: goleiros, defensores, atacantes e meio-campistas. A diferença é pequena e pode vir do arredondamento da ESPN ou de as outras fontes terem mais jogadores jovens.

ESPNOutras fontes
Altura média por posição, contando cada jogador uma vez. Passe o mouse sobre um ponto para ver quantos jogadores entram na média.

Há também 58 alturas com evidência mais fraca, em que a ficha confirmava só uma das temporadas do jogador ou o nome batia apenas em parte. Elas ficaram na base, marcadas, para que uma análise mais conservadora possa retirá-las.

08

Quando o nome engana: o caso Jordan

Na conferência da busca automática, um caso mostrou com clareza o limite de identificar jogadores pelo nome. Dois IDs diferentes da ESPN apontavam para a mesma pessoa nas outras fontes, e todas as evidências pareciam confirmar que se tratava de um cadastro duplicado:

  1. ESPNDois "Jordan" em 2021: um atacante do Ceará (ID 303479) e um defensor da Chapecoense (ID 270380).
  2. busca automáticaA mesma página da Wikipedia ("Jordan, footballer, born 1999", 1,90 m) casou com os dois IDs, e o mesmo jogador do Transfermarkt aparecia nos elencos do Ceará e da Chapecoense em 2021. A leitura natural era a de um jogador que passou pelos dois clubes no mesmo ano, e os IDs foram unificados.
  3. sinais contráriosAs posições na ESPN eram diferentes, e o elenco do Ceará 2021 no Transfermarkt tinha um segundo Jordan, de 1,80 m.
  4. conferência manualCruzei o caso no ogol e encontrei uma reportagem do ge Ceará sobre o zagueiro. Eram duas pessoas.

ID 270380

Anderson Jordan

posição
zagueiro
altura
1,90 m (Wikipedia, Transfermarkt)
2021
Ceará e Chapecoense

ID 303479

Jordan

posição
atacante
altura
1,80 m (conferida no ogol)
2021
Ceará

A unificação foi desfeita, e cada ID voltou a ter a sua linha de elenco e a sua altura. Como curiosidade, segundo a reportagem do ge, o nome do zagueiro, incomum no futebol brasileiro, foi inspirado em Michael Jordan, do basquete.

Nome, clube e temporada batendo, e até a mesma página em duas fontes diferentes, não bastaram para identificar o jogador. O que desfez o engano foi o contexto: a posição diferente nos dois cadastros, um segundo homônimo no elenco e fontes que contam a história do jogador. Por isso a base guarda, em cada altura, a fonte e a forma como o jogador foi identificado; quando uma conferência mais cuidadosa contradiz a busca automática, o caso pode ser encontrado e corrigido.

09

A revisão manual

Depois da busca automática, revisei os casos em que uma altura destoava do esperado ou em que as fontes discordavam entre si. Três jogadores ficaram com uma altura conferida à mão, registrada como manual, com as fontes consultadas:

jogadoro que chamou a atençãoaltura final
Marcelo, goleiro, Cruzeiro 2024 e 2025A Wikipedia em inglês dava 1,79 m, baixo para um goleiro.1,91 m
Pablo, Vitória 2024O Transfermarkt dava 1,65 m; as páginas da Wikipedia, 1,75 m. Sofascore, Wikipedia e ogol concordam em 1,75 m.1,75 m
Jordan, Ceará 2021O homônimo do passo 08.1,80 m

Os 284 que ficaram sem altura

Ao fim da busca, 284 jogadores continuaram sem altura. Inspecionei a lista, pesquisei parte dos nomes em mecanismos de busca e os cruzei com sites especializados de clubes grandes, como a FlaPress, do Flamengo. A conclusão foi que boa parte deles são jogadores da base, inscritos por exigência do regulamento, que provavelmente nem entraram em campo nas temporadas citadas e não são conhecidos do público em geral. Ou seja, não há uma fonte pública confiável para a altura deles, e continuar procurando custaria muito para pouco ganho. Eles seguem na base, com a altura em NULL, e toda comparação de altura entre grupos mostra a cobertura ao lado.

10

Fontes, licenças e por que a base não está publicada

Cada fonte usada tem uma regra diferente para o reutilização dos dados, e isso definiu o que esta página pode mostrar. Wikidata e Wikipedia têm licenças abertas, mas a ESPN, de onde vêm os elencos e a maior parte das alturas, e o Transfermarkt não autorizam a redistribuição do que publicam.

fonteo que forneceulicença ou termos
ESPN Brasilos 200 elencos, posições, nacionalidades e 3.049 alturastermos de uso não autorizam a redistribuição
Transfermarkt186 alturastermos de uso não autorizam a redistribuição
Wikipedia (en e pt)189 alturasCC BY-SA 4.0, com atribuição
Wikidata25 alturasCC0, domínio público
Conferência manual3 alturas (ogol, Sofascore, Wikipedia)só o valor conferido, com as fontes citadas

Sendo assim, decidi publicar o método e o código, mas não a base completa. Esta página mostra apenas contagens, médias e poucos exemplos individuais, necessários para explicar o processo. O projeto tem caráter educacional, e nomear as fontes faz parte dele: saber de onde vem cada dado e o que se pode fazer com ele é tão importante quanto saber coletá-lo.

A coluna fonte_altura foi pensada também para isso. Uma futura versão pública do app de treino pode filtrar a base e manter só o que vem de fontes abertas, o que exige antes testar quanto da base o Wikidata e a Wikipedia conseguem cobrir sozinhos.

11

Limitações e próximos passos

  • A altura é o cadastro atual, e não a da época. Para adultos isso muda pouco, mas um jogador que subiu da base em 2016 pode ter crescido depois.
  • A posição tem só quatro grupos (goleiro, defensor, meio-campista e atacante). Zagueiros e laterais ficam juntos em "defensor", o que esconde a diferença de altura entre eles. Separar os dois exigiria outra fonte.
  • Elenco não é escalação. A base não diz quem jogou nem por quanto tempo, e o elenco reflete principalmente os inscritos no começo do ano, sem as mudanças ao longo da temporada. Ou seja, uma média "por elenco" inclui jogadores que talvez nunca tenham entrado em campo.
  • A cobertura de 2024 e 2025 continua menor (92,3% e 90,4%, contra 96% a 99% nos outros anos).
  • A temporada de 2026 foi coletada, mas fica de fora, por estar em andamento.
  • Este é um projeto educacional. Outras escolhas, tal como outra fonte principal, outra regra para aceitar um casamento de nomes ou a exclusão das alturas de evidência mais fraca, mudariam alguns dos números desta página. As escolhas estão explicadas ao longo do texto para que possam ser questionadas e refeitas.

Referências

  • ESPN Brasil, páginas de elenco do Campeonato Brasileiro Série A, 2016 a 2025. espn.com.br. Acesso em outubro de 2026.
  • Wikidata, serviço de consultas SPARQL. query.wikidata.org. Dados sob CC0.
  • Wikipedia, em inglês e em português. Textos sob CC BY-SA 4.0.
  • Transfermarkt, páginas de elenco detalhado por temporada. transfermarkt.com.br.
  • ge Ceará. Apresentado no Ceará, zagueiro Jordan pede tranquilidade com maratona de jogos em 2021. ge.globo.com. Usada no caso Jordan (passo 08).
  • ogol e Sofascore, usados na conferência manual de casos específicos.
  • IBGE, divisão regional do Brasil (regiões das unidades da federação).