Biblioteca na cor branca, com estantes na cor preta.
Foto de @Oporanhho em Unsplash

Bibliotecário em Ciência de Dados: Uma introdução a coleta de dados

Francisco Foz
13 min readMay 30, 2023

--

A coleta de dados é uma das primeiras etapas no “processo de Ciência de Dados”, mas não por isso, ela sempre será mais rápida ou fácil.

Você pode apenas coletar dados em formatos mais simples, como um .csv, .txt, planilhas e JSON.

Você pode ter que consultar um banco de dados com SQL.

Talvez você possa coletar dados de uma API…

Ou ainda não existe uma API e você precisa fazer um web scraping…

Ou talvez não necessite e tenha uma biblioteca no PyPI (se você usar Python) que já te dê os resultados.

Há muitas e muitas formas e ela irá variar de acordo com o seu problema de negócio a ser resolvido.

Bom, no capítulo 9 do livro “Data Science do Zero”, de Joel Grus, ele aborda esse tema.

Quando iniciei esse blog, comecei a postar “resenhas” dos capítulos do livro, praticando em “problemas” no contexto da Biblioteconomia.

Os problemas não necessariamente são reais, mas sim para se tornar mais didático no entendimento do tema.

Mesmo que de forma introdutória, acabo demorando bastante tempo para postar um novo texto, porque ele se tornou apenas um elemento norteador dentro dos meus estudos em “Ciência de Dados”.

Mas no texto de hoje, vou falar a respeito da coleta de dados, com os principais temas abordados no capítulo.

Bora lá?!

Gif de um homem branco minereirando pedras em uma caverna, usando uma picareta e vestindo uma regata branca e uma jardineira azul.
Coletar dados pode parecer isso... às vezes.

Sumário

Abrindo um arquivo .csv

Obtendo dados da API do DOAB

Coletando dados do Twitter

Web Scraping no OASISBR

Considerações finais

Abrindo um arquivo .csv

Quando você começa a estudar Ciência de Dados, um dos primeiros passos é abrir um arquivo .csv.

O mais comum é usar o pd.read_csv() (no Pandas Python) e o read.csv() (com R).

Mas você sabe como abrir um arquivo csv sem o Pandas?

Se a sua formação de origem não é da área de tecnologia, provavelmente não saiba.

Eu não sabia!

Você pode abrir o arquivo e iterar ele com o .readlines()

with open(caminho, 'r') as arquivo_csv:
conteudo = arquivo_csv.readlines()

for linha in conteudo:
print(linha, end='')

Se quiser mais “performance”, apenas itere diretamente o arquivo:

with open(caminho, 'r') as arquivo_csv:
for linha in arquivo_csv:
print(linha, end='')

O “ganho de performance” nesse exemplo, com um arquivo csv de 366 linhas, rodando 1000 vezes, foi de aproximadamente 3% (e na realidade alguns milésimos de segundos a mais).

Verificar a perfomance foi apenas uma curiosidade 😅.

Você não vai usar esses métodos para abrir um csv no dia-a-dia, mas é legal ir conhecendo um pouquinho mais de como as coisas vão funcionando.

Obtendo dados da API do DOAB

Com alguma frequência, você pode querer obter dados através de uma API.

Como exemplo, vou extrair os dados da API do DOAB.

Logo do DOAB escrito em branco e laranja.

DOAB (Directory of Open Access Books) é um diretório de livros de acesso aberto.

Se você quiser consultar algum tema específico para sua pesquisa, trabalho etc, vale a pena consultar.

O único “impasse” é que geralmente são livros em inglês ou em outras línguas estrangeiras ao português.

O DOAB fornece mais informações da sua API nesse link.

Bom, mas vamos aos códigos!

Você vai precisar de bibliotecas para fazer a requisição HTTP (urllib), para carregar o JSON (JSON) e para formatar para um dado tabular (Pandas).

