Voltar ao blogue
Guias
Robert SfichiLast updated on Apr 22, 20268 min read

O guia definitivo sobre como começar a fazer web scraping com Elixir

O guia definitivo sobre como começar a fazer web scraping com Elixir

Introdução

Web scraping é o processo de extrair dados de sites publicamente disponíveis, como fóruns, redes sociais, sites de notícias, plataformas de comércio eletrónico, etc. Para lhe dar uma ideia do que irá construir hoje, este artigo descreve a criação de um web scraper em Elixir.

Se a definição de web scraping ainda não lhe é clara, consideremos o ato de guardar uma imagem de um site como web scraping manual. Se quiser guardar todas as imagens de um site manualmente, dependendo da complexidade do site, isso pode demorar horas ou até dias. 

Pode automatizar este processo criando um web scraper.

Talvez esteja a perguntar-se quais são alguns casos de utilização de um web scraper. Aqui estão os mais comuns:

Acompanhamento de notícias

Pode extrair as últimas notícias do seu site de notícias financeiras favorito, executar um algoritmo de análise de sentimento e saber em que investir minutos antes de o mercado abrir e começar a movimentar-se

Informações das redes sociais

Pode extrair os comentários das suas páginas nas redes sociais e analisar o que os seus seguidores estão a discutir e o que pensam sobre o seu produto ou serviço.

Monitorização de preços

Se a sua paixão é colecionar consolas e videojogos, mas não quer gastar uma fortuna na mais recente PS5, pode criar um web scraper que recupere os anúncios do eBay e lhe envie uma notificação quando uma consola barata estiver no mercado.

Formação em Aprendizagem Automática

Se quiser criar uma aplicação móvel capaz de identificar a raça de um gato em qualquer fotografia, vai precisar de muitos dados de treino; em vez de guardar manualmente centenas de milhares de fotografias com gatos para treinar o modelo, pode usar um web scraper para o fazer automaticamente.

Vamos construir o nosso web scraper em Elixir, uma linguagem de programação baseada em Erlang, criada por José Valim, membro da equipa principal do Ruby on Rails. A linguagem de programação aproveita a simplicidade da sintaxe do Ruby e combina-a com a capacidade do Erlang de construir sistemas de baixa latência, distribuídos e tolerantes a falhas.

Requisitos

Antes de escrever a primeira linha de código, certifique-se de que tem o Elixir instalado no seu computador. Descarregue o instalador para o seu sistema operativo e siga as instruções da página de instalação

Durante a instalação, irá verificar que a linguagem de programação Erlang também é necessária. Tenha em mente que o Elixir é executado na VM do Erlang, pelo que necessita de ambas.

Introdução

Neste artigo, irá aprender a criar um web scraper em Elixir, extrair os produtos do eBay para listagens de PS5 e armazenar localmente os dados extraídos (nome, URL, preço).

Analisar o alvo

É hora de inspecionar os resultados da pesquisa na página do eBay e recolher alguns seletores.

Vá a ebay.com, introduza o termo PS5 no campo de pesquisa e clique no botão Pesquisar. Assim que a página de resultados da pesquisa for carregada, abra a ferramenta Inspecionar no seu navegador (clique com o botão direito em qualquer ponto da página e selecione Inspecionar).

Precisa de recolher os seguintes seletores:

  • Item da lista de produtos
  • URL do produto
  • Nome do produto
  • Preço do produto

Utilize a ferramenta de seleção de elementos e procure a lista de itens do produto (ul) e o item do produto (li):

Usando estes dois elementos, pode extrair as classes necessárias para o rastreador para a extração de dados:

  • .srp-results .s-item os elementos filhos do elemento da lista de produtos (ul)
  • .s-item__title span o título do produto
  • .s-item__link o link do produto
  • .s-item__price o preço do produto

Criação do projeto

Vamos criar um projeto Elixir usando o comando mix:

mix new elixir_spider --sup

O sinalizador --sup gera um esqueleto de aplicação OTP, incluindo uma árvore de supervisão, uma funcionalidade necessária para uma aplicação que gere múltiplos processos simultâneos, como um rastreador.

Criar a pasta temporária

Mude o diretório atual para a raiz do projeto:

cd elixir_spider

Crie o diretório temp:

mkdir temp

Usamos este diretório para armazenar os itens extraídos.

Adicionar as dependências

Após a criação do projeto, é necessário adicionar as duas dependências:

  • O Crawly é uma estrutura de aplicações para rastrear sites e extrair dados estruturados
  • O Floki é um analisador HTML que permite a pesquisa de nós utilizando seletores CSS

Abra o ficheiro mix.exs e adicione as dependências no bloco deps:

defp deps do
    [
        {:crawly, "~> 0.14.0"},
        {:floki, "~> 0.33.1"}
    ]
end

Recupere as dependências executando este comando:

mix deps.get

Crie a configuração

Crie o ficheiro config/config.exs e cole esta configuração nele:

