Raspagem de dados¶
Raspagem de dados (data scraping), em seu sentido mais amplo, é um conceito aplicado à obtenção de dados de um certo programa a partir de outro de forma a extrair conteúdo de alto valor que sejam, prioritariamente, de fácil interpretação a humanos. Atualmente, a raspagem de dados é um sinônimo para raspagem da web (web scraping), visto que a fonte mais ampla para coleta dados é a web. Então, o ato de “raspar” dados da web equivale a utilizar scripts, programas ou APIs para obter dados relevantes de sites, páginas, blogs, repositórios, ou qualquer outro lugar acessível por conectividade e requisição.
Através da raspagem de dados, podemos, entre outras coisas,
coletar preços de ativos do mercado financeiro em tempo real;
baixar históricos de sinistros em saúde pública, tais como os registros de casos durante a pandemia da Covid-19;
localizar matérias jornalísticas sobre um mesmo tema em diversos canais de comunicação;
encontrar o placar final de todos os jogos da NBA nos últimos 5 anos.
Durante o curso, você já lidou implicitamente com a raspagem de dados ao utilizar, por exemplo, a função pandas.read_csv
, por exemplo, para realizar a leitura de um arquivo CSV hospedado em um site da internet. Neste capítulo, faremos uma breve introdução à raspagem de dados usando BeautifulSoap
, um dos módulos Python mais populares para dissecar páginas da web, de maneira a ampliar a nossa compreensão acerca dessa área de conhecimento extremamente relevante para a ciência de dados.
HTML 5¶
A maioria das páginas da internet hoje são escritas em uma linguagem chamada HTML (HyperText Markup Language), desenvolvida no início da década de 1990 como a linguagem básica da internet. O consórcio W3C (The World Wide Web Consortium) é quem assegura os padrões abertos para desenvolvedores web. Desde 2014, a versão HTML 5 é a recomendada pela W3C para todos os criadores de sites. Em 2019, a W3C e a WHATWG assinaram um acordo para uniformizar o HTML, concluindo um documento de especificações.
Note
Para saber mais sobre a história do HTML e do legado de Tim Berners-Lee, o inventor da World Wide Web (WWW), leia este texto.
Um documento HTML é estruturado por meio de elementos enclausurados por um par de tags e tem a seguinte aparência:
<!DOCTYPE html>
<html lang="pt-br">
<head>
<title>Introdução à Ciência de Dados</title>
</head>
<body>
<h1>Ciência de Dados no século XXI</h1>
<p>A História começa neste <a href="historia-icd.html">link</a>.</p>
<!-- comentário -->
</body>
</html>
No exemplo, <head>
e </head>
são exemplo de tags de abertura e fechamento para a seção head do documento.
A árvore DOM¶
Os navegadores da web interpretam o código HTML e transformam em uma “árvore”. Esta árvore caracteriza o modelo de objetos do documento, ou, formalmente, DOM (Document Object Model). Na forma de árvore DOM, o código acima tornar-se-ia algo como:
|- DOCTYPE: html
|- html lang="pt-br"
|- head
| |- #text:
| |- title
| | |- #text: Introdução à Ciência de Dados
| |- #text:
|- #text:
|- body
|- #text:
|- h1
| |- #text: Ciência de Dados no século XXI
|- #text:
|- p
| |- #text: A História começa neste
| |- a href="historia-icd.html"
| | #text: link
| |- #text:
|- #comment: comentário
|- #text:
APIs¶
A raspagem de dados pode ser otimizada através de uma API (Application Program Interface). APIs são mecanismos (interfaces) que usam aplicativos de terceiros para realizar “conexões” e puxar dados. APIs são parecidas com módulos, mas não oferecem meramente um conjunto de funções, mas sim um programa capaz de operar com muitos dados. Embora uma API possa funcionar localmente (offline), sua utilidade para raspagem de dados é melhor exibida quando se conecta a aplicativos da web (online).
Diversas instuições fornecem APIs para que desenvolvedores possam coletar dados. No início deste capítulo, mencionamos algumas aplicações de raspagem de dados. Algumas são possíveis apenas por meio de APIs. Google, Facebook, Twitter, Yahoo e Elsevier são algumas das empresas que fornecem APIs para aplicações em buscas na web, redes sociais, finanças e literatura científica. No Brasil, podemos citar como exemplos relevantes
as APIs da B3 (Bolsa de Valores);
e as APIs do Governo Federal.
Note
Veja uma lista de APIs públicas neste site.
Métodos HTTP¶
Na web, em geral lidamos com o HTTP (HyperText Transfer Protocol), isto é, um protocolo de comunicação entre clientes e servidores. Quando raspamos dados, eles são transferidos por meio de requisições (requests) e respostas. Em geral, os quatro métodos mais comuns para transitar informações entre browsers e servidores web via HTTP são os seguintes:
GET
, para recuperar informação;POST
, para criar informação;PUT
, para atualizar informação;DELETE
, para deletar informação;
JSON e XML¶
APIs utilizadas para raspagem de dados comumente retornam a informação em formato XML (eXtensible Markup Language), blocada em tags ou JSON (JavaScript Object Notation), serializada. Embora JSON seja a escolha de APIs mais modernas, é importante ter em mente que muitos provedores de APIs as fornecem com saída XML.
Um dos argumentos em favor de JSON é a economia de caracteres. Por exemplo, a estrutura XML
<user><firstname>Juan</firstname><lastname>Hernandes</lastname><username>Fernandez</username></user>
possui 100 caracteres.
A mesma informação, serializada em JSON,
{"user":{"firstname":"Juan","lastname":"Hernandes","username":"Fernandez"}}
com 75 caracteres, pouparia 25% do espaço.
Web crawlers¶
Rastreadores da web (web crawlers) são programas que indexam informações da rede a partir de várias fontes. Pelo fato de se comportarem como “espreitadores metódicos”, eles também são conhecidos como bots, aranhas ou escutadores da rede. Eles trabalham de uma forma recursiva ad infinitum puxando conteúdo de páginas e examinando-os.
Crawlers são úteis para coleta de dados, porém baseiam-se em termos de serviço e conduta. Todo site público possui, de alguma forma, termos de serviço geridos por um administrador que declaram o que é permitido ao crawler fazer ou não. Essas permissões (allows) ou restrições (disallows) estão expostas em arquivo chamado robots.txt
. Qualquer site relevante possui um arquivo deste associado à sua URL. Para vê-lo, basta adicionar este nome após a URL do endereço. As restrições sobre crawlers esbarram na fronteira da ética, principalmente no que diz respeito à raspagem de dados. Contudo, não discutiremos essas questões aqui.
Abaixo, mostramos o arquivo robots.txt
para o site da UFPB.
Conteúdo de https://www.ufpb.br/robots.txt:
Para outros exemplos, veja:
Bibliotecas para raspagem de dados¶
Existem muitos ferramentas, bibliotecas e frameworks para raspagem de dados. Alguns exemplos são: _requests, grab, scrapy, restkit, lxml, PDFMiner. Neste capítulo, vamos dar enfoque ao módulo Python BeautifulSoap e seus interpretadores (parsers).
Vantagens de BeautifulSoup
¶
Segundo o site oficial, a BeautifulSoup
fornece métodos simples e expressões idiomáticas “Pythônicas” para navegar, pesquisar e modificar uma árvore de análise: um kit de ferramentas para dissecar um documento e extrair o que você precisa.
converte automaticamente os documentos recebidos em Unicode e os documentos enviados em UTF-8. Você não tem que pensar em codificações, a menos que o documento não especifique uma codificação e a Beautiful Soup não consiga detectar uma.
baseia-se em interpretadores (parsers) Python populares como lxml e html5lib, permitindo que você experimente diferentes estratégias de análise para obter flexibilidade.
Note
A biblioteca BeautifulSoup foi assim denominada em homenagem a um poema de Lewis Carroll de mesmo nome em Alice no País das Maravilhas, em alusão à Sopa de Tartaruga.
Raspando dados do site da UFPB¶
Neste exemplo, faremos uma raspagem no site da UFPB para coletar a lista de cursos de graduação. Os passos a serem seguidos são:
abrir uma requisição para a URL da PRG/UFPB;
coletar o HTML da página;
extrair o conteúdo da tabela de cursos na árvore DOM;
construir um DataFrame cujas colunas devem conter: nome do curso, sede, modalidade, nome do(a) coordenador(a) e Centro de Ensino que administra o curso.
Entretanto, antes de produzirmos o DataFrame, faremos uma breve explanação sobre outras funcionalidades do módulo BeautifulSoap
.
Primeiramente, abriremos uma requisição com urllib.request
.
from urllib.request import urlopen
from bs4 import BeautifulSoup
html = urlopen('https://sigaa.ufpb.br/sigaa/public/curso/lista.jsf?nivel=G&aba=p-graduacao')
bs = BeautifulSoup(html.read(),'html.parser')
Neste ponto, criamos o objeto bs
que contém a árvore DOM do documento HTML. Podemos acessar as partes do documento diretamente a partir das tags head
, body
, title
etc.
# impressão na tela de head e body
# omitidas por serem grandes.
# Teste em seu computador!
head = bs.head
body = bs.body
title = bs.title
title
<title> SIGAA- Sistema Integrado de Gestão de Atividades Acadêmicas </title>
Podemos puxar o conteúdo das tags com contents
.
# é uma lista
title.contents
[' SIGAA- Sistema Integrado de Gestão de Atividades Acadêmicas ']
# opera sobre str
title.contents[0].strip()
'SIGAA- Sistema Integrado de Gestão de Atividades Acadêmicas'
Podemos navegar na árvore por meio das tags.
head.link
<link class="component" href="/sigaa/a4j/s/3_3_3.Finalorg/richfaces/renderkit/html/css/basic_classes.xcss/DATB/eAF7sqpgb-jyGdIAFrMEaw__.jsf;jsessionid=57EEFAB5ED6AB35AB2C8CBCF201B3551" rel="stylesheet" type="text/css"/>
head.meta
<meta content="IE=edge" http-equiv="X-UA-Compatible"/>
body.li
<li><a class="sr-only sr-only-focusable" href="#conteudo">Pular para conteúdo</a></li>
body.span
<span class="icone-contraste"></span>
Navegação na árvore para baixo¶
Inversamente, podemos acessar elementos “pai” (ou “mãe”) a partir dos filhos com parent
.
list(head.link.parent)[:4]
['\n',
<meta content="IE=edge" http-equiv="X-UA-Compatible"/>,
'\n',
<meta content="width=device-width, initial-scale=1" name="viewport"/>]
Para iterar sobre os elementos “pai” (ou “mãe”), use parents
.
for h in head.link.parents:
print(type(h))
<class 'bs4.element.Tag'>
<class 'bs4.element.Tag'>
<class 'bs4.BeautifulSoup'>
Realizando buscas na árvore¶
Funções de localização bastante úteis são find_all
e find
. Podemos aplicá-la passando como argumento uma tag
body.find_all('td')[10:15]
[<td>Presencial</td>,
<td>SILDIVANE VALCACIA SILVA</td>,
<td>
<a href="portal.jsf?id=1626776&lc=pt_BR" title="Visualizar Página do Curso">
<img src="/sigaa/img/view.gif;jsessionid=57EEFAB5ED6AB35AB2C8CBCF201B3551"/>
</a>
</td>,
<td class="subFormulario" colspan="5">
CCA - CENTRO DE CIÊNCIAS AGRÁRIAS (CCA)
</td>,
<td>
AGRONOMIA
</td>]
uma lista de tags
body.find_all(['li','ul'])
[<ul id="menu-acessibilidade">
<li><a class="sr-only sr-only-focusable" href="#conteudo">Pular para conteúdo</a></li>
<li class="separador"></li>
<li><a class="toggleable" href="#" id="acessibilidade-contraste" title="Contraste"><span class="icone-contraste"></span><span class="sr-only">Auto Contraste</span></a></li>
<li class="separador"></li>
<li>
<a href="/sigaa/logon/" style="font-size: 15px; font-weight: bold;" title="Acessar o SIGAA">Entrar</a>
</li>
</ul>,
<li><a class="sr-only sr-only-focusable" href="#conteudo">Pular para conteúdo</a></li>,
<li class="separador"></li>,
<li><a class="toggleable" href="#" id="acessibilidade-contraste" title="Contraste"><span class="icone-contraste"></span><span class="sr-only">Auto Contraste</span></a></li>,
<li class="separador"></li>,
<li>
<a href="/sigaa/logon/" style="font-size: 15px; font-weight: bold;" title="Acessar o SIGAA">Entrar</a>
</li>,
<ul>
</ul>]
ou tag e classe.
Note
Classes dizem respeito ao estilo de formatação do arquivo HTML, que segue regras da linguagem CSS.
# busca <table data> com classe "subFormulario"
body.find_all('td',class_="subFormulario")[:3]
[<td class="subFormulario" colspan="5">
CBIOTEC - CENTRO DE BIOTECNOLOGIA
</td>,
<td class="subFormulario" colspan="5">
CCA - CENTRO DE CIÊNCIAS AGRÁRIAS (CCA)
</td>,
<td class="subFormulario" colspan="5">
CCAE - CENTRO DE CIÊNCIAS APLICADAS E EDUCAÇÃO (CCAE)
</td>]
Podemos também realizar buscas específicas por expressões regulares. Para isso, basta usar o módulo re
e funções como re.compile
.
import re
body.find_all(string=re.compile('GRAD'))
['Consulta de Cursos -\n\tGRADUAÇÃO',
'Através desta página você pode consultar os cursos de\n\tGRADUAÇÃO ofereridos pela UFPB. ']
body.find_all(string=re.compile('CENTRO'))
['\n\t\t\t\t\t\t\t\t\t\tCBIOTEC - CENTRO DE BIOTECNOLOGIA\n\t\t\t\t\t\t\t\t\t\t',
'\n\t\t\t\t\t\t\t\t\t\tCCA - CENTRO DE CIÊNCIAS AGRÁRIAS (CCA)\n\t\t\t\t\t\t\t\t\t\t',
'\n\t\t\t\t\t\t\t\t\t\tCCAE - CENTRO DE CIÊNCIAS APLICADAS E EDUCAÇÃO (CCAE)\n\t\t\t\t\t\t\t\t\t\t',
'\n\t\t\t\t\t\t\t\t\t\tCCS - CENTRO DE CIÊNCIAS DA SAÚDE (CCS)\n\t\t\t\t\t\t\t\t\t\t',
'\n\t\t\t\t\t\t\t\t\t\tCCEN - CENTRO DE CIÊNCIAS EXATAS E DA NATUREZA (CCEN)\n\t\t\t\t\t\t\t\t\t\t',
'\n\t\t\t\t\t\t\t\t\t\tCCHLA. - CENTRO DE CIÊNCIAS HUMANAS, LETRAS E ARTES (CCHLA)\n\t\t\t\t\t\t\t\t\t\t',
'\n\t\t\t\t\t\t\t\t\t\tCCHSA - CENTRO DE CIÊNCIAS HUMANAS SOCIAIS E AGRÁRIAS (CCHSA)\n\t\t\t\t\t\t\t\t\t\t',
'\n\t\t\t\t\t\t\t\t\t\tCCJ - CENTRO DE CIÊNCIAS JURÍDICAS (CCJ)\n\t\t\t\t\t\t\t\t\t\t',
'\n\t\t\t\t\t\t\t\t\t\tCCM - CENTRO DE CIENCIAS MEDICAS (CCM)\n\t\t\t\t\t\t\t\t\t\t',
'\n\t\t\t\t\t\t\t\t\t\tCCSA - CENTRO DE CIÊNCIAS SOCIAIS E APLICADAS (CCSA)\n\t\t\t\t\t\t\t\t\t\t',
'\n\t\t\t\t\t\t\t\t\t\tCCTA - CENTRO DE COMUNICAÇÃO, TURISMO E ARTES (CCTA)\n\t\t\t\t\t\t\t\t\t\t',
'\n\t\t\t\t\t\t\t\t\t\tCE - CENTRO DE EDUCAÇÃO (CE)\n\t\t\t\t\t\t\t\t\t\t',
'\n\t\t\t\t\t\t\t\t\t\tCEAR - CENTRO DE ENERGIAS E ALTERNATIVAS E RENOVÁVEIS (CEAR)\n\t\t\t\t\t\t\t\t\t\t',
'\n\t\t\t\t\t\t\t\t\t\tCI - CENTRO DE INFORMÁTICA (CI)\n\t\t\t\t\t\t\t\t\t\t',
'\n\t\t\t\t\t\t\t\t\t\tCT - CENTRO DE TECNOLOGIA (CT)\n\t\t\t\t\t\t\t\t\t\t',
'\n\t\t\t\t\t\t\t\t\t\tCTDR - CENTRO DE TECNOLOGIA E DESENVOLVIMENTO REGIONAL (CTDR)\n\t\t\t\t\t\t\t\t\t\t']
Se o resultado a ser localizado for único, podemos usar find
.
body.find(string=re.compile('Copyright'))
'\n\t\t\tSIGAA - 21.11.0 -| STI - Superintendência de Tecnologia da Informação | Copyright © 2006-2021 - UFPB\n\t\t'
Funções customizadas¶
Agora, implementaremos algumas funções customizadas para extrair o cabeçalho e o conteúdo da tabela de cursos do site da UFPB. Essas funções varrem a árvore DOM e coletam apenas as informações de interesse, transformando-as para listas.
# extrai cabeçalhos
def get_table_head(t):
'''Lê objeto tabela e extrai header para lista'''
res = []
thead = t.find('thead')
th = thead.find_all('th')
for f in th:
res.append(f.getText().strip())
return res
t_header = get_table_head(body.div)
t_header
['Nome', 'Sede', 'Modalidade', 'Coordenador', '']
# extrai linhas
def get_table_body(t):
res = []
tbody = t.find('tbody')
tr = tbody.find_all('tr')
for row in tr:
this_row = []
row_fields = row.find_all('td')
for f in row_fields:
this_row.append(f.getText().strip())
res.append(this_row)
return res
r = get_table_body(body.div)
Limpeza dos dados extraídos¶
Finalmente, construiremos o DataFrame a partir das listas anteriores. Porém, precisamos realizar procedimento de preenchimento de dados e limpeza.
Note que a tabela original não traz os nomes dos cursos organizados por linha. Então, precisamos de uma coluna com o nome de cada Centro de Ensino organizado por curso.
import pandas as pd
# cria DataFrame
df = pd.DataFrame(r,columns=t_header).drop_duplicates().reset_index(drop=True)
mask = df['Nome'].str.find('CENTRO') != -1
centros = df['Nome'].loc[mask]
idx = centros.index.values
# preenchimento
vals = []
for k in range(1,len(idx)):
for i in range(max(idx)+1):
if i >= idx[k-1] and i < idx[k]:
vals.append(centros.iloc[k-1])
# extra
dx = len(df) - max(idx)
vals.extend(dx*[vals[-1]])
# limpa e renomeia
df['Centro'] = vals
df = df.drop(idx).rename(columns={"Nome": "Curso"}).reset_index(drop=True)
df
Curso | Sede | Modalidade | Coordenador | Centro | ||
---|---|---|---|---|---|---|
0 | BIOTECNOLOGIA | João Pessoa | Presencial | SILDIVANE VALCACIA SILVA | CBIOTEC - CENTRO DE BIOTECNOLOGIA | |
1 | AGRONOMIA | Areia | Presencial | FABIO MIELEZRSKI | CCA - CENTRO DE CIÊNCIAS AGRÁRIAS (CCA) | |
2 | CIÊNCIAS BIOLÓGICAS- AREIA | Areia | Presencial | LAIS ANGELICA DE ANDRADE PINHEIRO BORGES | CCA - CENTRO DE CIÊNCIAS AGRÁRIAS (CCA) | |
3 | MEDICINA VETERINÁRIA | Areia | Presencial | GISELE DE CASTRO MENEZES | CCA - CENTRO DE CIÊNCIAS AGRÁRIAS (CCA) | |
4 | QUÍMICA | Areia | Presencial | DAYSE DAS NEVES MOREIRA | CCA - CENTRO DE CIÊNCIAS AGRÁRIAS (CCA) | |
... | ... | ... | ... | ... | ... | ... |
115 | QUÍMICA INDUSTRIAL | João Pessoa | Presencial | RAUL ROSENHAIM | CT - CENTRO DE TECNOLOGIA (CT) | |
116 | ADMINISTRAÇÃO PÚBLICA | João Pessoa | A Distância | JULIANA FERNANDES MOREIRA | CT - CENTRO DE TECNOLOGIA (CT) | |
117 | GASTRONOMIA | João Pessoa | Presencial | SAMARA DE MACÊDO MORAIS | CT - CENTRO DE TECNOLOGIA (CT) | |
118 | TECNOLOGIA DE ALIMENTOS | João Pessoa | Presencial | ISMAEL IVAN ROCKENBACH | CT - CENTRO DE TECNOLOGIA (CT) | |
119 | TECNOLOGIA EM PRODUÇÃO SUCROALCOOLEIRA | João Pessoa | Presencial | SOLANGE MARIA DE VASCONCELOS | CT - CENTRO DE TECNOLOGIA (CT) |
120 rows × 6 columns