Tulio Paschoalin Leao

Minerando dados de distribuição por sexo no Skoob

· Tulio Paschoalin Leao · 6 min

Há cerca de um mês publiquei um artigo de análise de dados com a demografia de todos os livros que já li e hoje vou mergulhar em como isso foi feito!

Procurando por uma API existente

Quando percebi que cada página do Skoob continha a distribuição dos leitores por sexo, imediatamente soube que tinha de minerá-las para ver as correlações com o meu histórico de leitura. O primeiro e óbvio passo seria ver se o Skoob possuía uma API oficial pública e documentada, mas não tinha 1. Minha segunda melhor chance seria encontrar alguém que tivesse escrito uma API não oficial para usar, foi quando encontrei a API Skoob do Fernando Arruda. A documentação do projeto descreve que ela consegue reaver as estantes dos usuários e as informações de cada livro, então pensei: “vai ser moleza!”.

Estudando o objeto Book que a API retorna, percebi que ele continha muitas informações acerca do livro, mas não o que eu queria (a distribuição de leitores por sexo), então tive de ir mais fundo para ver como a informação estava sendo consultada. Não sou uma pessoa do TypeScript, então tive a sorte de ver que havia um tópico aberto questionando a necessidade de a API utilizar cookies. Ali descobri que havia sim algum tipo de API do Skoob que o projeto estava aproveitando, mas o Fernando já tinha mapeado tudo que ela retornava em seus objetos, então essa não seria a minha saída.

Escrevendo meu próprio script com Python Requests

Meio frustrado por não ter um código pronto para usar e meio empolgado de ter de escrever o meu próprio, rapidamente criei um arquivo Python e pensei: “hora de usar meus parcos conhecimentos em Python Requests!”. Para os desavisados, é um pacote muito poderoso que te permite fazer requisições tipo get e post a uma página. É bem versátil e estava confiante que conseguiria navegar nas páginas do Skoob sem pestanejar, usando algo como:

1import requests
2argumentos = {'email': email, 'senha': senha}
3sessao = requests.Session()
4pedido_sessao = sessao.post("https://www.skoob.com.br/login/", data=argumentos)

Executei o script e o fiz funcionar2 🥳, agora era apenas uma questão de navegar até as estantes para coletar as páginas dos livros e seus dados, mas dali em diante não consegui mais progredir 🫠. Comecei a fuçar no Chrome DevTools para ver o código HTML da página e vi que estava repleto de blocos do tipo ng, que são usados pelo Angular para construir uma página dinâmica3. Isso significava que meu código nunca seria capaz de reaver as informações completas da página, já que ele dependia do fato de, uma vez feita a requisição, a página ser completamente retornada. Era assim que o script estava quando o abandonei.

Imagem gerada por IA com a descrição “a bearded man with glasses scratching his head looking at a computer monitor with a piece of code he wrote. He is wearing a t-shirt and a black watch on the left wrist” usando o Hotpot AI Art Generator

Escrevendo meu próprio script com Python Selenium

Comecei a pesquisar para ver se o Requests ainda seria uma opção viável para essa tarefa e todos os resultados que eu encontravam diziam “use Selenium”. Empregá-lo não é tão diferente do que é feito com o Requests, no sentido de que você ainda está fazendo diversas chamadas do tipo get, mas há três diferenças importantes:

  1. Ele tem funcionalidades nativas que te permitem esperar que um elemento apareça na página antes de prosseguir.
  2. Toda vez que você busca por exemplos na internet, seu primeiro resultado sempre retornará um exemplo em Java, e levei um tempo e várias exceções no Python até entender isso 🫣.
  3. Ele roda em primeiro plano: você vê o navegador aparecer e navegar por conta própria, como se você tivesse gravado uma série de passos para serem executados automaticamente, o que é recompensador depois de acabar de programar, apesar de mais lento4.

O primeiro ponto foi fundamental, já que agora eu poderia procurar por partes do HTML que só apareciam depois que a página acabasse de carregar e usá-las como pontos de parada antes de salvar as informações. Eis alguns exemplos::

1# Esperar até ser redirecionado para a página inicial do usuário após fazer login
2WebDriverWait(driver, 5).until(EC.presence_of_element_located((By.ID, "livro-perfil-status02")))
3# Esperar até que a primeira capa de livro apareça, significando que a paginação terminou
4WebDriverWait(driver, 5).until(EC.presence_of_element_located((By.CSS_SELECTOR, ".livro-capa")))

Note que ele permite a localização de diferentes elementos HTML por suas diferentes características, o que foi muito importante para mim, já que nem sempre poderia confiar que um elemento teria um ID. Essa mesma técnica pôde ser usada para achar informações dentro da página, uma vez que ela carregasse, em vez de ter que percorrê-la toda na unha usando expressões regulares ou parecido:

1# Construir lista de páginas de livros
2links_livros = driver.find_elements(By.CSS_SELECTOR, ".livro-capa")
3paginas_livros_lidos.extend([elem.find_element(By.TAG_NAME, "a").get_attribute('href') for elem in links_livros])
4# Encontrar botão de próxima página para clicar
5botao_proxima_pagina = driver.find_element(By.XPATH, "//*[contains(text(), '›')]")

Com isso foi apenas questão de rodar o script e esperar que a mágica acontecesse 🪄! Havia muitos trechos que poderiam ser melhorados: lugares que não usei uma rotina apropriada de espera e só fiz o script “dormir”, alguns identificadores fixados, mas eu não queria que fosse um script completamente genérico, só queria ser capaz de baixar os dados.

Depois de minerar tudo, comecei a brincar com a Matplotlib no mesmo script para desenhar o histograma e a tabela em markdown que utilizei no artigo. Para o histograma provavelmente seria mais rápido se eu utilizasse o Excel ou Google Sheets, mas gosto de passar tempo lutando aprendendo sobre a matplotlib. Nesse ponto, já que não queria buscar os dados repetidamente enquanto ajustava os eixos, cores e etc do histograma, coloquei alguns caches em JSON JSON no script, para que pudesse pular a parte lenta de executar o Selenium5.

O código final não é bonito, mas funcionou e me deu muitos conhecimentos interessantes e uma tarde de programação divertida. Estou ansioso pela próxima oportunidade de usar Selenium e estou contente que ele agora faz parte do meu cinto de utilidades.


  1. Esse item do FAQ não tem data. De acordo com a Wayback Machine do Internet Archive a página costumava dizer que “existe uma API, mas não está pública ainda” em janeiro de 2016, e em algum momento entre maio de 2022 e dezembro de 2023 ela mudou para “Não temos, mas estamos avaliando a possibilidade de ter uma” 🤔. ↩︎

  2. Não imediatamente, sabe, senão não seria tão empolgante depois. ↩︎

  3. Por sorte eu tinha tido um contato breve com Angular quando fizemos o projeto do Currículo para Elas durante a pandemia de COVID-19, o que me ajudou a entender o que era rapidamente. ↩︎

  4. Ainda tem o bônus de receber um olhar indagador de sua esposa enquanto você olha para a tela com um navegador que se mexe sozinho🤣. ↩︎

  5. A melhor solução provavelmente teria sido fazer a mineração e os gráficos em scripts, mas fui preguiçoso. ↩︎

#python

Responda a este post por email ↪