Como coletar e estruturar dados semi-estruturados no R

Utilize uma simples rotina de web scraping para construir sua base de dados

Guilherme Viana
Data Hackers
9 min readMay 31, 2020

--

Photo by Dakota Roos on Unsplash

Este tutorial tem os seguintes pré-requisitos:

  • Nível intermediário em R;
  • Familiaridade com o pacote tidyverse, em especial dplyr, assim como com o operador pipe (%>% *atalho* ctrl + shift + M);
  • Conhecimento de construção de funções.

Pacotes que serão utilizados:

  • tidyverse (dplyr, purrr & stringr) — manipulação dos dados;
  • rvest — extração de dados de páginas da internet (web scraping);
  • glue — construção de nomes de objetos (strings);
  • [opcional] tictoc — temporizador simples;
  • [opcional/avançado] future & furrr — computação paralela;
  • [opcional] rio — importa e exporta dados.

Introdução

Não é novidade que uma quantidade absurda de informação está disponível online. Todos os dias compartilhamos, de graça, nas redes sociais, diversas informações pessoais sobre como nos sentimos, onde estamos, o que comemos, dentre outros dados. Todas essas informações são armazenados e estão se acumulando rapidamente¹.

As instituições, públicas e privadas, também fazem essa disponibilização de informações. A administração pública, na verdade, deve fazer essa publicização de suas informações, de acordo com a Lei da Transparência. Graças a isso, há muitos dados disponíveis para que possamos utilizá-los conforme nossos estudos e interesses. O principal problema é seu formato, que geralmente é não estruturado. Mas o que isso significa, exatamente?

Dados estruturados são fáceis de identificar, pois é o que queremos e precisamos para começar qualquer análise exploratória de dados (EDA): é a base de dados, em formato retangular, não importando onde está armazenada (excel, rdata, sql, etc.), na qual cada coluna representa uma característica específica (variável) de cada linha (unidade de observação).

E, de forma simplista, dados não estruturados são todos os que não se encaixam na definição acima — é necessário trabalhá-los para conseguir iniciar sua análise exploratória. Alguns exemplos dessa categoria incluem, dentre outros, as páginas web, publicações em redes sociais, áudios e vídeos.

O problema

Estamos (no meu trabalho) sempre estudando a adoção de novas informações para auxiliar as unidades acadêmicas e a universidade como um todo. Analisando os dados de disciplinas oferecidas, descobrimos que as informações que tínhamos sobre currículo dos cursos estavam desatualizadas; mas adivinha onde estavam atualizadas? Sim, na internet.

“com licença Luciana, muito fácil estar na internet quando já estava na internet”

Olhando a página de oferta de cursos da graduação, no campus Darcy Ribeiro, vemos que a oferta está elencada por curso, já com a informação de que são 96, dispostos em uma tabela organizada. Ao clicarmos em um dos links, somos encaminhados à página das áreas de concentração daquele curso, na qual o botão de currículo finalmente nos leva à relação de disciplinas que queremos, as quais não estão exatamente estruturadas.

Ou seja, os dados que buscamos estão na última página dessa cadeia de cliques (cursos -> opções -> disciplinas). Poderíamos até pensar em um “rápido” ctrl + c ctrl + v (copia e cola) para uma planilha de excel, se precisássemos de um ou dois cursos. Porém, com 96? Não obrigado. Então nos resta fazer um “rápido” web scraping. Bem melhor, certo?

Conceitos apresentados

Cada curso de graduação pode ter uma ou mais opções. Vamos pegar o curso de Ciências Sociais, por exemplo: os estudantes da UnB podem cursar uma dentre quatro opções: antropologia, sociologia, ciências sociais bacharelado ou ciências sociais licenciatura. E cada opção, por sua vez, tem um currículo distinto, que é a seleção de disciplinas (matérias) que o discente deve e/ou pode fazer para graduar-se em seu curso/opção escolhido.

A solução

Caso específico

O primeiro passo é tentar extrair os dados de uma página. Para isso vamos pegar, por exemplo, o curso de Artes Visuais, que tem apenas uma opção. Começamos chamando os pacotes que vamos utilizar (tidyverse, rvest & glue) e lendo a página, com a função read_html.