Fiz uma função para pesquisar por assuntos diversos dentro dela:

def assunto_url(assunto):
'''
Esta função recebe um assunto como entrada e retorna uma URL para fazer uma pesquisa desse assunto na API do DOAB.

assunto = string entre aspas duplas (ex.: "data science")
'''
assunto = assunto.replace(' ', '+')
url = f'https://directory.doabooks.org/rest/search?query={assunto}&expand=metadata'
return url

E outra para retornar o JSON:

def resposta_json(url):
'''
Esta função recebe uma URL como entrada, faz uma solicitação HTTP usando o cabeçalho armazenado na variável de instância headers,
lê a resposta HTTP e retorna o JSON da resposta.
'''
try:
req = Request(url)
response = urlopen(req)
json = response.read()
except HTTPError as e:
print(e.status, e.reason)
except URLError as e:
print(e.reason)
return json

Então eu carrego o JSON com o json.loads() e já tenho ele para manipular. :)

Imagem do JSON

JSONMas quando fiz a requisição, pedi para que viessem com os metadados e eles não estão no mesmo “nível” que os demais.

Por isso vou iterar ele:

data = json.loads(doab_resposta_json)
# para cada item na lista, crie um novo dicionário com as chaves de 'metadata' como chaves principais
for item in data:
metadata = {}
for meta in item['metadata']:
metadata[meta['key']] = meta['value']
# exclua a chave 'metadata' e adicione o novo dicionário ao objeto principal
del item['metadata']
item.update(metadata)

# imprima o resultado
data

Pronto!

Depois, posso apenas passar um pd.DataFrame e … :D

Imagem do DataFrame Pandas.

Coletando dados do Twitter

Talvez você precise coletar dados do Twitter, para ter algum cenário para sua unidade de informação… Ou talvez, só tenha curiosidade mesmo.

No livro, o autor usou a biblioteca Twython, mas eu acabei usando a Tweepy (eu já tinha feito um curso com ela e também achei mais “fácil” de usar).

A ideia aqui é apenas exemplificar, usando o básico do básico da biblioteca.

O primeiro passo é fazer uma conta de desenvolvedor no Twitter e obter suas credenciais.

Você pode então salvar elas em um arquivo JSON e deixá-las em um ambiente que só você tenha acesso.

Ele poderá fica assim:

{
"app_key":"f7QSm35s4d354ssTocmWEtMl",
"app_secret":"UAJzuhHVBJHVBJH54OVjbDfhbSdbuUrxSZagoIUSzuwSKi646",
"bearer_token": "AAAAAAAkjgjbgjhbAAAAGyzlQEAAAAApD84qXC2lGe55Y3DUh8aW1qzDlE%3DtCb16QBUf9XwIBIXYTGLwuxbV0pRlmf9dXWBvRx3UyO9CACgOQ"
}

Após isso, apenas abra o arquivo e defina nas variáveis. Dessa forma você não irá expor seus dados para, caso suba esse código no GitHub etc.

with open('/home/franciscofoz/Documents/credentials_twitter.json') as arquivo:
credenciais = json.load(arquivo)

bearer_token = credenciais['bearer_token']
client = tweepy.Client(bearer_token)

Vou usar o .search_recent_tweets() para buscar os tweets mais recentes de acordo com uma String de busca que definir.

Ela tem um limite de 100 tweets por requisição.

Vou usar a string de busca para a hashtag de “#Biblioteconomia”, trazendo os dados de quem twitou, o id do tweet e a data (precisa formatar para seu fuso horário) e salver ele em um arquivo .csv.

string_de_busca = '#Biblioteconomia'

tweets = client.search_recent_tweets(
string_de_busca,
expansions=['author_id'],
tweet_fields=['created_at'],
max_results=100
)

tweet_data = []

for tweet in tweets.data:
tweet_datetime = datetime.strptime(str(tweet.created_at), "%Y-%m-%d %H:%M:%S%z")
brasilia_offset = timedelta(hours=-3)

