Capturando dados do Campeonato Brasileiro com Python
Um passo a passo para você capturar os dados do Brasileirão
E aí, PessoAll!
Como eu disse no post “Sobre mim”, minha ideia é compartilhar o que eu for exercitando dos cursos e leituras que eu fizer.
Estou bastante feliz em vir hoje compartilhar o primeiro código “robusto” em Python que fiz.
Deixarei o link com o código e a base gerada ao final do texto para quem quiser utilizar.
Motivação
Eu gosto muito de futebol e vi algumas matérias sobre como a Alemanha usou dados na Copa, o Liverpool na Champions e o Grêmio que adotou a mesma solução da seleção alemã. Guardadas as devidas proporções, fiquei imaginando se teria uma base de dados pública que me permitisse analisar dados de jogos do futebol brasileiro.
Dei uma pesquisada, mas não encontrei uma base “pronta”. Nem no site da CBF. Porém vi que site concentra os dados, notícias de diversas competições que a entidade organiza. Acessei a página do Campeonato Brasileiro e lá tem dados desde 2012 da série A (tem das demais séries, mas olhei essa a princípio).
Os dados estão disponíveis, mas não de uma maneira simples de extrair. Precisaria fazer um trabalho braçal enorme para conseguir coletar os dados. Por quê? Bom… Por temporada são 380 jogos e os dados de cada jogo estão “espalhados” em diferentes locais da página. Levaria uns meses copiando e colando.
E agora? Como fazer?
Como contei anteriormente, fiz alguns cursos sobre diversos assuntos e acabei não praticando. Um deles foi o Python Básico, da Solyd (é gratuito!) que dá uma visão geral da linguagem . Dei uma “Googlada” para relembrar os conceitos que vi no curso e para verificar o que mais eu poderia utilizar para capturar os dados e construir uma base para armazená-los.
Cheguei a um artigo publicado no iMasters que me relembrou como utilizar a biblioteca requests que vi no curso e explicou rapidamente biblioteca BeautifulSoup para fazer web scraping.
A biblioteca requests fará uma solicitação ao servidor que armazena a página e o download da página; a biblioteca BeautifulSoup possibilitará analisar a página capturada pela requests e extrair as tags em que os dados.
E o que são essas benditas tags? O HTML é uma linguagem de marcação que utilizam as tags para definir como o navegador exibirá o conteúdo da página. Simplificando, para toda uma tag que abre outra fecha.
<html> <header> </header> <body> </body></html>
E como eu descobri quais as tags que eu queria capturar das páginas dos jogos? Cliquei com o botão direito no navegador e em “exibir o código fonte da página”
Ao analisar o código da página consegui identificar as tags que continham os dados que eu queria capturar: data, hora, local do jogo, placar e times.
Com as tags que desejava capturar enfim definidas…
Mãos à obra!
Para codar (sempre quis dizer isso!) utilizei o Jupyter Notebook por ter achado a interface mais agradável do que a do PyCharm, mas poderia ter utilizado o Google Colab ou a edição Community do Databricks. Até recomendo utilizarem o Colab ou o Databricks porque não precisarão instalar nada na máquina e possibilita o desenvolvimento e execução do código em qualquer dispositivo com conexão à internet, mas fica ao seu critério.
É importante dizer, antes de qualquer coisa que, excetuando as bibliotecas que fiz uso, o código que escrevi utiliza o considerado “básico da programação”: estruturas de dados (listas), variáveis e loops (for). E entenda que esse “básico”, para mim, é complexo (vem se tornando um pouco menos conforme venho exercitando).
Vamos ao código…
Captura
Como eu disse lá no começo do texto, o site da CBF disponibiliza os dados com as temporadas a partir de 2012 a atual, além das diversas séries. Como vi que as páginas da série A e B do campeonato são idênticas, resolvi ousar um pouco e expandir o código para capturar os dados também da série B.
Então agora o código teria que percorrer duas séries (A e B), 8 temporadas de cada e mais 380 jogos em cada temporada. Isso deu um número de 6.080(!) links de jogos para acessar e capturar os dados.
2 * 8 * 380 = 6080
Para isso utilizei três loops for para construir o link de cada jogo que deveria ser capturado:
for i in lstSerie:
serie = i
for j in range(2012, 2020):
ano = j
for nj in range (1, 381):
linkJogo = linkRaiz+serie+'/'+str(ano)+'/'+str(nj)
Exemplo do link que deve ser acessado:
BeautifulSoup
Utilizei as funções do BeautifulSoup para acessar as tags que eram do meu interesse na página do jogo:
a) Número do Jogo
soup.find(class_='color-white block text-1')
Retornou
<span class="color-white block text-1">
Jogo: 380
</span>
b) Data, hora e local do jogo
soup.find_all(class_='text-2 p-r-20')
Retornou
[<span class="text-2 p-r-20">
<i aria-hidden="true" class="glyphicon glyphicon-map-marker"></i>
Rei Pelé - Maceio - AL
</span>,
<span class="text-2 p-r-20"><i aria-hidden="true" class="glyphicon glyphicon-calendar"></i> Domingo, 08 de Dezembro de 2019</span>,
<span class="text-2 p-r-20"><i aria-hidden="true" class="glyphicon glyphicon-time"></i> 16:00</span>,
<span class="text-2 p-r-20"><i aria-hidden="true" class="glyphicon glyphicon-hd-video"></i> Premiere</span>]
c) Times
soup.find_all(class_='time-nome color-white')
Retornou
[<h3 class="time-nome color-white">Csa - AL</h3>,
<h3 class="time-nome color-white">São Paulo - SP</h3>]
d) Gols dos times
soup.find_all(class_='time-gols block')
Retornou
[<strong class="time-gols block">1</strong>,
<strong class="time-gols block">2</strong>]
Listas
Como viram as funções do BeautifulSoup , por conta de como o site foi desenvolvido, retornaram listas e como queremos trabalhar apenas com os dados, precisaremos efetuar alguns tratamentos.
Mas antes de irmos aos tratamentos, precisamos falar rapidamente sobre listas. As listas são estruturas de dados, em Python, delimitadas por colchetes [] e os elementos armazenados na lista são delimitados por vírgula.
lista = [elemento1, elemento2, ..., elementon]
E como se acessa cada item da lista? É necessário passar como parâmetro o index da posição que se deseja acessar. Ou seja, tomando como exemplo a lista acima, se eu quiser acessar o primeiro item da lista devo utilizar o comando lista[0] para o segundo devo utilizar lista[1] e assim por diante.
Retomando o nosso exemplo dos jogos, a lista abaixo armazena os times que disputaram o jogo:
lista_times = soup.find_all(class_=’time-nome color-white’)[<h3 class="time-nome color-white">Csa - AL</h3>,
<h3 class="time-nome color-white">São Paulo - SP</h3>]
Para acessar somente o mandante do jogo deve-se utilizar:
lista_times[0]
<h3 class="time-nome color-white">Csa - AL</h3>
E para acessar o visitante:
lista_times[1]
<h3 class="time-nome color-white">São Paulo - SP</h3>
Esse foi o mesmo padrão para as demais tags que foram capturadas, já que todas elas retornaram listas.
Um adendo, é importante acrescentar a função get_text() para que o resultado traga apenas o texto armazenado dentro das tags.
lista_times[1].get_text()
'São Paulo - SP'
Tratamento dos dados
Os dados em sua origem costumam não estar padronizados, seja por erro no input, incompatibilidade do encoding (UTF-8, ANSI…), diferença entre os ambientes de captura e armazenamento etc. E em 99,9% das vezes é necessário efetuar diversos tratamentos no dado para adequá-lo ao ambiente em que ele será carregado, para criar um novo campo a ser utilizado numa análise etc.
Tomemos novamente o exemplo dos times que disputam os jogos. Ao capturá-los é possível verificar que junto com nome retornou o estado do qual o time é:
'São Paulo - SP'
Achei bom separar estes dois dados e para fazê-lo utilizei o comando split() para o qual é necessário passar o parâmetro que serve de delimitador, no nosso caso o ‘-’:
#Armazenando o mandante do jogo em uma variável
mandante = lista_times[1].get_text() #Exibindo o mandante do jogo
'São Paulo - SP'##Separando o time da UF
mandante.split("-")
['São Paulo ', ' SP']
Como é possível verificar, novamente o retorno é uma lista.
Cada uma das tags retornou dados que precisaram de algum tipo de tratamento, seja o split, remoção de acentos ou remoção de espaços à direita e à esquerda, padronização do campo de data, conversão de strings em numéricos etc.
Todos esses tratamentos estão documentados e comentados no Jupyter Notebook que disponibilizo ao final do texto. Para organizar esses tratamentos, criei um bloco no notebook que concentra todas as funções que utilizei para tratar os campos.
Armazenamento
Para cada jogo pesquisado, armazenei em uma lista (sim, ela de novo!) todos os campos que criei a partir das tags:
lista = [str(ano), serie.upper(), num_jogo, num_rodada, num_turno
, estadio_jogo, cidade_jogo, uf_jogo
, data, diasemana, hora_jogo
, mandante, ufMandante, gol_mandante
, gol_visitante, visitante, ufVisitante
, gols_jogo
, resultado, resultado_mandante, resultado_visitante
, placar, linkJogo, dt_ini_cst, dt_fim_cst]
Na verdade, utilizei duas listas, a primeira armazena somente o jogo atual que o loop esta pesquisando e na outra eu “appendei” o histórico de todos os jogos que o programa capturou:
listaFinal.append(lista)
Se você exibir um registro da lista o resultado seria:
[['2019',
'A',
380,
38,
2,
'Rei Pele',
'Maceio',
'AL',
'2019-12-08',
'Domingo',
'16:00',
'CSA-AL',
'AL',
1,
2,
'Sao Paulo',
'SP',
3,
'Visitante',
'Derrota',
'Vitoria',
'2-1',
'https://www.cbf.com.br/futebol-brasileiro/competicoes/campeonato-brasileiro-serie-a/2019/380',
'2020-10-22 21:35:14.984952',
'2020-10-22 21:35:15.040861']]
Poderia ter utilizado um dicionário também, mas bati cabeça e não consegui fazer funcionar, então optei por ficar com a lista mesmo.
Ainda como parte do exercício utilizei a biblioteca Pandas para converter a lista em um DataFrame:
df = pd.DataFrame(
##Passando a lista onde foram armazenados os jogos capturados.
listaFinal
##Nomeando as colunas do Dataframe
, columns = ['Temporada', 'Série', 'Número do Jogo'
,'Rodada','Turno', 'Estádio', 'Cidade', 'UF'
, 'Data', 'Dia da Semana', 'Hora'
, 'Mandante', 'UF Mandante', 'Gol Mandante'
, 'Gol Visitante', 'Visitante', 'UF Visitante'
, 'Gols no Jogo', 'Resultado'
, 'Resultado Mandante', 'Resultado Visitante'
, 'Placar', 'Link do Jogo'
, 'Data de Início da Consulta'
, 'Data de Fim da Consulta'])
Olha o DataFrame aí!
Aproveitei para salvar o resultado em um arquivo Excel e facilitar caso alguém só queira analisar os dados como era a minha intenção no início.
df.to_excel(
##Nomeando o arquivo
"CampeonatoBrasileiro_Jogos_CBF_20121017_SerieAB.xlsx"
##Não gravar o index do DataFrame no Arquivo
, index = False)
Para não prolongar ainda mais o texto, vou parando por aqui, mas no notebook com o código ainda tem alguns complementos relacionados a profiling e visualização de dados que em posts futuros irei abordar com mais profundidade
Considerações finais
Você que chegou até aqui, muito obrigado por ler!
Ficarei muito grato se deixar seu comentário sobre o conteúdo, o texto, o código ou o que mais você achar por bem comentar e que possa me ajudar a melhorar.
Repositório com o código e os dados
O código e o excel com os dados estão armazenados no meu repositório do GitHub.
Por hoje é só, PessoAll!
Referências
Paruchuri, V. Aprendendo sobre web scraping em Python utilizando BeautifulSoup. iMasters, 2016. Disponível em: <https://imasters.com.br/back-end/aprendendo-sobre-web-scraping-em-python-utilizando-beautifulsoup>. Acesso em: 21 de out. de 2020
Beautiful Soup Documentation. c2004–2020. Página Inicial. Disponível em <https://www.crummy.com/software/BeautifulSoup/bs4/doc/>. Acesso em: 23 de out. de 2020