Agora teremos que saber só um pouquinho de html, antes de continuar. Para pegar exatamente o que queremos, que nesse caso é uma tabela em html, precisar conseguir indicar isso na programação. Para nossa sorte isso geralmente é simples, apesar de não parecer: precisamos inspecionar a página e copiar o xpath apropriado. Pequeno vídeo que eu fiz para exemplificar:

Com o xpath em mãos, usamos a função html_nodes para pegar exatamente a parte que vamos utilizar.

dados <- html_nodes(pagina, xpath = '//*[@id="datatable"]')

O objeto (dados) será sempre uma lista, devido não só à função utilizada, mas também às diversas propriedades do html que vêm junto. Inspecionando a lista, vemos que extraímos seis tabelas. Voltando à página e analisando-a, realmente temos seis tabelas — a primeira com informações gerais do curso, e as demais listando as disciplinas, dependendo das particularidades do currículo. Então aparentemente deu certo a extração. Para garantir, é bom olharmos cada sub-objeto (dados[[1]], dados[[2]], etc.), conforme figura abaixo. Apesar de parecer estranho, o nome da disciplina está ali, então vamos confiar, por enquanto.

Como mencionei anteriormente, não tenho especial interesse na primeira tabela (informações gerais do curso), porém posso aproveitá-la para pegar algumas informações complementares. Para isso, preciso separá-la e transformar esse emaranhando de html em uma tabela. Para tal, vou aplicar a função html_table, que transforma uma tabela html em um objeto mais simples, do tipo data.frame.

cabecalho <- dados[1] %>% html_table

Isso, no entanto, não dá certo — o objeto continua no formato de lista. Para resolver isso, temos que extrair a tabela dessa lista. A melhor opção, a meu ver, é utilizar a função map_df, do pacote purrr, que irá transformar os dados em tabela, aplicando (map) a função html_table aos dados, ao mesmo tempo que retorna o resultado no formato de data.frame (df).

cabecalho <- dados[1] %>% map_df(html_table)

O resultado não está bonito, mas é utilizável. Desse cabeçalho vou querer os códigos de curso e de habilitação, que vou mostrar em seguida.

OK. Agora devemos arrumar as principais tabelas. Para isso vou remover o cabeçalho e fazer o mesmo tratamento que fiz acima.

Isso já resulta em uma boa tabela. Mas podemos melhorar.

Podemos começar pela variável Créditos. De acordo com o próprio site do MatriculaWeb, esses valores correspondem, em ordem, aos créditos teóricos, práticos, de extensão e de estudo. O número total de créditos de uma disciplina é caracterizado pela soma das três primeiras categorias, então vamos fazer esse cálculo. Seria interessante, também, agregar as informações do cabeçalho.

Sabia que é possível renomear uma variável dentro do select? Não precisa chamar a função rename só para isso.

Agora sim! Temos o resultado final conforme esperávamos: sabemos os códigos do curso, da opção e de cada disciplina, assim como seu nome e quantidade de créditos. Estamos prontos para a próxima fase — generalização.

yey! faltam 95 só…

Generalização (todos os casos)

Para começar a generalização, precisamos, inicialmente, analisar os endereços (urls) e verificar se eles “fazem sentido”. Vou discorrer sobre isso.

A página que utilizamos tem o final cod=1376, o que indica que poderemos substituir os códigos de opção nesse final e acessar a página de cada um dos cursos/opções. Aparentemente. De qualquer forma, precisamos da listagem de todos os cursos e todas as opções, que não temos, mas podemos extrair.

Voltando à página que lista os 96 cursos de graduação do Darcy Ribeiro, queremos extrair a coluna “Código”. Analisando melhor essa tabela html, vemos que há a informação de modalidade, se presencial ou à distância. Não tenho interesse nos cursos à distância, então vou aproveitar a programação para manter apenas os cursos presenciais.

Por que eu peguei os códigos de curso mesmo? Porque, como vimos lá no começo, meu primeiro clique é na página do curso, seguido pelo clique no botão de currículo, para chegar na página que tem as disciplinas.