tweet_datetime_brasilia = tweet_datetime + brasilia_offset

# Obter o nome do usuário do tweet
users = client.get_user(id=tweet.author_id).data
username = f'@{users}'
username_name = users.name

date_brasilia = tweet_datetime_brasilia.date()
hora_brasilia = tweet_datetime_brasilia.time()
tweet_id = tweet.id
tweet_text = tweet.text
date_GMT = tweet_datetime.date()
hora_GMT = tweet_datetime.time()


tweet_data.append([username,username_name, date_brasilia, hora_brasilia, tweet_id, tweet_text,date_GMT,hora_GMT])


df = pd.DataFrame(tweet_data,
columns=['username',
'username_name',
'date_brasilia',
'hora_brasilia',
'tweet_id',
'tweet_text',
'date_GMT',
'hora_GMT'])

Feito isso, posso usar o NLTK, o WordCloud, o Matplotlib e o Pandas para manipular os dados e começar a extrair mais informações.

Quais usuários mais twitaram:

A contagem por datas:

E também posso fazer uma nuvem de palavras, removendo as stopwords (NLTK te ajuda com isso), além de outros termos que você possa também querer retirar.

tweets_biblioteconomia.tweet_text = tweets_biblioteconomia.tweet_text.str.lower()
palavras_tweets = []
for tweet in tweets_biblioteconomia['tweet_text']:
palavras_tweets.extend(tweet.split())

stop_words = stopwords.words('portuguese')
termos_excluidos = ['#biblioteconomia','biblioteconomia']

stop_words.extend(termos_excluidos)

palavras_tweets_sem_stopwords = []

for palavra in palavras_tweets:
if palavra not in stop_words:
palavras_tweets_sem_stopwords.append(palavra)

Então é apenas contar as palavras, com um for ou com o FreqDist do NLTK:

contagem = {}
for elemento in palavras_tweets_sem_stopwords:
contagem[elemento] = palavras_tweets_sem_stopwords.count(elemento)

sorted(dict(contagem).items(), key=lambda x: x[1], reverse=True)
frequencia_palavras = FreqDist(palavras_tweets_sem_stopwords)
sorted(dict(frequencia_palavras).items(), key=lambda x: x[1], reverse=True)

E criar a nuvem:

# Criar a nuvem de palavras
wordcloud = WordCloud(stopwords=stop_words,
background_color="white",
width=1920,
height=1080,
max_words=20,
prefer_horizontal=1).generate_from_frequencies(frequencia_palavras)
frequencia_palavras

# Plotar a nuvem de palavras
plt.figure(figsize=(15, 10))
plt.imshow(wordcloud, interpolation='bilinear')
plt.axis('off')
plt.show()
Nuvem de palavras formada a partir da busca da hashtag #Biblioteconomia

O RT é de retweet, se quiser excluir, você poderia modificar a string de busca para “#Biblioteconomia -is: retweet” e pronto.

Você pode ter notado que o @pedroisandretta é um dos termos que mais apareceram.

O Pedro Andretta é Bibliotecário, Professor na Universidade Federal de Rondônia e compartilha muita informação nas suas redes sociais, principalmente no seu Twitter.

Se você é uma pessoa bibliotecária que usa o Twitter, sem dúvidas você deveria seguir ele.

Há outras API’s do Tweepy para obter dados também.

Como essa que já conta a quantidade de Tweets de acordo com uma string.

.get_recent_tweets_count()

Vou apenas deixar o código e o resultado para o texto não ficar ainda mais extenso.

# QUANTIDADE TWEETS TWITTER

import json
import tweepy
from datetime import datetime, timedelta
import pandas as pd

# AUTENTICANDO CREDENCIAIS
with open('/home/franciscofoz/Documents/credentials_twitter.json') as arquivo:
credenciais = json.load(arquivo)

