Volver al blog
Guías
Robert SfichiLast updated on Mar 31, 20268 min read

La guía definitiva para iniciarse en el web scraping con Elixir

La guía definitiva para iniciarse en el web scraping con Elixir

Introducción

El web scraping es el proceso de extraer datos de sitios web de acceso público, como foros, redes sociales, portales de noticias, plataformas de comercio electrónico, etc. Para que te hagas una idea de lo que vas a crear hoy, este artículo describe la creación de un web scraper en Elixir.

Si aún no tienes clara la definición de web scraping, pensemos en guardar una imagen de un sitio web como un ejemplo de web scraping manual. Si quisieras guardar todas las imágenes de un sitio web a mano, dependiendo de la complejidad del sitio, podría llevarte horas o incluso días. 

Puedes automatizar este proceso creando un rastreador web.

Quizás te preguntes cuáles son algunos casos de uso de un rastreador web. Estos son los más comunes:

Seguimiento de noticias

Puedes extraer las últimas noticias de tu sitio web de noticias financieras favorito, ejecutar un algoritmo de análisis de sentimiento y saber en qué invertir minutos antes de que el mercado abra y se mueva

Información de redes sociales

Puedes extraer los comentarios de tus páginas en redes sociales y analizar de qué hablan tus seguidores y qué opinan sobre tu producto o servicio.

Seguimiento de precios

Si tu pasión es coleccionar consolas y videojuegos, pero no quieres gastarte una fortuna en la última PS5, puedes crear un rastreador web que recupere los anuncios de eBay y te envíe una notificación cuando haya una consola barata en el mercado.

Entrenamiento de aprendizaje automático

Si quieres crear una aplicación móvil capaz de identificar la raza de un gato en cualquier foto, necesitarás una gran cantidad de datos de entrenamiento; en lugar de guardar manualmente cientos de miles de fotos de gatos para entrenar el modelo, puedes usar un rastreador web para hacerlo automáticamente.

Crearemos nuestro rastreador web en Elixir, un lenguaje de programación basado en Erlang, creado por José Valim, miembro del equipo principal de Ruby on Rails. El lenguaje de programación toma prestada la simplicidad sintáctica de Ruby y la combina con la capacidad de Erlang para construir sistemas de baja latencia, distribuidos y tolerantes a fallos.

Requisitos

Antes de escribir la primera línea de código, asegúrate de tener Elixir instalado en tu ordenador. Descarga el instalador para tu sistema operativo y sigue las instrucciones de la página de instalación

Durante la instalación, verás que también se requiere el lenguaje de programación Erlang. Ten en cuenta que Elixir se ejecuta en la máquina virtual de Erlang, por lo que necesitas ambos.

Introducción

En este artículo, aprenderás a crear un rastreador web en Elixir, a rastrear los productos de eBay en busca de anuncios de PS5 y a almacenar localmente los datos extraídos (nombre, URL, precio).

Inspeccionar el objetivo

Es hora de inspeccionar los resultados de búsqueda en la página de eBay y recopilar algunos selectores.

Ve a ebay.com, introduce el término «PS5» en el campo de búsqueda y haz clic en el botón «Buscar». Una vez que se haya cargado la página de resultados de búsqueda, abre la herramienta «Inspeccionar» de tu navegador (haz clic con el botón derecho en cualquier lugar de la página y selecciona «Inspeccionar»).

Debes recopilar los siguientes selectores:

  • Elemento de la lista de productos
  • URL del producto
  • Nombre del producto
  • Precio del producto

Utiliza la herramienta de selección de elementos y busca la lista de elementos del producto (ul) y el elemento del producto (li):

Con estos dos elementos, puede extraer las clases necesarias para que el rastreador extraiga los datos:

  • .srp-results .s-item: los elementos secundarios del elemento de la lista de productos (ul)
  • .s-item__title span: el título del producto
  • .s-item__link: el enlace del producto
  • .s-item__price: el precio del producto

Creación del proyecto

Creemos un proyecto Elixir utilizando el comando mix:

mix new elixir_spider --sup

El indicador --sup genera un esqueleto de aplicación OTP que incluye un árbol de supervisión, una característica necesaria para una aplicación que gestiona múltiples procesos concurrentes, como un rastreador.

Creación de la carpeta temporal

Cambia el directorio actual a la raíz del proyecto:

cd elixir_spider

Crea el directorio temp:

mkdir temp

Usamos este directorio para almacenar los elementos extraídos.

Añadir las dependencias

Una vez creado el proyecto, debes añadir las dos dependencias:

  • Crawly es un marco de aplicaciones para rastrear sitios web y extraer datos estructurados
  • Floki es un analizador HTML que permite buscar nodos utilizando selectores CSS

Abre el archivo mix.exs y añade las dependencias en el bloque deps:

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

Obtén las dependencias ejecutando este comando:

mix deps.get

Crea la configuración

Crea el archivo config/config.exs y pega esta configuración en él:

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"}
    ]

Opciones generales

Repasemos cada propiedad y veamos qué significa:

  • closespider_timeout: entero, el número máximo de segundos que permanecerá abierto el rastreador
  • concurrent_requests_per_domain: el número máximo de solicitudes que se realizarán para cada dominio rastreado
  • closespider_itemcount: el número máximo de elementos que pasan por el canal de elementos

Agente de usuario

Al configurar el agente de usuario, mejoras los resultados del rastreo imitando a un navegador real. Los sitios web no ven con buenos ojos a los rastreadores e intentan bloquear cualquier agente de usuario que no parezca real. Puedes utilizar una herramienta como esta para obtener el agente de usuario de tu navegador.