import Config

config :crawly,
    closespider_timeout: 10,
    concurrent_requests_per_domain: 8,
    closespider_itemcount: 100,

    middlewares: [
            Crawly.Middlewares.DomainFilter,
            Crawly.Middlewares.UniqueRequest,
            {Crawly.Middlewares.UserAgent, user_agents: ["Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:105.0) Gecko/20100101 Firefox/105.0"]}
    ],
    pipelines: [
            {Crawly.Pipelines.Validate, fields: [:url, :title, :price]},
            {Crawly.Pipelines.DuplicatesFilter, item_id: :title},
            Crawly.Pipelines.JSONEncoder,
            {Crawly.Pipelines.WriteToFile, extension: "jl", folder: "./temp"}
    ]

Opções gerais

Vamos analisar cada propriedade e compreender o seu significado:

  • closespider_timeout: inteiro, o número máximo de segundos que o spider permanecerá aberto
  • concurrent_requests_per_domain: o número máximo de pedidos que serão efetuados para cada domínio rastreado
  • closespider_itemcount: o número máximo de itens que passam pelo pipeline de itens

Agente de utilizador

Ao definir o agente do utilizador, melhora os resultados do scraping ao imitar um navegador real. Os sites não gostam de scrapers e tentam bloquear quaisquer agentes do utilizador que não pareçam reais. Pode usar uma ferramenta como esta para obter o agente do utilizador do seu navegador.

A WebScrapingAPI alterna o agente do utilizador e o endereço IP a cada pedido e também implementa inúmeras evasões para evitar este tipo de situação. Os seus pedidos não serão bloqueados e a implementação de um mecanismo de repetição irá proporcionar-lhe resultados excelentes.

Pipelines

Os pipelines são comandos processados de cima para baixo e permitem a manipulação dos itens processados. Utilizamos os seguintes pipelines:

  • Validar campos (título, preço, URL): verifica se o item tem os campos extraídos definidos
  • Filtro de duplicados: verifica se existem itens duplicados por título
  • Codificador JSON: codifica as estruturas para um objeto JSON
  • Gravar em ficheiro: grava os itens na pasta ./temp

Criação do spider

Um rastreador web, ou spider, é um tipo de bot que percorre um site e extrai dados utilizando campos definidos pelo utilizador através de seletores CSS. Um rastreador pode extrair todos os links de uma página e utilizar alguns específicos (como links de paginação) para rastrear mais dados. 

É hora de definir a base para o rastreador: crie o ficheiro ebay_scraper.ex na pasta lib/elixir_spider e cole o seguinte código nele:

# lib/elixir_spider/ebay.ex
defmodule EbayScraper do
    use Crawly.Spider

    @impl Crawly.Spider
    def base_url(), do: ""

    @impl Crawly.Spider
    def init() do

    end

    @impl Crawly.Spider
    def parse_item(response) do

    end
 end

Este é apenas o esqueleto do ficheiro e não irá executar-se nem devolver quaisquer resultados. Vamos falar primeiro sobre cada função e, em seguida, preenchê-las uma a uma.

A função base_url() é chamada uma vez e devolve o URL base do site de destino que o rastreador irá rastrear; também é utilizada para filtrar links externos e impedir que o rastreador os siga. Não se pretende rastrear toda a Internet.

@impl Crawly.Spider
def base_url(), do: "https://www.ebay.com/"

A função init() é chamada uma vez e é utilizada para inicializar o estado padrão do rastreador; neste caso, a função devolve o start_url a partir do qual a recolha de dados terá início.

Substitua a sua função em branco por esta:

@impl Crawly.Spider
def init() do
[start_urls: ["https://www.ebay.com/sch/i.html?_nkw=ps5"]]
end

Toda a magia da extração de dados acontece na função parse_item(). Esta função é chamada para cada URL rastreada. Dentro desta função, usamos o analisador HTML Floki para extrair os campos de que precisamos: título, URL e preço.

A função ficará assim:

@impl Crawly.Spider
def parse_item(response) do
    # Parse response body to document
    {:ok, document} = Floki.parse_document(response.body)

    # Create item (for pages where items exists)
    items =
        document
        |> Floki.find(".srp-results .s-item")
        |> Enum.map(fn x ->
         %{
           title: Floki.find(x, ".s-item__title span") |> Floki.text(),
           price: Floki.find(x, ".s-item__price") |> Floki.text(),
           url: Floki.find(x, ".s-item__link") |> Floki.attribute("href") |> Floki.text(),
         }
    end)

    %{items: items}
end

Como deve ter notado, estamos a utilizar as classes que encontrámos na secção Introdução - Inspecionar o alvo para extrair os dados de que precisamos dos elementos DOM.

Executar o spider

É hora de testar o código e garantir que funciona. A partir do diretório raiz do projeto, execute este comando:

iex -S mix run -e "Crawly.Engine.start_spider(EbayScraper)"

