h3llh0und

Dia 3 de 100 - Web Scraping com Python - Parte 2 de 3

25 Oct 2019

Bom, primeiramente gostaria de lamentar profundamente por meu afastamento periódico da a atividades do #100daysofcode. Estou em uma situação mista de pós-operatório e de pico de condição neuropsiquiátrica transitória, somada a alguns outros estressores externos e a alguns jobs pesados que surgiram - e que foram concluídos com sucesso. Mas, como o desafio não pode parar, desde já combinamos que, por mais que haja espaçamento maior entre postagens, a contagem seguirá de 1 a 100 normalmente.

Para hoje (dia 3 de 100), seguimos na parte 2 sobre web scraping - agora com uma abordagem mais prática, porém simulada (leia-se, um ambiente propício pão aprendizado). A parte 3 será uma aplicação “real” dos dados aqui obtidos para inferência estatística. Havendo possibilidade, posteriormente faremos uma parte bônus com um hands-on “diferente”. 😜

Dados utilizados

Uma boa alternativa para o ambiente “simulado” são as seções de datasets do Kaggle e do Sports Reference. Em ambas, estão disponíveis boas quantidades de dados em condições para manipulação na forma como desejamos. Especialmente no caso do Sports Reference, há vários tipos de estatísticas, desde as mais simples até as avançadas, sobre Basquete Futebol Americano e outros, do universitário ao profissional das mais diferentes ligas. Esta será, pois, a base utilizada neste experimento “acadêmico” e, a seguir, há uma captura de tela para sua visualização.


Datasets do site Sports Reference

Como exemplo, usaremos o dataset de Basquete para extrair dados da NBA. Contudo, o conteúdo aqui transmitido possibilitará a você reproduzir o procedimento com poucas modificações em qualquer outro dataset, uma vez que todos pertencem à mesma empresa e possuem o mesmo padrão.

Bibliotecas utilizadas

  • requests: para para execução de requisições HTTP;
  • BeautifulSoup: para extração de dados em arquivos HTML e XML; e
  • Pandas: para armazenar, limpar e salvar os dados em forma de tabela.

Há algumas outras formas distintas de realizar os processos aqui apresentados (por exemplo, via Selenium e simulando requisições por uma página com interação simulada no Chromium), mas abordo aqui esta alternativa por seu desempenho otimizado.

Desta forma, usaremos a biblioteca requests para executar requisições GET e obter o código HTML das páginas que queremos, depois, utilizaremos a BeautifulSoup para extrair os dados que queremos destas páginas e, por fim, salvaremos esses dados em um dataframe do Pandas.

Extração de dados

Como primeiro exemplo, vamos utilizar a página de Estatísticas Individuais Totais da temporada de 2018/2019 da NBA. Esta página contém uma única tabela, onde cada linha representa um jogador, seguido de suas informações na temporada da NBA de interesse, como por exemplo a quantidade de minutos jogados e arremessos convertidos.

Utilizando a ferramenta de inspeção do navegador na tabela em questão para analisar a estruturação dos dados, verifica-se que eles estão armazenados em uma tabela HTML normal, representado pela tag table. Sabendo-se qual elemento devemos extrair da página HTML para conseguir os dados desejados, it’s time for hands on!


Inspecionando o código-fonte para identificar a estruturação dos dados

O primeiro passo é, claramente, importar a bibliotecas que serão usadas:

import pandas as pd
import requests
from bs4 import BeautifulSoup

Logo após, faremos uma requisição GET para a página. Para isso, devemos chamar o método requests.get com a URL da página como argumento.

req = requests.get('https://www.basketball-reference.com/leagues/NBA_2019_totals.html')

if req.status_code == 200:
    print('Requisição bem sucedida!')
    content = req.content

Após obter o código HTML da página, podemos utilizar a biblioteca BeautifulSoup para extrair a tabela. Primeiro, devemos criar um objeto que onde o documento será armazenado de forma estruturada (de acordo com as tags) e depois poderemos acessar o elemento que desejarmos, chamando o método find e passando como argumento o nome da tag desejada - neste caso, table. Segue:

soup = BeautifulSoup(content, 'html.parser')
table = soup.find(name='table')

Uma vez armazenado o código HTML da tabela, podemos utilizar o Pandas para carregar os dados em um dataframe utilizando o método read_html. Para tanto, há dois pontos que demandam atenção em dobro: o primeiro é que, antes de passar a variável table na função, devemos convertê-la para string, haja vista que, no momento, ela é um objeto do BeautifulSoup; o segundo ponto é que o retorno deste método é sempre uma lista de dataframes e, portanto, devemos acessar sua posição 0 para obter a tabela.

table_str = str(table)
df = pd.read_html(table_str))[0]

Faz-se mister frisar, ainda, uma nova particularidade: no caso corrente, a página trabalhada contém apenas uma tabela, havendo tão somente uma tag table para extrair. No entanto, em casos onde existam várias tabelas, para obter dados de uma tabela específica existem duas opções:

  1. Substituir o método find pelo find_all, o qual retorna uma lista de todos os elementos encontrados, e acessar a tabela desejada verificando em qual posição do vetor ela se encontra.
  2. Utilizar o argumento attrs do método find, passando um dicionário que indica quais atributos o elemento deve ter para ser extraído. Por exemplo, considerando a página de classificações em vez da página atual, é imaginando que queremos extrair as colocações dos times na conferência Oeste (Western Conference), usamos o inspetor para verificar que o id dessa tabela é “confs_standings_W”. Portanto, o código ficaria da seguinte forma:
table = soup.find(name='table', attrs={'id':'confs_standings_W'})

Extraindo dados de múltiplas temporadas

Como todas as páginas que exibem os dados de cada temporada são baseadas em um mesmo template, uma solução factível e otimizada é criar um loop que tiete sobre uma lista de anos, repetindo o processo para cada um destes.

Destarte, o código a seguir define uma função que automatiza o processo e extrai as estatísticas totais de 2015 a 2019 (escolha propria). Note que uma coluna Year é criada em cada extração para que seja possível diferenciar de qual ano cada estatística pertence no dataframe principal.

def scrape_stats(base_url, year_start, year_end):
    years = range(year_start,year_end+1,1)

    final_df = pd.DataFrame()

    for year in years:
        print('Extraindo ano {}'.format(year))
        req_url = base_url.format(year)
        req = requests.get(req_url)
        soup = BeautifulSoup(req.content, 'html.parser')
        table = soup.find('table', {'id':'totals_stats'})
        df = pd.read_html(str(table))[0]
        df['Year'] = year
        
        final_df = final_df.append(df)

    return final_df

url = 'https://www.basketball-reference.com/leagues/NBA_{}_totals.html'
df = scrape_stats(url, 2015, 2019)

E pronto! Temos armazenado em df um dataframe com

Conclusão parcial

Bom, neste ponto já estamos com nossos dados baixados e armazenados no dataframe df e, na próxima parte desta série, manipularemos este dataframe para extrair informações de interesse e mostrar exemplos de como usar e analisar estatisticamente seus resultados.

#100daysofcode #day3of100 #programming #coding #code #python #developer #coder #programmer #peoplewhocode #hacking #ethicalhacking #hacktheplanet #webscraping