WebScrapingAPI alterna el agente de usuario y la dirección IP con cada solicitud y también implementa innumerables evasiones para evitar este tipo de situaciones. Tus solicitudes no serán bloqueadas y la implementación de un mecanismo de reintento te proporcionará resultados excelentes.

Pipelines

Los pipelines son comandos procesados de arriba abajo y permiten la manipulación de los elementos procesados. Utilizamos los siguientes pipelines:

  • Validar campos (título, precio, URL): comprueba si el elemento tiene los campos extraídos configurados
  • Filtro de duplicados: comprueba si hay elementos duplicados por título
  • Codificador JSON: codifica las estructuras a un objeto JSON
  • Escribir en archivo: escribe los elementos en la carpeta ./temp

Creación de la araña

Un rastreador web, o araña, es un tipo de bot que recorre un sitio y extrae datos utilizando campos definidos por el usuario a través de selectores CSS. Un rastreador puede extraer todos los enlaces de una página y utilizar algunos específicos (como los enlaces de paginación) para rastrear más datos. 

Es hora de sentar las bases del rastreador: crea el archivo ebay_scraper.ex en la carpeta lib/elixir_spider y pega el siguiente código en él:

# 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

Esto es solo el esqueleto del archivo y no se ejecutará ni devolverá ningún resultado. Hablemos primero de cada función y luego las rellenaremos una por una.

La función base_url() se llama una vez y devuelve la URL base del sitio web de destino que el rastreador va a rastrear; también se utiliza para filtrar enlaces externos y evitar que el rastreador los siga. No quieres rastrear todo Internet.

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

La función init() se invoca una vez y se utiliza para inicializar el estado predeterminado del rastreador; en este caso, la función devuelve la start_url desde donde comenzará el rastreo.

Sustituye tu función en blanco por esta:

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

Toda la magia de la extracción de datos tiene lugar en parse_item(). Esta función se invoca para cada URL rastreada. Dentro de esta función, utilizamos el analizador HTML de Floki para extraer los campos que necesitamos: title, url y price.

La función tendrá este aspecto:

@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 habrás notado, estamos utilizando las clases que encontramos en la sección «Introducción: inspeccionar el objetivo» para extraer los datos que necesitamos de los elementos DOM.

Ejecutar el rastreador

Es hora de probar el código y asegurarnos de que funciona. Desde el directorio raíz del proyecto, ejecuta este comando:

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

Si utilizas PowerShell, asegúrate de sustituir iex por iex.bat; de lo contrario, obtendrás un error por el parámetro -S inexistente. Utiliza este comando para PowerShell:

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

Comprobación de los resultados

Abre la carpeta ./temp y comprueba el archivo .jl. Deberías ver un archivo de texto que contiene una lista de objetos JSON, uno por línea. Cada objeto contiene la información que necesitábamos de la lista de productos de eBay: título, precio y URL.

Así es como debería verse el objeto del producto:

{"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"}

Mejorar el rastreador

Hemos recuperado todos los productos de la primera página de la lista de productos, pero esto no es suficiente. Es hora de implementar la paginación y dejar que el rastreador extraiga todos los productos disponibles.

Modifiquemos la función parse_item() y añadamos un nuevo bloque que cree una estructura requests con el enlace de paginación siguiente. Añade este código después del código de los elementos:

# 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()

Actualiza la instrucción return de la función parse_item() para incluir también las siguientes solicitudes. La estructura tendrá este aspecto:

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

Ejecuta el rastreador de nuevo, pero esta vez prepárate un café. Rastrear todas las páginas de los anuncios de la PS5 llevará unos minutos.

Una vez que el rastreador haya terminado su trabajo, comprueba la carpeta ./temp para ver los resultados extraídos. Has extraído con éxito de eBay las consolas PS5 y tienes una lista con sus precios. Puedes ampliar este rastreador para extraer cualquier otro producto.

Conclusión

En este artículo has aprendido qué es un rastreador web, para qué sirven estos rastreadores, cómo utilizar bibliotecas ya creadas para configurar un rastreador con Elixir en cuestión de minutos, y cómo ejecutarlo y extraer los datos reales.

Si te ha parecido mucho trabajo, tengo que darte una noticia no tan buena: solo hemos arañado la superficie. Ejecutar este rastreador durante mucho tiempo te acarreará más problemas de los que puedas imaginar.

eBay detectará tu actividad y la marcará como sospechosa; el rastreador empezará a recibir captchas; tendrás que ampliar la funcionalidad del rastreador para resolverlos

Los sistemas de detección de eBay podrían marcar tu dirección IP y bloquearte el acceso al sitio web; tendrás que conseguir un conjunto de proxies y rotar las direcciones IP con cada solicitud. 

¿Ya te está dando vueltas la cabeza? Hablemos de otro problema más: el agente de usuario. Tienes que crear una gran base de datos con agentes de usuario y rotar ese valor con cada solicitud. Los sistemas de detección bloquean los rastreadores basándose en la dirección IP y el agente de usuario.

Si quieres centrarte más en el aspecto empresarial e invertir tu tiempo en la extracción de datos en lugar de resolver los problemas de detección, utilizar un scraper como servicio es una mejor opción. Una solución como WebScrapingAPI resuelve todos los problemas mencionados anteriormente y muchos más. 

Acerca del autor
Robert Sfichi, Desarrollador full-stack @ WebScrapingAPI
Robert SfichiDesarrollador full-stack

Robert Sfichi forma parte del equipo de WebScrapingAPI, donde contribuye al desarrollo del producto y ayuda a crear soluciones fiables que dan soporte a la plataforma y a sus usuarios.

Empieza a crear

¿Estás listo para ampliar tu recopilación de datos?

Únete a más de 2000 empresas que utilizan WebScrapingAPI para extraer datos de la web a escala empresarial sin ningún gasto de infraestructura.