Se estiver a utilizar o PowerShell, certifique-se de que substitui iex por iex.bat; caso contrário, receberá um erro devido ao parâmetro -S inexistente. Utilize este comando para o PowerShell:

iex.bat -S mix run -e "Crawly.Engine.start_spider(EbayScraper)"

Verificar os resultados

Abra a pasta ./temp e verifique o ficheiro .jl. Deverá ver um ficheiro de texto contendo uma lista de objetos JSON, um por linha. Cada objeto contém as informações de que precisávamos da lista de produtos do eBay: título, preço e URL.

Eis como o objeto do produto deve ficar:

{"url":"https://www.ebay.com/itm/204096893295?epid=19040936896&hash=item2f851f716f:g:3G8AAOSwNslhoSZW&amdata=enc%3AAQAHAAAA0Nq2ODU0vEdnTBtnKgiVKIcOMvqJDPem%2BrNHrG4nsY9c3Ny1bzsybI0zClPHX1w4URLWSfXWX%2FeKXpdgpOe%2BF8IO%2FCh77%2FycTnMxDQNr5JfvTQZTF4%2Fu450uJ3RC7c%2B9ze0JHQ%2BWrbWP4yvDJnsTTWmjSONi2Cw71QMP6BnpfHBkn2mNzJ7j3Y1%2FSTIqcZ%2F8akkVNhUT0SQN7%2FBD38ue9kiUNDw9YDTUI1PhY14VbXB6ZMWZkN4hCt6gCDCl5mM7ZRpfYiDaVjaWVCbxUIm3rIg%3D%7Ctkp%3ABFBMwpvFwvRg","title":"PS5 Sony PlayStation 5 Console Disc Version! US VERSION!","price":"$669.99"}

Melhorar o spider

Recuperámos todos os produtos da primeira página da lista de produtos, mas isso não é suficiente. É hora de implementar a paginação e deixar que o rastreador extraia todos os produtos disponíveis.

Vamos alterar a função parse_item() e adicionar um novo bloco que cria uma estrutura requests com o link de paginação seguinte. Adicione este código após o código dos itens:

# Extract the next page link and convert it to a request
requests =
  document
  |> Floki.find(".s-pagination a.pagination__next")
  |> Floki.attribute("href")
  |> Crawly.Utils.build_absolute_urls(response.request_url)
  |> Crawly.Utils.requests_from_urls()

Atualize a instrução return da função parse_item() para incluir também os próximos requests. A estrutura ficará assim:

%{
  :requests => requests,
  :items => items
}

Execute o rastreador novamente, mas desta vez prepare um café. O scraping de todas as páginas com listagens do PS5 levará alguns minutos.

Depois de o rastreador terminar o seu trabalho, verifique a pasta ./temp para ver os resultados extraídos. Extraí com sucesso do eBay as consolas PS5 e tem uma lista com os seus preços. Pode alargar este rastreador para extrair quaisquer outros produtos.

Conclusão

Neste artigo, aprendeu o que é um web scraper, quais são os casos de uso destes crawlers, como utilizar bibliotecas já criadas para configurar um scraper usando Elixir em poucos minutos e como executar e extrair os dados reais.

Se achou que isto deu muito trabalho, tenho de lhe dar uma notícia não muito boa: mal tocámos na superfície. Executar este scraper durante muito tempo irá causar-lhe mais problemas do que imagina.

O eBay irá detetar a sua atividade e marcá-la como suspeita; o crawler começará a receber captchas; terá de ampliar a funcionalidade do crawler para resolver os captchas

Os sistemas de deteção do eBay podem sinalizar o seu endereço IP e impedir o seu acesso ao site; terá de obter um conjunto de proxies e alternar os endereços IP a cada pedido. 

Já está a ficar com a cabeça a andar à roda? Vamos falar de mais um problema: o agente de utilizador. Precisa de criar uma grande base de dados com agentes de utilizador e alternar esse valor a cada pedido. Os sistemas de deteção bloqueiam os scrapers com base no endereço IP e no agente de utilizador.

Se quiser concentrar-se mais no lado comercial e investir o seu tempo na extração de dados em vez de resolver os problemas de deteção, utilizar um scraper como serviço é a melhor escolha. Uma solução como a WebScrapingAPI resolve todos os problemas apresentados acima e muitos mais. 

Sobre o autor
Robert Sfichi, Desenvolvedor Full-Stack @ WebScrapingAPI
Robert SfichiDesenvolvedor Full-Stack

Robert Sfichi é membro da equipa da WebScrapingAPI, contribuindo para o produto e ajudando a criar soluções fiáveis que apoiam a plataforma e os seus utilizadores.

Comece a construir

Pronto para expandir a sua recolha de dados?

Junte-se a mais de 2.000 empresas que utilizam a WebScrapingAPI para extrair dados da Web à escala empresarial, sem quaisquer custos de infraestrutura.