Próximo passo é pegar os códigos das opções. Para isso, precisamos do xpath do botão de currículo de cada curso, pois ao clicarmos nesse botão, somos direcionados à página da opção, com as disciplinas que queremos extrair, ou seja, esse link possui a informação que queremos. Fazemos então aquele procedimento de clicar com o botão direito -> inspecionar -> copiar xpath.

mantendo o mouse no botão de “currículo” o link aparece

Mas, diferentemente dos cursos, a informação não está prontamente disposta em uma tabela — vou precisar ir em cada curso, extrair esse endereço da opção, e extrair desse texto o número, que é o código da opção. Fazendo o teste com o curso de Artes Visuais, recebemos uma lista vazia como resultado, o que indica que o xpath não está exatamente correto.

Pegando um curso que tenha mais de uma opção, como Letras Tradução, vamos inspecionar o xpath de cada um dos botões para tentar decifrar o que está errado:

xpath b1 = /html/body/section/div/div[3]/div/div/div[2]/div[1]/a
xpath b2 = /html/body/section/div/div[3]/div/div/div[2]/div[3]/a

Com os xpath diferentes, precisamos achar uma solução para conseguir coletar tudo via programação. Que tal o “denominador comum” dentre esses dois xpath?

Isso funcionou, resultado em dois números: 4529 e 4511. Se formos verificar na página do MatriculaWeb, é isso mesmo!

Assim como fizemos com os cursos, vamos extrair todos os códigos de opção. Para isso, precisamos fazer nossa programação entrar no endereço de cada curso, consultar todos os botões de currículo e extrair, de cada um, o código de opção correspondente. Ufa. Não sei se vocês perceberam, mas parece ser uma tarefa computacionalmente intensiva. Por isso, vou botar um timer, para ter ideia do tempo que demora, utilizando o pacote tictoc. Vou utilizar, também, a função glue para criar os endereços de cada um dos cursos.

Como esperado, foi demorado (média de 220 segundos, no meu notebook). Mas deu certo, resultando em uma lista com 122 opções. Uma análise rápida permite a identificação de um curso com opção 0 (zero). Claramente um erro, então vou retirar da lista, para não nos atrapalhar mais adiante.

EXTRA

Podemos otimizar, computacionalmente, o código acima, utilizando as capacidades de computação paralela do software. Para isso, precisamos dos pacotes future e furrr. É necessário, também, transformar o for loop em função.

Fazendo isso, conseguimos diminuir o tempo de processamento para apenas 74 segundos, 32% do tempo inicial! Devo destacar que esse ganho de tempo depende do seu hardware — meu notebook é um i7 quad core. Máquinas melhores levarão menos tempo ainda. Não vou entrar em detalhes pois foge do escopo deste artigo.

Com a lista dos códigos de opção disponível, podemos finalmente pegar todos os currículos. Novamente, o programa deve entrar em cada link de opção e extrair as listas de disciplinas, fazendo a referência de qual curso e opção as mesmas pertencem. Esse código vai ser mais computacionalmente intensivo que o anterior, pois temos mais códigos de opção e são feitos alguns tratamentos nos dados.

E realmente meu notebook tomou seu tempo, sem preocupação. Depois de algumas tentativas frustradas (com erro), consegui após 843 segundos a listagem de disciplinas. Não tive coragem de fazer testes adicionais para pegar um tempo médio, então só posso concluir que demorou bastante.

essa tecnologia…

EXTRA

Utilizando, mais uma vez, os pacotes future e furrr, conseguimos diminuir drasticamente o tempo de processamento desse código para apenas 90 segundos! Aí sim, muito melhor.

Pronto! Conseguimos sair de um cenário em que passaríamos muita raiva copiando e colando tabelas desformadas, de muitas páginas, para um cenário em que, após cuidadosa análise das informações e utilizando as ferramentas certas, conseguimos extrair uma base de dados com todas as informações que precisamos, de uma só vez, demonstrando um pouco do poder da técnica de web scraping.

Obrigado por ficar até o fim, e até a próxima!

Meus contatos: IG, Twitter, GitHub

--

--

Guilherme Viana
Data Hackers

Estatístico, mestre em Adm, doutorando em Economia na UnB. Principais interesses: programação com R, análise & visualização de dados, dogs e jogos de RPG