Elixir e o web scraping
Esse artigo é o primeiro de alguns onde quero mostrar algumas técnicas de web scraping em diversas linguagens. Já virou mania criar ferramentas desse tipo para experimentar uma nova tecnologia/linguagem e quando comecei com elixir não foi diferente hehehe!
Agenda
O Go-to do webscraping
Para quem está sendo introduzido ao assunto, o conceito em grandes linhas é basicamente fazer requisições para uma ou mais urls e trabalhar em cima do resultado traduzindo o conteúdo obtido para estruturas da linguagem, dessa forma fica fácil a manipulação de várias formas, permitindo envio das informações para outro serviço, armazenamento local entra outras coisas
Configurando o projeto
Nossa “stack” do projeto:
- Elixir
- Bibliotecas
- Tesla -> Client http
- Floki -> Parser html
Caso não tenha elixir instalado: Install elixir
Criação via mix
Via mix criamos a estrutura inicial do projeto que nos permite com facilidade: instalar libs, fazer testes, criar executáveis, entre outras coisas
1 | $ mix new erlscrap |
Saída:
1 | * creating README.md |
Instalação libs
tesla
Tesla is an HTTP client loosely based on Faraday. It embraces the concept of middleware when processing the request/response cycle
A lib tesla é muito simples de usar e é muito extensível. Excelente para requisições simmples ou até mesmo clientes complexos com middlewares
floki
Floki is a simple HTML parser that enables search for nodes using CSS selectors.
Desenvolvida pelo nosso querido Philipe sampaio a biblioteca floki é um parser html simples e perfomático que permite a manipulação de elementos html via seletores css o que significa que fica muito simples obter dados dentro de páginas
Inicialmente iremos editar a função deps
dentro do arquivo mix.exs
:
1 | defp deps do |
Dessa forma após executar o comando: mix deps.get
as depêndencias serão instaladas.
Features
Nossa ferramenta terá 4 funcionalidades básicas que são:
Parse argumentos cli
Antes de parsear os argumentos vindo da linha de comando iremos configurar nossa aplicação para que seja possível geramos um binário atráves do escript que pode rodar em qualquer sistema que tenha Erlang instalado (Existem outras formas de distribuição de binário que quero explorar em outros posts).
- Inicialmente iremos alterar a função
project
do nossomix.exs
:
1 | def project do |
- após isso implementamos a nova função
escript/0
no mesmo arquivo:
1 | defp escript do |
Feito isso podemos criar nosso módulo principal: lib/cli.ex
. Como já temos nosso ponto de entrada definido, podemos começar com a implementação da função main/1
que recebe os argumentos passados por cli:
1 | defmodule Elscrap.Cli do |
Para fins de testes agora podemos criar um binário e testar se os argumentos estão sendo passados corretamente:
1 | $ mix escript.build |
Esses são os argumentos que queremos ter ao final da nossa aplicação. Quando executamos podemos ver a lista que é gerada logo abaixo.
Para tirar proveito desses argumentos podemos utilizar a função OptionParser.parse/2
que nos da diversas opções de entrada. Muito similar ao que está na documentação do escript iremos definir nossa flag --extract-links
como um booleano para facilitar as operações que temos em mente
Dito isso nossa função main/1
muda e implementamos a função parse_args/1
:
1 | def main (args \\ []) do |
:warning: offtopic
Nesse trecho temos algumas expressões que se você não tem familiaridade com elixir pode estranhar… (Existe muito artigo sobre mas irei escrever algo sobre em breve) a primeira coisa em evidência é o operador pipe (|>
) ele simplesmente cria um fluxo de execução entre funções passando o resultado de uma função como argumento para a próxima chamada, podemos criar algo como:
1 | [1, 2, 3] |
Fazendo com que o resultado de uma função seja passado para outra função (é sempre bom lembrar que em elixir todas as funções retornam sua última expressão) então acaba sendo algo natural, para explorar mais: Operador pipe - Elixir school
Outra coisa que fica em evidência é o uso de pattern matching
na expressão a seguir:
1 | {opts, _value, _} = ... |
Nesse caso utilizamos como uma “destruct expression” para facilitar o paralelo com outras linguagens mas é claro que o pattern matching é muito mais que isso e recomendo fortemente a doc caso ainda não conheça: Pattern matching - Elixir scrhool.
Request
Agora entra a parte da requisição para a url passada através do parâmetro url
.
A nossa função main/1
ganha uma chamada extra e entra em jogo as funções scrap/1
e request/1
:
1 | def main (args \\ []) do |
Fazer uma chamada com a lib Tesla
é de fato simples:
1 | {:ok, response} = Tesla.get(url) |
Logo em seguida retornamos o body da resposta esperamos uma resposta válida através do átomo :ok. Vamos utilizar a função IO.inspect/1
para garantir que o retorno realmente funciona:
1 | {:ok, response} = Tesla.get(url) |
Teste
1 | $ mix escript.build |
Podemos partir então para a extração de links
Extração de links
A função scrap
agora evolui:
1 | if opts[:extract_links] do |
Estamos expressando que iremos fazer a requisição via a função request/1
passaremos o resultado para extract_links/1
que retorna uma lista, logo após isso printamos nossa lista quebrando linha
Implementação da função extract_links/1
:
1 | defp extract_links(response_body) do |
Algumas coisas acontecem aqui:
1 | {:ok, document} = Floki.parse_document(response_body) |
1 | links = document # passagem da variavel obtida do parsing acima |
Atualmente nosso módulo se encontra da seguinte forma:
1 | defmodule Elscrap.Cli do |
Agora já podemos rebuildar nossa aplicação e voilá:
1 | $ mix escript.build |
Dessa forma obtemos todas as urls presentes na página do github.
Salvar dados
Da forma que a ferramenta se encontra é fácil salvar o resultado para um arquivo usando o comando: ./bin/elscrap .. >> resultado.txt
mas para explorar um pouco mais podemos implementar uma função simples que cria um arquivo output quando quisermos
Dentro da nossa função scrap/1
podemos adicionar um if
checando se o parâmetro --save
foi informado:
1 | if opts[:extract_links] do |
E agora a implementaçã da função save_links/2
:
1 | defp save_links(url_id, links) do |
Criamos a pasta, rebuildamos a ferramenta e ao rodar o script agora com o novo parâmetro:
1 | ./bin/elscrap --extract-links --url "https://github.com" --save |
Conclusão
Assim como outras linguagens é muito simples criar uma ferramenta de web scraping em elixir, temos a vantagem também de implementar funcionalidades assíncronas e perfomáticas de forma eficaz e simples além da expressividade da linguagem. É isso…
O repo da ferramenta está disponível no github: Elscrap