bearer_token = credenciais['bearer_token']
client = tweepy.Client(bearer_token)

# REALIZANDO BUSCA
string_de_busca = input('Digite sua string de busca:')

response = client.get_recent_tweets_count(string_de_busca, granularity="day")

dicionario_tweets_data = {'data':[],'quantidade':[]}

for count in response.data:
tweet_start = (datetime.strptime(str(count['start']), "%Y-%m-%dT%H:%M:%S.%fZ") - timedelta(hours=-3)).date().isoformat()
quantidade = count['tweet_count']

dicionario_tweets_data['data'].append(tweet_start)
dicionario_tweets_data['quantidade'].append(quantidade)

# CRIANDO DATAFRAME
df_tweets_data = pd.DataFrame(dicionario_tweets_data)
# OBTENDO PERCENTUAL
total = df_tweets_data['quantidade'].sum()
df_tweets_data['percentual'] = round(df_tweets_data['quantidade'].div(total) * 100,1)

print('-'*60)
print(f'String de busca: {string_de_busca}\n')
print(df_tweets_data,'\n')
print(f'TOTAL={total}\n')
print('-'*60)
Imagem do código da frequência de tweets da hashtag #InteligenciaArtificial
Imagem do código da frequência de tweets da hashtag #ChatGPT
Imagem do código da frequência de tweets da hashtag #MidJourney

Ah… Ela sempre irá buscar os tweets voltando 7 dias antes (de acordo com a sua hora atual, portando é 7*24 = 168 horas antes). Por isso o primeiro e último dia têm menores quantidades.

Web Scraping no OASISBR

Nem sempre os dados vão estar fáceis para você coletar.

Às vezes é necessário fazer, literalmente, uma “raspagem” com um “Web Scraping”.

A primeira etapa que você precisa fazer é ter certeza que não existe nenhuma outra forma de obter esses dados.

Web Scraping pode parecer legal, mas pode levar um tempão e tempo/trabalho perdido ninguém quer, certo?!

Além disso, você não pode sair “raspando” dados de todos os lugares sem “permissão”.

Para saber se você não pode obter esses dados através de uma raspagem, verifique o “/terms” do site ou o “/robots.txt” dele. Apenas passe ao final da URL.

Apenas como fins ilustrativos, vou fazer uma raspagem nos dados da OASISBR.

O Portal Brasileiro de Publicações e Dados Científicos em Acesso Aberto (Oasisbr) é uma iniciativa do Instituto Brasileiro de Informação em Ciência e Tecnologia (Ibict) que reúne a produção científica e os dados de pesquisa em acesso aberto, publicados em revistas científicas, repositórios digitais de publicações científicas, repositórios digitais de dados de pesquisa e bibliotecas digitais de teses e dissertações.

O portal já fornece estatísticas dos seus documentos:

E você pode exportar arquivos .csv das suas buscas.

Porém os arquivos .csv não possuem todos os metadados descritivos do documento:

E serão eles que eu vou buscar.

Nesse caso, você vai precisar da urllib (para a requisição HTTP), da BeautifulSoup (para o Web Scraping) e do Pandas (para manipular os dados).

Cada busca no portal gerará um resultado com diversos links, cada link é um documento com metadados, no qual quero salvar eles em um arquivo para posteriormente analisar.

Posso dividir em 5 etapas:

  1. Requisição HTTP de acordo com uma string de busca em um assunto.
  2. Tratamento do HTML
  3. Obtenção do número total de páginas da busca
  4. Web Scraping
  5. Criação do DataFrame
  6. Requisição HTTP de acordo com uma string de busca em um assunto.

Fiz duas funções, uma para buscar um termo dentro de um assunto e outra para realizar a requisição.

def assunto_url(assunto):
'''
Esta função recebe um assunto como entrada e retorna uma URL para fazer uma pesquisa desse assunto no site do OASISBr.
'''
assunto = assunto.replace(' ', '+')
url = f'https://oasisbr.ibict.br/vufind/Search/Results?lookfor={assunto}&type=Subject&limit=20'
return url


