Criando um script de extração de dados em Python 🐍
Quer criar aprender algo simples e que pode te ajudar muito no dia-a-dia em Python? Ou, quem sabe, quer se abrir e a partir daí deixar a criatividade te convidar a pensar o que pode surgir a partir desse pequeno aprendizado?
Vem entender comigo como eu fiz para passar um conjunto de dados de uma tabela para dentro de um banco de dados do MongoDB.
Tirando da caixa hoje algo bem especial, hoje eu quero mostrar a vocês como eu criei na semana passada um extrator de dados utilizando o Python como linguagem. Tive como ponto de partida o objetivo principal de criar uma base de dados dentro do MongoDB para posteriormente poder trabalhar com consultas nestes dados (que são de meu grande interesse — já chego lá e explico pra vocês que dados são esses).
Bom, mas daí tu me pergunta… de onde surgiu toda essa vontade de colocar em prática o python agora?? do nêidahhhh??? E onde tá o valor nisso???
Conta mais!
Conto! Vem comigo!
O exemplo que eu vou mostrar aqui é aplicado aos meus interesses pessoais, voltado ao ecossistema filmes e séries para atender a propósitos de curadoria de filmes. Foi prático de fazer e o resultado foi atingido, então decidi fazer esse tutorial que julguei relevante de ser compartilhado.
Link da branch do projeto: aqui
O extrator que eu vou mostrar pra vocês foi idealizado e concebido em alguns dias, e tem por responsabilidade ler os dados de um arquivo baixado da internet no formato TSV (Tab-Separated Values), realizar tratamentos quando necessário (normalização) e salvar os dados em coleções (collections) dentro de um banco de dados no MongoDB.
Quer saber como levar um bulk de dados da internet pra dentro de uma coleção do Mongo para posteriormente poder trabalhar com esta coleção, aplicando filtros, por exemplo?
Pois bem, para darmos o pontapé inicial na criação do script extrator, precisamos obter um conjunto de dados de interesse. Os arquivos com os dados que serão usados aqui são de propriedade do Internet Movie Database (IMDb) e têm por objetivo fornecer metadados a respeito de filmes (nome, ano de lançamento, gênero, duração, avaliação do filme, número de votos). O IMDb fornece as bases de dados de uso não comercial através do endereço https://developer.imdb.com/non-commercial-datasets/. As bases de dados que serão usadas nesse momento são as seguintes: title.basics.tsv.gz e title.ratings.tsv.gz.
Parênteses: o IMDb é referência absoluta em catalogação de dados de filmes, séries de TV e jogos. Eles possuem uma robusta API para fins comerciais através do AWS Data Exchange ao custo de $150k USD/y (tá validado de que tá correta essa informação mesmo, tá? hahaha. Eu quando li precisei ler umas 3 vezes e ainda confirmei a informação pesquisando hehe). Essa API oferece exatamente os mesmos dados que são obtidos através dos bulks não-comerciais gratuitos que estamos utilizando, mais o adicional do Box Office Mojo (um site que rastreia as receitas de bilheteria de forma sistemática e algorítmica).
Data-mining com pandas
Pandas! A força necessária na manipulação dos dados
Mãos-à-obra no entendimento dos dados. Como etapa inicial, é necessário fazer download dos arquivos title.basics.tsv.gz e title.ratings.tsv.gz . Iremos nesse momento de reconhecimento dos dados, abrir os arquivos, ver os tipos de dados e entender o tipo de mineração nos dados que deve ser feita. Quero dizer com isso que iremos pensar no tipo de dados, nos assegurar de que os dados estão no formato desejado e de que forma iremos limpar eles (deixá-los consistentes e com as informações preenchidas). Basicamente é todo o entendimento de se iremos submeter os dados a alguma espécie de tratamento e como esse tratamento deverá ser feito.
Iremos criar aqui 2 scripts, um para a importação de cada arquivo. Cada um deles irá ler um documento no formato TSV e realizar manipulações (parametrizações) nos dados. Para isso, nada melhor do que usar o pandas, que é uma biblioteca para Python amplamente usada para a manipulação e análise de dados.
Os nossos conjuntos de dados são tabelas, com os dados separados por tabulação. Para a manipulação de tabelas, o pandas possui a estrutura chamada DataFrame, que é a estrutura bidimensional do pandas, similar à tabela, onde cada coluna pode ter o seu tipo de dado. No arquivo title.ratings.tsv, os dados ocupam 3 colunas e estão separados pela tabulação:
tconst averageRating numVotes
tt0000001 5.7 2059
tt0000002 5.6 277
tt0000003 6.5 2026
tt0000004 5.4 179
tt0000005 6.2 2793
No DataFrame do pandas, cada coluna é tratada como uma chave, que pode ser usada para acessar os dados dessa coluna, de modo que o panda lê os dados da tabela acima deste modo:
data = {
'tconst': ['tt0000001', 'tt0000002', 'tt0000003', 'tt0000004', 'tt0000005'],
'averageRating': [5.7, 5.6, 6.5, 5.4, 6.2],
'numVotes': [2059, 277, 2026, 179, 2793]
}
Onde cada coluna possui seus valores, e, após a aplicação da função de conversão dos dados em DataFrame, o DataFrame resultante é:
tconst averageRating numVotes
0 tt0000001 5.7 2059
1 tt0000002 5.6 277
2 tt0000003 6.5 2026
3 tt0000004 5.4 179
4 tt0000005 6.2 2793
Porém, este formato do DataFrame não é suportado pelo Mongo. No MongoDB, os registros individuais são chamados de documentos. Cada documento é uma estrutura de dados JSON/BSON que consiste em pares chave-valor:
[
{'tconst': 'tt0000001', 'averageRating': 5.7, 'numVotes': 2059},
{'tconst': 'tt0000002', 'averageRating': 5.6, 'numVotes': 277},
{'tconst': 'tt0000003', 'averageRating': 6.5, 'numVotes': 2026},
{'tconst': 'tt0000004', 'averageRating': 5.4, 'numVotes': 179},
{'tconst': 'tt0000005', 'averageRating': 6.2, 'numVotes': 2793}
]
Precisararemos realizar uma conversão de modo que os dados sejam transformados em uma lista de dicionários. Um dicionário em Python é uma coleção de itens, onde cada item é um par chave-valor. Com os itens convertidos, poderá ser salvo no banco.
Criando os scripts
Começaremos criando um projeto novo usando o editor de código VSCode — código aqui — e criaremos um arquivo para o script de extração para cada um dos documentos de origem.
Bom, com o projeto criado e as bases de dados baixadas na máquina, iremos elaborar e rodar dois scripts de extração de dados, um para o title.basics, e um para o title.ratings.
Será comum a ambos os scripts:
- As importações das bibliotecas necessárias: pandas para a manipulação dos dados, pymongo para realizar a leitura e escrita no MongoDB, e as dependências os e dotenv para a interação com variáveis de ambiente;
- A função que realizará a leitura do arquivo bruto dos dados e o converterá em um DataFrame do pandas, tornando-o, assim, apto à manipulação;
- Algum tratamento, quando aplicável, nos dados;
- A criação de um Mongo Client para realizar a conexão com o servidor MongoDB e o acesso ao banco de dados e coleções que serão criadas;
- A conversão do DataFrame para uma lista de dicionários, aceito pelo Mongo para salvamento dos registros;
- A inserção dos dados já tratados e na estrutura de dicionário na coleção do Mongo.
Primeiro script: avaliações de filmes
Vamos começar criando o script que irá extrair os dados do arquivo de avaliações de um filme ou série (title.ratings). O script irá extrair todos os parâmetros existentes no arquivo original, composto por:
- tconst (string) — identificador alfanumérico exclusivo do título
- AverageRating — média ponderada de todas as avaliações individuais dos usuários
- numVotes — número de votos que o título recebeu
Importações
Como os arquivos com os dados possuem origem na máquina local (aka. "meu computador") , tornei estes caminhos variáveis de ambiente dentro do arquivo .env
, e fiz o mesmo com o nome das coleções que serão criadas para armazenar cada um dos documentos também. Utilizei para isso as dependências os
e dot-env
para interagir com elas a partir do .env
no ambiente de execução do Python. Enquanto que o os
é uma biblioteca padrão do Python, que fornece funções para interagir com o sistema operacional (manipulação de arquivos e diretórios, variáveis de ambiente, etc), o dotenv
é uma biblioteca externa usada para carregar variáveis de ambiente a partir de um arquivo .env
para o ambiente de execução do Python. A função load_dotenv
é utilizada para carregar essas variáveis.
Função de leitura dos dados
Entrando de fato na execução do script, iniciamos escrevendo a função que irá ter como argumento o caminho do arquivo na máquina local. Iremos dar à função o nome de read_tsv
:
Dentro da função, o bloco try
é usado para tentar executar o código. Nele, eu defini uma variável local "df" para retornar o método de leitura de arquivos read_csv
do pandas, que é usado para carregar o código e convertê-lo em um DataFrame do pandas. Passei como argumentos do método: file-path
, sep
(o tipo de separação) e low-memory
, um argumento usado para melhorar a eficiência de leitura de arquivos muito grandes (esta base tem cerca de 1.5M registros). Caso a excecução entre na Exception
, irá parar a execução do código e imprimir o erro.
Inserção dos registros no Mongo
Adicionei logs nas etapas dentro de cada bloco para ir acompanhando a execução do código.
Dentro do bloco try:
- Conecta ao MongoDB através da dependência
pymongo
e seleciona a coleção especificada. Uma coleção é um grupo de documentos no MongoDB, semelhante a uma tabela em um banco de dados relacional. Aqui, estamos criando a collection referente às avaliações (ratings) de um filme dentro plataforma do IMDb; - Prepara o documento: converte o DataFrame em uma lista de dicionários, através do método
.to_dict()
dopandas
, pois o MongoDB espera documentos no formato JSON/BSON, conforme já vimos antes. O método.to_dict()
espera como parâmetro uma orientação. Para criar registros no MongoDB a partir de um DataFrame do pandas convertido em dicionário, a orientação mais adequada é 'records', onde casa linha é transformada em um dicionário, facilitando a inserção de múltiplos documentos. Assim, cada linha do DataFrame será um BSON. - Insere os dados na coleção usando o método
insert_many()
dopymongo
. Este método aceita uma lista de dicionários, onde cada dicionário é um item a ser inserido na coleção.
Após a exception, que irá lançar a exceção em caso de erro, encerramos a conexão com o Mongo dentro do finally.
Executando as funções
Com as funções que executam as atividades já escritas, precisamos concatená-las para rodar o código que irá ler o arquivo e inserir os registros no MongoDB.
Criamos uma função main para a execução principal do script.
- Dentro do bloco try, é executada a função que lê o arquivo tsv e carrega em um DataFrame, e posteriormente insere os dados do DataFrame no MongoDB, na coleção especificada por collection_name.
- A linha
if __name__ == "__main__":
é uma construção comum em scripts Python. Ela é usada para garantir que um certo bloco de código seja executado apenas quando o script é executado diretamente, e não quando ele é importado como um módulo em outro script.
Após instalar o ambiente local, ativar o ambiente local, instalar as dependências, conectar o Mongo e rodar o código, vemos o script iniciar através dos logs espalhados pelo script, e podemos acompanhar a importação dos registros:
Segundo script: metadados essenciais sobre os filmes
Vamos agora criar o script que irá extrair os dados do arquivo de avaliações de um filme ou série (title.basics). O script irá extrair todos os parâmetros existentes no arquivo original, composto por:
- tconst (string) — identificador alfanumérico exclusivo do título
- titleType (string) — o tipo/formato do título (por exemplo, filme, curta, série de TV, episódio de TV, vídeo, etc)
- primaryTitle (string) — o título mais popular/o título usado pelos cineastas em materiais promocionais no momento do lançamento original
- Title (string) — título original, no idioma original
- isAdult (booleano) — 0: título não adulto; 1: título adulto
- startYear (YYYY) — representa o ano de lançamento de um título. No caso de séries de TV, é o ano de início da série
- endYear (YYYY) — Ano final da série de TV. ‘\N’ para todos os outros tipos de títulos
- runtimeMinutes — tempo de execução principal do título, em minutos
- gêneros (array de strings) — inclui até três gêneros associados ao título
Diferentemente do script anterior, iremos realizar algumas transformações nos dados. Em se tratando de um arquivo TSV, os genres de um filme estão armazenados em uma única célula como uma string única, mas semanticamente, representam uma matriz. Esta propriedade precisará ser trabalhada de modo a ser transformada em um array de strings. Além disso, iremos converter em número os tipos de dados numéricos (startYear, endYear e runtimeMinutes).
Manipulando dados no DataFrame
Para realizar a transformação da imagem acima, foi feito tratamento nas colunas startYear, endYear, runtimeMinutes e genres.
Dentro do bloco try foi usado o método .to_numeric()
do pandas, nas colunas nas quais o tipo do dado é um número. Esse método irá converter o argumento para um tipo numérico. Usamos o parâmetro errors='coerce'
de modo a tratar os valores não numéricos, que serão substituídos por NaN. Deste modo, ao invés de levantar um erro, irá aplicar NaN nos valores.
Já no caso da coluna genres, onde temos uma string contendo os gêneros, foi usado um string acessor .str
do pandas que permite aplicar funções de string nos elementos da coluna. Junto ao acessor foi aplicado o método .split
, que divide cada string na coluna em uma lista de strings, usando a vírgula como separador.
Do mesmo modo que o script anterior, este script possui as funções read_tsv
e main
próprias, além de file_path
e collection_name
próprio. Ao rodar este segundo script, obtivemos não somente 1.5M registros, mas sim 10.5M de registros, o que nos indica que muitos títulos não possuem avaliação dentro da base de dados do IMDb.
Ufaaa… bastante coisa!! Mas estamos mais fortes ao final rs!
Se você chegou até aqui, curte, comenta, me conta que tipo de conteúdo te desperta interesse no código, e o que gostaria de ver por aqui! Também aceito aquela diquinha de filme!! adorooo hehehe!!
Link da branch do projeto: aqui