· Tutoriales  · 5 min read

Web Scrapping con Elixir

❓ ¿Qué es el web scrapping?

El web scrapping nos permite convertir un documento o estructura HTML en datos utilizables en nuestros programas.

En este pequeño proyecto, obtendremos información sobre las tasas de cambio de divisas diarias de una tabla actualizada por el banco de Europa. Enlace

Esta tabla contiene información sobre símbolos, nombres de divisas y la tasa de cambio basada en el Euro.

Usaremos 2 bibliotecas para nuestro programa en elixir

  • HTTPoison (para hacer solicitudes HTTP)
  • Floki (para transformar HTML en datos utilizables)

Necesitamos crear un proyecto de Elixir con Mix, en este caso uso “Curry” como nombre para la solución.

mix new curry && cd curry

En la carpeta, necesitamos agregar las dependencias en el archivo llamado mix.exs

defp deps do
    [
        {:httpoison, "~> 1.8"},
        {:floki, "~> 0.30.1"}
    ]
 end

Si usas VS Code como editor junto con la extensión Elixir LS, estas dependencias deberían instalarse automáticamente después de guardar el archivo. De lo contrario, tienes que ejecutar el siguiente comando en la terminal dentro del directorio del proyecto para obtener las dependencias.

mix deps.get

En el archivo curry.ex (dependiendo del nombre de tu proyecto) dentro del directorio lib, debemos crear una función dentro del módulo Curry (o el nombre que elijas)

@url "https://www.ecb.europa.eu/stats/policy_and_exchange_rates/euro_reference_exchange_rates/html/index.en.html"

def get_currencies do
    case HTTPoison.get(@url) do
      {:ok, response} ->
        {:ok, html} = Floki.parse_document(response.body)

        content =
          html
          |> Floki.find("td")
          |> Floki.find("a")
          |> Floki.text(sep: "|")
          |> String.split("|")
          |> Enum.chunk_every(3)
          |> Enum.map(fn [a, b, c] -> %{symbol: a, name: b, rate: c} end)

        {:ok, content}
      {:error, error} ->
        IO.inspect error.reason
    end
  end

Ahora expliquemos qué está sucediendo paso a paso

🔗 Declarar la URL

Asignar la URL de la cual necesitamos obtener información a la constante URL

@url "https://www.ecb.europa.eu/stats/policy_and_exchange_rates/euro_reference_exchange_rates/html/index.en.html"

🌐 Usar la respuesta en un case

Crear una estructura case para gestionar patrones de respuestas devueltas por la función get del módulo HTTPoison.

case HTTPoison.get(@url) do
...
end

🌚 Declarar el patrón necesario como opción

Esta función HTTPoison.get(url) devuelve una tupla con un átomo y un mapa, en este caso solo necesitamos el cuerpo, y podemos obtener solo esto de toda la respuesta usando pattern matching

Estas opciones nos permiten guardar todo el cuerpo de la respuesta en una variable body solo si la solicitud fue exitosa

{:ok, response} ->

🌝 Transformar los datos del cuerpo en una estructura de Floki

Ahora la estructura HTML del cuerpo está guardada en la variable body, y ahora necesitamos analizar estos datos con la función parse_document del módulo Floki.

Nuevamente, necesitamos usar pattern matching para guardar nuestros datos transformados en la variable html, que convierte el documento HTML en una serie de listas y tuplas

{:ok, html} = Floki.parse_document(response.body)

🔮 ¡Ahora, la magia!

Con estos datos, ahora podemos comenzar a filtrar y transformar los datos con otras funciones de Floki.

content =
    html
    |> Floki.find("td") # Buscar todos los elementos td en los datos html
    |> Floki.find("a") # Buscar todos los elementos a en todos los elementos td encontrados previamente
    |> Floki.text(sep: "|") # Obtener solo el texto en los elementos a y agregar el carácter | como separador entre todas las ocurrencias.
    |> String.split("|") # Transformar todo el texto en una lista usando y eliminando el carácter separador de la función anterior.
    |> Enum.chunk_every(3) # Nuestras filas contienen 3 campos de datos (símbolo, nombre, tasa) así que dividir la lista en sublistas de 3 elementos
    |> Enum.map(fn [a, b, c] -> %{symbol: a, name: b, rate: c} end)  # Transformar la lista de listas en una lista de mapas.

{:ok, content} # Devolver una tupla con el átomo :ok y la lista de mapas

📄 Ejemplo de respuesta exitosa

{:ok,
 [
   %{name: "US dollar", rate: "1.2081", symbol: "USD"},
   %{name: "Japanese yen", rate: "132.37", symbol: "JPY"},
   %{name: "Bulgarian lev", rate: "1.9558", symbol: "BGN"},
   %{name: "Czech koruna", rate: "25.575", symbol: "CZK"},
   %{name: "Danish krone", rate: "7.4365", symbol: "DKK"},
   %{name: "Pound sterling", rate: "0.86063", symbol: "GBP"},
   %{name: "Hungarian forint", rate: "357.13", symbol: "HUF"},
   %{name: "Polish zloty", rate: "4.5467", symbol: "PLN"},
   %{name: "Romanian leu", rate: "4.9273", symbol: "RON"},
   %{name: "Swedish krona", rate: "10.1720", symbol: "SEK"},
   %{name: "Swiss franc", rate: "1.0960", symbol: "CHF"},
   %{name: "Icelandic krona", rate: "150.50", symbol: "ISK"},
   %{name: "Norwegian krone", rate: "10.1270", symbol: "NOK"},
   %{name: "Croatian kuna", rate: "7.5275", symbol: "HRK"},
   %{name: "Russian rouble", rate: "89.7244", symbol: "RUB"},
   %{name: "Turkish lira", rate: "10.2653", symbol: "TRY"},
   %{name: "Australian dollar", rate: "1.5673", symbol: "AUD"},
   %{name: "Brazilian real", rate: "6.4027", symbol: "BRL"},
   %{name: "Canadian dollar", rate: "1.4665", symbol: "CAD"},
   %{name: "Chinese yuan renminbi", rate: "7.7969", symbol: "CNY"},
   %{name: "Hong Kong dollar", rate: "9.3841", symbol: "HKD"},
   %{name: "Indonesian rupiah", rate: "17317.81", symbol: "IDR"},
   %{name: "Israeli shekel", rate: "3.9774", symbol: "ILS"},
   %{name: "Indian rupee", rate: "88.8595", symbol: "INR"},
   %{name: "South Korean won", rate: "1368.03", symbol: "KRW"},
   %{name: "Mexican peso", rate: "24.3353", symbol: "MXN"},
   %{name: "Malaysian ringgit", rate: "4.9835", symbol: "MYR"},
   %{name: "New Zealand dollar", rate: "1.6885", symbol: "NZD"},
   %{name: "Philippine peso", rate: "57.903", symbol: "PHP"},
   %{name: "Singapore dollar", rate: "1.6120", symbol: "SGD"},
   %{name: "Thai baht", rate: "37.874", symbol: "THB"},
   %{name: "South African rand", rate: "17.1055", symbol: "ZAR"}
 ]}

Ahora con esto, podrás usar estos datos para interpolar la tasa de cambio basada en cualquiera de estas divisas y no solo el Euro. O obtener datos específicos, guardarlos en una bd, etc. Pero haremos esto en otra publicación.

Si tienes alguna pregunta o sugerencia estaré feliz de leerlas, puedes enviarme un correo a [email protected]

Volver al Blog