def resposta_html(url,headers):
'''
Esta função recebe uma URL como entrada, faz uma solicitação HTTP usando o cabeçalho armazenado na variável de instância headers,
lê a resposta HTTP e retorna o HTML da resposta.
'''
try:
req = Request(url, headers= headers)
response = urlopen(req)
html = response.read()
except HTTPError as e:
print(e.status, e.reason)
except URLError as e:
print(e.reason)
return html

2. Tratamento do HTML

Vou tratar o HTML, apenas para que ele fique mais legível, sem as suas marcações.


def trata_html(html):
'''
Esta função irá receber uma resposta http de uma de um endereço da web,
transformará os bytes do html em uma string e excluirá tabulações,
quabras de linhas e espaços dela.

input: String de um endereço da web
'''
#Transformando de btes para string
html = html.decode('utf-8')
#Excluindo tabulação, quebra de linha e espaços em branco das TAGs
html = " ".join(html.split()).replace('> <', '><')
return html

3. Obtenção do número total de páginas da busca

Agora começo a usar o BeautifulSoup.

Uso o .find() e o .find_all() para percorrer o HTML e pegar o valor da última página.

def obtem_total_paginas(html):
'''
Esta função recebe o HTML da página de resultados de pesquisa e retorna o número total de páginas
de resultados que correspondem à pesquisa.
'''
soup = BeautifulSoup(html, 'html.parser')
total_paginas = soup.find('ul', {'class': 'pagination'}).find_all('li')[-1].get_text()
total_paginas = total_paginas.replace('[', '').replace(']', '')
total_paginas = int(total_paginas)
return total_paginas
Imagem exemplificando onde está a tag HTML para ser “raspada”.

E percorro todas as páginas das buscas até o final da última página, coletando todos os links:

def links_paginas(url, total_paginas,headers):
'''
Esta função recebe uma URL de pesquisa e o número total de páginas de resultados como entrada.
Em seguida, extrai os links para todas as páginas de resultados e retorna uma lista de links para cada página.
'''
links_paginas = []
numero_pagina = 1
while numero_pagina < total_paginas + 1:
numero_pagina = str(numero_pagina)
resposta_html_pagina = resposta_html(url + f'&page={numero_pagina}',headers=headers)
html = trata_html(resposta_html_pagina)
soup = BeautifulSoup(html, 'html.parser')
for card in soup.findAll('div', {'class': 'result'}):
links_paginas.append('https://oasisbr.ibict.br' + card.find('a', {'class': 'title getFull'}).get('href'))
numero_pagina = int(numero_pagina)
numero_pagina += 1
return links_paginas

4. Web Scraping

Agora para cada link, preciso obter os dados da tabela cuja a classe do css é “citation table table-striped”.

Faço já alguns tratamentos e pronto!

def scraping_dados(links_paginas,headers):
'''
A função extrai informações como título, autor, data de publicação e tipo de documento para cada página e armazena
as informações em uma lista.
'''
dados_lista = []

for link in links_paginas:
url = link
html = resposta_html(url,headers=headers)
html = trata_html(html)
soup = BeautifulSoup(html,'html.parser')

table = soup.find('table',{'class':'citation table table-striped'})

data = {}
for row in table.find_all('tr'):
th = row.find('th').get_text()
td = row.find('td').get_text()
data[th] = td
metadados_separados_ponto_virgula = ['author_facet','author2','author2_role','dc.contributor.author.fl_str_mv',
'dc.contributor.authorLattes.fl_str_mv','dc.subject.eng.fl_str_mv','topic',
'dc.source.none.fl_str_mv']

for metadado in metadados_separados_ponto_virgula:
if metadado in data:
data[metadado] = data[metadado].replace('\n ',';').replace(';', '', 1)

for key in data:
data[key] = data[key].replace('\n ', '').replace('\n', '')

