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().
Projeto de coleta e limpeza de dados
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.
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
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 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).
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_elenco | linhas | de onde vem |
|---|---|---|
elenco_brasileirao | 8.392 | página de elenco da Série A |
escalacoes_partidas | 173 | escalações dos jogos de 2020 (4 clubes) |
elenco_copa_do_brasil | 23 | elenco 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
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 elencojogadores
jogador_id · nome · posição · altura · fonte_altura · nacionalidade
3.736 linhasclubes
time_id · nome · cidade · uf
34 clubes que passaram pela Série Aclassificacao
temporada · time_id · posicao_final
200 linhas, da ordem das páginasestados
uf · estado · região
27 UFs, inclusive as 15 que não tiveram clubeelencos 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.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.LEFT JOIN.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;
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
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
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
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.
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
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:
# 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%.
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.
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
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:
ID 270380
ID 303479
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
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:
| jogador | o que chamou a atenção | altura final |
|---|---|---|
| Marcelo, goleiro, Cruzeiro 2024 e 2025 | A Wikipedia em inglês dava 1,79 m, baixo para um goleiro. | 1,91 m |
| Pablo, Vitória 2024 | O 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á 2021 | O homônimo do passo 08. | 1,80 m |
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
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.
| fonte | o que forneceu | licença ou termos |
|---|---|---|
| ESPN Brasil | os 200 elencos, posições, nacionalidades e 3.049 alturas | termos de uso não autorizam a redistribuição |
| Transfermarkt | 186 alturas | termos de uso não autorizam a redistribuição |
| Wikipedia (en e pt) | 189 alturas | CC BY-SA 4.0, com atribuição |
| Wikidata | 25 alturas | CC0, domínio público |
| Conferência manual | 3 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