dados_lista.append(data)

return dados_lista

5. Criação do DataFrame:

Agora só crio o DataFrame do Pandas. :)

def dataframe_dados_lista(lista_dados):
'''
Esta função transforma uma lista de dicionários com os dados coletados da raspagem e retorna um DataFrame do pandas com esses dados.

'''

df = pd.DataFrame(lista_dados)

return df

Cada Web Scraping é um código e dependendo de como for, pode gerar um trabalhão.

As páginas, podem mudar e com isso o código também muda… mas ainda assim não é um “bicho de 7 cabeças.”

Lembrando que usei apenas a superfície do Beautiful Soup e tem muito a se explorar no mundo do Web Scraping.

Fica minha indicação de dois artigos da Ciência da Informação para você ler:

ScraperCI: um web scraper para coleta de dados científicos

“Ademais, é possível realizar análises não apenas dos aspectos técnicos e produtivos, mas também dos impactos sociais decorrentes do uso dessa tecnologia.“

Coleta de dados para agregação de repositórios digitais: Entidades vinculadas à Secretaria Especial de Cultura do Brasil

“Mesmo assim, com essa discrepância na disponibilização de dados dos acervos culturais públicos, o relato aqui apresentado, aponta como técnicas oriundas do contexto da ciência de dados podem auxiliar a explicitar, de maneira prática, as barreiras e possibilidades de reuso dos dados dos repositórios. (…)

Elencando, por exemplo, a afirmação de Virkus e Garaoufallou (2020), que apontam a curadoria dos metadados através de técnicas de ciência de dados, como uma responsabilidade dos cientistas da informação.

Dessa forma, como apontamento final deste artigo, reitera-se o potencial da aplicação de técnicas de ciência de dados em estudos da ciência da informação, tanto na análise de dados de pesquisa, quanto em formas de reuso dos dados.”

Considerações finais

Não foi apenas lendo o livro, lendo a documentação, praticando e testando, mas eu também fiz cursos lá na Alura.

Logo da Alura, escrito na cor branca e em um fundo azul, com desenhos que simbolizam a cidade de São Paulo, capital do estado de São Paulo.

Eu particularmente gosto bastante da plataforma devido a didática, qualidade técnica do conteúdo e amplitude de possibilidades dentro de um mesmo plano.

Eu recomendo para qualquer pessoa que queira aprender tecnologia no geral.

Se caso você não for um(a) aluna(o) ainda, confira meu cupom de desconto especial aqui.

Aqui estão alguns dos cursos que fiz dentre os conhecimentos que utilizei fazendo esse texto:

Tem muito mais do que apenas abrir um csv viu?!

Vale a pena se você quiser dar um outro passo na programação.

Nesse curso você poderá aprender mais sobre streaming de dados com Spark, através do Twitter.

Esse curso já foi descontinuado da grade oficial e em breve sairá um novo, mas nem por isso eu aprendi menos.

Para você ver a qualidade da plataforma.

Apenas fiz uma introdução ao tema e dá para se especializar muito mais, principalmente em Web Scraping e o funcionamento de bibliotecas específicas de coleta de dados.

Como dica de outras referências, fica do livro:

Web Scraping com Python: Coletando Mais Dados da web Moderna

Ainda não li esse livro, mas gosto bastante da O’Reilly, assim como da Casa do Código para livros de programação.

Agora me diga:

Qual outra técnica/forma de coleta de dados não foi abordada e você gostaria de compartilhar?

Compartilhe também outras referências de Biblioteconomia/Ciência da informação que usam dessas técnicas.

Deixe nos comentários e vamos compartilhar conhecimento.

Se você chegou até aqui e curtiu, dê palmas, compartilhe e se inscreva para me acompanhar.

Ainda há muito a se explorar…

--

--

Francisco Foz

Bibliotecário | Analista de dados | Disseminando informações para produzir conhecimento.