<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Pedro Chaparro </title>
    <description>The latest articles on DEV Community by Pedro Chaparro  (@pedrochaparro).</description>
    <link>https://dev.to/pedrochaparro</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F875893%2F888e0605-ce77-4bf9-ad1d-5c7a41394ec3.png</url>
      <title>DEV Community: Pedro Chaparro </title>
      <link>https://dev.to/pedrochaparro</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pedrochaparro"/>
    <language>en</language>
    <item>
      <title>Buscador de vídeos con OpenSearch y React | Parte 3 | Limpieza y almacenamiento de los datos</title>
      <dc:creator>Pedro Chaparro </dc:creator>
      <pubDate>Fri, 18 Nov 2022 14:08:31 +0000</pubDate>
      <link>https://dev.to/pedrochaparro/buscador-de-videos-con-opensearch-y-react-parte-3-limpieza-y-almacenamiento-de-los-datos-5e21</link>
      <guid>https://dev.to/pedrochaparro/buscador-de-videos-con-opensearch-y-react-parte-3-limpieza-y-almacenamiento-de-los-datos-5e21</guid>
      <description>&lt;p&gt;&lt;strong&gt;⚠️ Nota:&lt;/strong&gt; La idea original de este proyecto surgió gracias al canal &lt;strong&gt;Soumil Shah&lt;/strong&gt;, por lo que doy crédito y recomiendo ver su serie de vídeos sobre Elastic Search &lt;a href="https://www.youtube.com/watch?v=obTK8dAaOkc"&gt;Aquí&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;El repositorio con el resultado final puede ser consultado en &lt;a href="https://github.com/PChaparro/search-engine"&gt;Github&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Índice
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Post 1 - Introducción: &lt;a href="https://dev.to/pedrochaparro/buscador-de-videos-con-opensearch-y-react-parte-1-introduccion-4gfd"&gt;Aquí&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Post 2 - Recolección de los datos: &lt;a href="https://dev.to/pedrochaparro/buscador-de-videos-con-opensearch-y-react-parte-2-recoleccion-de-datos-5b6l"&gt;Aquí&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Post 3 - Limpieza y almacenamiento de los datos: &lt;a href="https://dev.to/pedrochaparro/buscador-de-videos-con-opensearch-y-react-parte-3-limpieza-y-almacenamiento-de-los-datos-5e21"&gt;Aquí&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Post 4 - Desarrollo de la API (🚧 Trabajando en ello...) &lt;/li&gt;
&lt;li&gt;Post 5 - Desarrollo del cliente Web (🚧 Trabajando en ello...) &lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Requisitos
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Python (3.10.6 Opcional) y conocimientos básicos de Python. &lt;/li&gt;
&lt;li&gt;Jupyter Notebook&lt;/li&gt;
&lt;li&gt;Docker / Conocimientos sobre Docker y Docker Compose. &lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  "Instalación" de Open Search con Docker
&lt;/h2&gt;

&lt;p&gt;Luego de realizar una limpieza sencilla de los datos, estos serán almacenados en Open Search, por lo que comenzaremos utilizando Docker para inicializar el servicio de Open Search. &lt;/p&gt;

&lt;p&gt;Para esto, dentro de la carpeta &lt;code&gt;backend/&lt;/code&gt; crearemos el archivo &lt;code&gt;docker-compose.yml&lt;/code&gt; con el siguiente contenido (Leer los comentarios del código) &lt;a href="https://opensearch.org/docs/latest/opensearch/install/docker/"&gt;(Más información)&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: "3"

services:
  # Este es el servicio principal de Open Search, que nos
  # permitirá almacenar datos y realizar búsquedas. 
  se-opensearch:
    # Imagen de docker oficial de open search
    image: opensearchproject/opensearch:1.3.6
    container_name: se-opensearch 
    # Nombre del host dentro de la red de docker
    hostname: se-opensearch
    restart: on-failure
    ports: 
      - "9200:9200"
      # Performance analyzer, no lo usaremos, pero 
      # en la documentacion oficial lo utilizan
      - "9600:9600" 
    expose:
      - "9200"
      - "9600"
    environment: 
      - discovery.type=single-node
      # Deshabilitamos el plugin de seguridad para poder
      # conectarnos sin certificados SSL (no recomendado
      # para producción)
      - DISABLE_SECURITY_PLUGIN=true 
    volumes: 
      # Creamos un volúmen para que los datos no se pierdan
      # al momento de detener el contenedor de docker. 
      - opensearch-data-1:/usr/share/opensearch/data
    networks: 
      # Red interna de docker.
      - se-opensearch-net

  # Este servicio es opcional, solamente añade un dashboard
  # web para poder visualizar nuestros datos. 
  se-opensearch-dashboards:
    image: opensearchproject/opensearch-dashboards:1.3.6
    container_name: se-opensearch-dashboards
    hostname: se-opensearch-dashboards
    depends_on:
      - se-opensearch
    restart: always
    ports: 
      - "5601:5601"
    expose: 
      - "5601"
    environment: 
      - OPENSEARCH_HOSTS="http://se-opensearch:9200"
      - DISABLE_SECURITY_DASHBOARDS_PLUGIN=true
    networks: 
      - se-opensearch-net

# Creamos el volúmen 
volumes:
  opensearch-data-1:

# Creamos la red
networks:
  se-opensearch-net:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Una vez creado y guardado el archivo &lt;code&gt;docker-compose.yml&lt;/code&gt;, dentro de la carpeta &lt;code&gt;database/&lt;/code&gt; ejecutamos el comando &lt;code&gt;docker-compose up&lt;/code&gt; para iniciar el / los servicios:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Limpieza de los datos
&lt;/h2&gt;

&lt;p&gt;Para comenzar con este apartado, dentro de la carpeta &lt;code&gt;data/cleansing/&lt;/code&gt; crearemos un archivo de Jupyter Notebook, pero antes, &lt;strong&gt;de manera opcional&lt;/strong&gt;, crearemos un entorno virtual de Python &lt;a href="https://www.geeksforgeeks.org/python-virtual-environment/"&gt;(Más información)&lt;/a&gt;: &lt;/p&gt;

&lt;p&gt;Dentro de la carpeta &lt;code&gt;cleansing/&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virtualenv &lt;span class="nt"&gt;-p&lt;/span&gt; python3 environment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para activar el entorno virtual desde Linux:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;source &lt;/span&gt;environment/bin/activate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para activar el entorno virtual desde Windows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./environment/Scripts/activate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Independientemente de si hemos creado o no el entorno virtual, ahora realizaremos la instalación de Jupyter Notebook, para esto, ejecutamos en la consola:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;notebook
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Luego de instalarlo, lo ejecutamos con:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;jupyter notebook
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dentro de la interfaz web de Jupyter Notebook, creamos un nuevo "cuaderno": &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kloxGFmD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/px20hyjc7huclsbexus8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kloxGFmD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/px20hyjc7huclsbexus8.png" alt="Pasos para la creacion de un nuevo cuaderno en Jupyter Notebook" width="880" height="220"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Dentro del nuevo cuaderno, instalaremos los paquetes necesarios:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;!pip install pandas
# Paquete para conectarnos a open search
!pip install opensearch-py
# "Paquete" para convertir los textos a vectores
!pip install sentence-transformers

import pandas as pd
import json
import re # Expresiones regulares

# Estos dos paquetes los usaremos para generar un numero 
# aleatorio mas adelante
import time 
import math

# Coneccion a Open Search
from opensearchpy import OpenSearch
# Helpers para insertar todos los datos de manera rápida
from opensearchpy import helpers 

from sentence_transformers import SentenceTransformer
# Descargamos el modelo para transformar los textos 
# (Esto puede demorar un poco)
transformer_model = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Luego de instalar los paquetes, "leemos" el archivo &lt;code&gt;data.json&lt;/code&gt; generado con el &lt;em&gt;web scraping&lt;/em&gt; y lo convertimos a un &lt;code&gt;DataFrame&lt;/code&gt; de pandas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Leer el archivo
file = open('../scraping/data.json')
data = json.load(file)
# Convertir a un DataFrame de pandas
df = pd.DataFrame(data)
# Mostar el DataFrame
df
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Si todo fue correcto, deberíamos ver una tabla como la siguiente: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TCZjNN3o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u608t5bzwc9opxsivacs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TCZjNN3o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u608t5bzwc9opxsivacs.png" alt="Ejemplo de DataFrame" width="880" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;El primer paso que realizaremos para limpiar los datos es &lt;strong&gt;eliminar las entradas duplicadas&lt;/strong&gt;, para esto, ejecutamos el siguiente código:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;print('Length before dropping duplicates: {}'.format(df.shape))
# Eliminamos entradas duplicadas a partir de la url ya que
# esta deber ser única. 
df.drop_duplicates(subset=['url'], keep='first', inplace=True, ignore_index=False)
print('Length after dropping duplicates: {}'.format(df.shape))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Luego de eliminar los datos duplicados, podemos eliminar los caracteres indeseados, que en este caso serían: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Enlaces en las descripciones de los videos&lt;/li&gt;
&lt;li&gt;Saltos de línea. &lt;/li&gt;
&lt;li&gt;Caracteres no alfanuméricos (Incluídos los emojis) &lt;/li&gt;
&lt;li&gt;Espacios en blanco redundantes. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Lo anterior lo haremos mediante expresiones regulares ejecutando el siguiente código:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Realizamos una copia del dataframe para no afectar el 
# original en caso de que algo salga mal
df_bk = df.copy()

# Iteramos cada fila del DataFrame
for index in df_bk.index:
    title = df_bk['title'][index]
    description = df_bk['description'][index]
    tags = df_bk['tags'][index]
    new_tags = []

    # Remove urls
    title = re.sub(r'(http|https|www)\S+', '', title)
    description = re.sub(r'(http|https|www)\S+', '', description)

    # Remove \n texts
    title = title.replace('\n', ' ')
    description = description.replace('\n', ' ')

    # Remove non-alphanumeric chars
    # title = re.sub(r'[^a-zA-Z0-9\']', ' ', title)
    description = re.sub(r'[^a-zA-Z0-9]', ' ', description)

    # Iteramos cada tag ya que los tags son un array de strings
    for tag in tags:
        new_tag = re.sub(r'[^a-zA-Z0-9]', '', tag)
        new_tag = re.sub(' +', ' ', new_tag) 
        new_tags.append(new_tag.)

    # Remove redundant spaces
    title = re.sub(' +', ' ', title)
    description = re.sub(' +', ' ', description)

    # Set new value
    df_bk['title'][index] = title
    df_bk['description'][index] = description
    df_bk['tags'][index] = tags

# Mostramos el DataFrame resultante al final
df_bk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Si el paso anterior se ejecutó correctamente, deberíamos ver una tabla similar a la que se mostró unos cuantos pasos atrás.&lt;/p&gt;

&lt;h2&gt;
  
  
  Almacenamiento en Open Search
&lt;/h2&gt;

&lt;p&gt;Teniendo los datos, el paso restante es almacenarlos en Open Search, para lo cual, primero generamos una conexión:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Connection variables
host = 'localhost'
port = '9200'
# Usuario y contraseñas por defecto
auth = ('admin', 'admin')

# Connect
client = OpenSearch(
    timeout = 300,
    hosts = [{'host': host, 'port': port}],
    http_compress = True, 
    http_auth = auth,
    use_ssl = False,
    verify_cers = False,
)

client.ping()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Si ejecutamos la celda anterios, debería mostrarse un &lt;code&gt;True&lt;/code&gt;, caso contrario, &lt;strong&gt;comprobar que el docker-compose esté siendo ejecutado o revisar la documentación oficial:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--iPhXjiim--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dgp39a45basra32iq1gb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iPhXjiim--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dgp39a45basra32iq1gb.png" alt="Respuesta de conexión exitosa" width="745" height="332"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Antes de almacenar los datos en Open Search, crearemos una nueva columna para almacenar el vector que representa cada uno de los vídeos &lt;a href="https://www.delftstack.com/howto/python-pandas/how-to-create-an-empty-column-in-pandas-dataframe/"&gt;(Más información)&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Creamos una columna vacía
df_bk = df_bk.assign(vector="")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ahora, insertaremos en la nueva columna el vector del vídeo correspondiente &lt;a href="https://www.sbert.net/"&gt;(Más información)&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Iteramos cada fila / vídeo
for index in df_bk.index:
    title = df_bk['title'][index]
    description = df_bk['description'][index]
    tags = df_bk['tags'][index]

    # Creamos un solo string que contenga los textos importantes del vídeo
    bundle = title + ' ' + description

    for tag in tags:
        bundle += ' ' + tag

    # Transformarmos el string único a un vector con el 
    # modelo descargado previamente
    vector = transformer_model.encode(bundle)
    # Asignamos el vector a la columna vacía
    df_bk['vector'][index] = vector

# Mostramos el DataFrame final
df_bk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Si todo se ejecutó correctamente, deberíamos ver una tabla como la siguiente: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vOlk36vt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w5sodx491pvw7ppszaz4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vOlk36vt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w5sodx491pvw7ppszaz4.png" alt="DataFrame con el vector incluído" width="880" height="529"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Teniendo todos los datos del vídeo, podemos crear el índice de Open Search, &lt;strong&gt;el cual puede ser visto como el equivalente a una tabla en bases de datos relacionales, aunque no son exactamente lo mismo&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;index_name = 'videos'

index_body = {
    'settings': {
        # Es necesario configurar esto para utilizar el plugin KNN
        # EN la mayoría de campos se dejaron los valores por defecto
        'index': {
            'number_of_shards': 20, 
            'number_of_replicas': 1,
            'knn': {
                'algo_param': {
                    # Default 512: https://opensearch.org/docs/latest/search-plugins/knn/knn-index#method-definitions
                    # Higher values lead to more accurate but slower searches.
                    'ef_search': 256, 
                    # Using during graph creation
                    'ef_construction': 256, 
                    # Bidirectional links for each element
                    'm': 4 
                }
            }
        },
        'knn': 'true'
    },
    # A continuación se definen las "columnas" del índice, las
    # cuales son los campos de nuestros videos
    'mappings': {
        'properties': {
            'url': {
                'type': 'text'
            },
            'thumbnail': {
                'type': 'text'
            },
            'title': {
                'type': 'text'
            }, 
            'description': {
                'type': 'text'
            },
            'tags': {
                # Text type can be used as array
                'type': 'text'
            }, 
            'vector': {
                'type': 'knn_vector', 
                'dimension': 384
            }
        }
    }
}

# Si el índice ya existe, lo eliminamos (Esto es solo en caso
# de ejecutar el notebook nuevamente)
if(client.indices.exists(index=index_name)):
    client.indices.delete(index=index_name)

# Creamos el índice
reply = client.indices.create(index_name, index_body)
print(reply)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lo anterior debería mostrar una respuesta como la siguiente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{'acknowledged': True, 'shards_acknowledged': True, 'index': 'videos'}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finalmente, podemos insertar nuestros datos; para esto, podríamos iterar cada una de las filas e insertarlas individualmente, pero utilizaremos el método &lt;em&gt;bulk&lt;/em&gt; de Open Search que nos permite insertar grandes cantidades de datos de un manera rápida:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Crearemos un array ya que el método bulk recibe un elemento
# iterable
data = []

# Iteramos cada fila del DataFrame
for index in df_bk.index:
    # Tomamos todos los datos
    url = df_bk['url'][index]
    thumbnail = df_bk['thumbnail'][index]
    title = df_bk['title'][index]
    description = df_bk['description'][index]
    tags = df_bk['tags'][index]
    vector = df_bk['vector'][index]

    # Formamos un diccionario con los datos y lo insertamos al array
    # El campo _index es necesario para indicarle a Open Search
    # el índice al que debe agregar los datos
    data.append({'_index': index_name,
                 'url': url, 
                 'thumbnail': thumbnail, 
                 'title': title, 
                 'description': description, 
                 'tags': tags, 
                 'vector': vector})

# Insertamos los datos
reply = helpers.bulk(client, data, max_retries=5)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para comprobar que los datos se insertaron correctamente, podemos simular búsquedas dentro del &lt;em&gt;Notebook&lt;/em&gt; (&lt;a href="https://youtu.be/obTK8dAaOkc"&gt;Ver ídea original&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Recibir el input por teclado
query = input('Enter your query: ')
# Convertir el input a un vector (Para poder usar el plugin KNN en Open Search).
query_vector = transformer_model.encode(query)

open_search_query = {
    # Tomamos 24 resultados
    'size': 24, 
    # Campos que nos interesan de la respuesta
    '_source': ['url', 'thumbnail', 'title', 'tags'],
    # Filtro
    "query": {
        "bool": {
            'must': [
                # Usamos el plugin knn
                {'knn': {
                    "vector": {
                        # Le pasamos nuestro vector
                        "vector": query_vector,
                        # Tomamos los 24 "vecinos más cercanos"
                        "k": 24
                    }
                }}
            ]
        }
    }
}

response = client.search(
    # Buscamos en nuestro índice
    index = index_name, 
    # Limitamos a 24 resultados
    size = 24, 
    # Cuerpo de la búsqueda
    body = open_search_query,
    request_timeout = 64
)

# Simplificamos el resultado ya que por defecto tiene muchos
# otros campos
videos = [x['_source'] for x in response['hits']['hits']]

# Mostramos los resultados obtenidos
videos
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A continuación algunos ejemplos de búsquedas: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6o-6FNnb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8f30dpr7wfhbrxxezkfb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6o-6FNnb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8f30dpr7wfhbrxxezkfb.png" alt='Ejemplo de búsqueda "Learn how to create websites"' width="778" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uCNOkS5b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b225p3j9c78xwswgo0et.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uCNOkS5b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b225p3j9c78xwswgo0et.png" alt='Ejemplo de búsqueda "I don´t know which new video game i should buy"' width="880" height="302"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Final
&lt;/h2&gt;

&lt;p&gt;Con esto se concluye esta tercera parte en la que hicimos una limpieza sencilla de nuestros datos y los almacenamos en Open Search, te invito a continuar con la siguiente en la que desarrollaremos una API de Python para permitir a nuestros usuaruios realizar búsquedas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Referencias
&lt;/h2&gt;

&lt;p&gt;Para consultar las referencias dirigirse a cada uno de los enlaces que aparencen dentro o al final de los diferentes párrafos.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>jupyter</category>
      <category>database</category>
      <category>python</category>
    </item>
    <item>
      <title>Buscador de vídeos con OpenSearch y React | Parte 2 | Recolección de datos</title>
      <dc:creator>Pedro Chaparro </dc:creator>
      <pubDate>Fri, 18 Nov 2022 14:05:09 +0000</pubDate>
      <link>https://dev.to/pedrochaparro/buscador-de-videos-con-opensearch-y-react-parte-2-recoleccion-de-datos-5b6l</link>
      <guid>https://dev.to/pedrochaparro/buscador-de-videos-con-opensearch-y-react-parte-2-recoleccion-de-datos-5b6l</guid>
      <description>&lt;p&gt;&lt;strong&gt;⚠️ Nota:&lt;/strong&gt; La idea original de este proyecto surgió gracias al canal &lt;strong&gt;Soumil Shah&lt;/strong&gt;, por lo que doy crédito y recomiendo ver su serie de vídeos sobre Elastic Search &lt;a href="https://www.youtube.com/watch?v=obTK8dAaOkc"&gt;Aquí&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;El repositorio con el resultado final puede ser consultado en &lt;a href="https://github.com/PChaparro/search-engine"&gt;Github&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Índice
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Post 1 - Introducción: &lt;a href="https://dev.to/pedrochaparro/buscador-de-videos-con-opensearch-y-react-parte-1-introduccion-4gfd"&gt;Aquí&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Post 2 - Recolección de los datos: &lt;a href="https://dev.to/pedrochaparro/buscador-de-videos-con-opensearch-y-react-parte-2-recoleccion-de-datos-5b6l"&gt;Aquí&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Post 3 - Limpieza y almacenamiento de los datos: &lt;a href="https://dev.to/pedrochaparro/buscador-de-videos-con-opensearch-y-react-parte-3-limpieza-y-almacenamiento-de-los-datos-5e21"&gt;Aquí&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Post 4 - Desarrollo de la API (🚧 Trabajando en ello...) &lt;/li&gt;
&lt;li&gt;Post 5 - Desarrollo del cliente Web (🚧 Trabajando en ello...) &lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Requisitos:
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Go (1.19.2 Opcional) y conocimientos básicos de Go. &lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Creación de las carpetas y archivos
&lt;/h2&gt;

&lt;p&gt;Para el desarrollo de las diferentes partes del proyecto, crearemos 3 carpetas principales, &lt;code&gt;frontend&lt;/code&gt;, &lt;code&gt;backend&lt;/code&gt; y &lt;code&gt;data&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
├── frontned
├── backend
└── data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ahora, dentro de la carpeta de data, crearemos la carpeta &lt;code&gt;scraping&lt;/code&gt; y &lt;code&gt;cleansing&lt;/code&gt;, para la obtención y limpieza de los datos respectivamente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data/
├── scraping
├── cleansing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Por ahora solo crearemos un nuevo módulo de Go para obtener los datos, esto dentro de la carpeta &lt;code&gt;scraping/&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;data/scraping
go mod init github.com/username/reponame
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Si el anterior paso fue ejecutado correctamente, debería haber un archivo &lt;code&gt;go.mod&lt;/code&gt; dentro de la carpeta &lt;code&gt;scraping/&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data/
├── scraping
│   ├── go.mod
├── cleansing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Obtención de los datos
&lt;/h2&gt;

&lt;p&gt;En primer lugar, debemos descargar el paquete &lt;a href="https://pkg.go.dev/github.com/PChaparro/go-youtube-scraper"&gt;youtubescraper&lt;/a&gt;, para ello, estando dentro de la carpeta &lt;code&gt;scraping/&lt;/code&gt; ejecutamos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go get github.com/PChaparro/go-youtube-scraper
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Si no se mostró ningún error en la consola, dentro de la misma carpeta, creamos el archivo &lt;code&gt;main.go&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;touch &lt;/span&gt;main.go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dentro del nuevo archivo creado, podemos copiar el siguiente código para comprobar que la instalación haya sido correcta y el paquete esté funcionando:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package main

import (
    "fmt"

    // Importamos el paquete para hacer el scraping
    youtubescraper "github.com/PChaparro/go-youtube-scraper"
)

func main() {
    // Usamos el metodo GetVideosData con los siguientes argumentos
    // El primer argumento es una key de la api de youtube, en este caso no lo vamos a usar
    // El segundo argumento es el texto que queremos buscar en youtube
    // El tercer argumento es el numero de videos que queremos obtener (100)
    // El cuarto argumenteo es el limite de concurrencia para el funcionamiento del paquete
    // El ultimo argumento es si queremos usar la api de youtube en lugar de web scraping
    videos, err := youtubescraper.GetVideosData("", "Learn web development", 100, 64, false)

    if err != nil {
        fmt.Print("Error :(")
    }

    fmt.Println(len(videos.Videos))
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El argumento del límite de concurrencia hace referencia al número máximo de &lt;code&gt;go routines&lt;/code&gt; que el paquete podrá utilizar para obtener los datos de los vídeos. Lo anterior puede verse como &lt;strong&gt;el número de vídeos que el paquete procesará al mismo tiempo&lt;/strong&gt;. &lt;a href="https://www.geeksforgeeks.org/difference-between-concurrency-and-parallelism/"&gt;Más información&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Si todo sale bien, podemos ejecutar el archivo &lt;code&gt;main.go&lt;/code&gt; y al cabo de unos segundos (en mi caso 20 segundos aunque puede variar dependiendo de la conexión a internet) veremos un número en consola:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go run main.go
... Luego de unos segundos
100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El número mostrado hace referencia al &lt;strong&gt;número de datos obtenido por el paquete&lt;/strong&gt;, puede ser exactamente el mismo que el número de datos solicitado o puede ser un poco menor. &lt;/p&gt;

&lt;p&gt;Sabiendo que el paquete funcionó correctamente, es momento de obtener los más de 2000 datos para nuestro buscador, esta vez, utilizando una API key de Youtube para que el proceso sea mucho más rápido. &lt;/p&gt;

&lt;p&gt;Primero creamos un archivo &lt;code&gt;.env&lt;/code&gt; en dentro de la carpeta &lt;code&gt;scraping/&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;touch&lt;/span&gt; .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dentro del archivo creado, colocaremos la variable de entorno &lt;code&gt;KEY&lt;/code&gt; (aunque puede tener cualquier nombre) y su valor será nuestra &lt;em&gt;API key&lt;/em&gt; de Youtube. Si no tienes una &lt;em&gt;API key&lt;/em&gt;, te recomiendo ver &lt;a href="https://youtu.be/zVJKcbjE52w"&gt;este vídeo&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;KEY=your api key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Una vez creado el archivo &lt;code&gt;.env&lt;/code&gt; con la variable &lt;code&gt;KEY&lt;/code&gt; que almacena nuestra API Key, es momento de hacer algunos cambios en el código (Leer los comentarios):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package main

import (
    // Paquete para convertir los videos a bytes para guardarlos como json
    "encoding/json"
    "fmt"

    // Paquete para crear el archivo .json final
    "os"
    // Paquete para medir el tiempo que demora la ejecucion
    "time"

    // Paquete para obtener los datos
    youtubescraper "github.com/PChaparro/go-youtube-scraper"
    // Interfaces / tipos del paquete para obtener los datos
    ysi "github.com/PChaparro/go-youtube-scraper/interfaces"
    // Paquete para leer la variable de entorno
    "github.com/joho/godotenv"
)

func main() {
    // Cargar las variables de entorno
    err := godotenv.Load()

    if err != nil {
        fmt.Println("Error al cargar las variables de entorno.")
    }

    // Leer y verificar la variable de entorno
    key := os.Getenv("KEY")

    if key == "" {
        fmt.Println("La variable de entorno está vacía")
    } else {
        // Búsquedas que queremos realizar
        queue := []string{
            "Learn web development",
        }

        // Array para guardar los vídeos
        results := []ysi.Video{}

        // Iterar cada una de las búsquedas definidas anteriormente
        for _, query := range queue {
            // Tomar el tiempo de inicio
            start := time.Now()

            // Obtener los vídeos de la búsqueda actual
            videos, _ := youtubescraper.GetVideosData(key, query, 100, 64, true)
            // Agregar los vídeos de la búsqueda actual al array de resultados
            results = append(results, videos.Videos...)

            // Imrprimir el tiempo que tomó
            fmt.Printf("%s query took %v\n", query, time.Since(start))
        }

        // Crear los bytes que serán guardasos en formato json
        json, err := json.Marshal(results)

        if err != nil {
            fmt.Printf("Error al convertir a bytes")
        } else {
            // Si no hay ningún error, guardamos el resultado como un .json
            // El 0666 final son permisos de lectura y escritura para todos los usuarios
            os.WriteFile("data.json", json, 0666)
        }

    }

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Si no hay ningún error, luego de ejecutar el archivo veríamos un mensaje en la consola como el siguiente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go run main.go
... Luego de unos segundos
Learn web development query took 4.953656575s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El mensaje, como el caso anterior, nos indica que los datos se obtubieron de manera satisfactoria, y deberíamos poder encontrar un archivo &lt;code&gt;data.json&lt;/code&gt; creado dentro de la carpeta &lt;code&gt;scraping/&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data/
├── scraping
│   ├── .env
│   ├── go.mod
│   ├── go.sum
│   ├── main.go
│   ├── data.json
├── cleansing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dentro del archivo &lt;code&gt;data.json&lt;/code&gt; podremos ver un array de objetos como el siguiente: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Kjf8U-nU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/as3erm4omwwp407af3lz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Kjf8U-nU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/as3erm4omwwp407af3lz.png" alt="Image description" width="880" height="459"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Si lo anterior es cierto, felicidades 🎉! Haz completado el paso de recolección satisfactoriamente, el único paso restante es agregar más búsquedas al array &lt;code&gt;queue&lt;/code&gt;, como en el siguiente ejemplo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;queue := []string{
    "Learn web development",
    "Top new games", 
    "Best world carnivals",
        ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Luego de agregar más elementos (&lt;strong&gt;Ten en cuenta el límite de peticiones diarios de la API de Youtube, con 10 o 15 está más que bien&lt;/strong&gt;), puedes ejecutar de nuevo el archivo &lt;code&gt;main.go&lt;/code&gt; para obtener los nuevos resultados.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final
&lt;/h2&gt;

&lt;p&gt;Con esto se concluye esta segunda parte en la que recolectamos nuestros datos utilizando un paquete de &lt;em&gt;web scraping&lt;/em&gt;, te invito a continuar con la siguiente en la que limpiaremos nuestros datos y los almacenaremos en Open Search.&lt;/p&gt;

&lt;h2&gt;
  
  
  Referencias
&lt;/h2&gt;

&lt;p&gt;Para consultar las referencias dirigirse a cada uno de los enlaces que aparencen dentro o al final de los diferentes párrafos.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>go</category>
      <category>webscraping</category>
    </item>
    <item>
      <title>Buscador de vídeos con OpenSearch y React | Parte 1 | Introducción</title>
      <dc:creator>Pedro Chaparro </dc:creator>
      <pubDate>Fri, 18 Nov 2022 14:02:40 +0000</pubDate>
      <link>https://dev.to/pedrochaparro/buscador-de-videos-con-opensearch-y-react-parte-1-introduccion-4gfd</link>
      <guid>https://dev.to/pedrochaparro/buscador-de-videos-con-opensearch-y-react-parte-1-introduccion-4gfd</guid>
      <description>&lt;p&gt;&lt;strong&gt;⚠️ Nota:&lt;/strong&gt; La idea original de este proyecto surgió gracias al canal &lt;strong&gt;Soumil Shah&lt;/strong&gt;, por lo que doy crédito y recomiendo ver su serie de vídeos sobre Elastic Search &lt;a href="https://www.youtube.com/watch?v=obTK8dAaOkc"&gt;Aquí&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;El repositorio con el resultado final puede ser consultado en &lt;a href="https://github.com/PChaparro/search-engine"&gt;Github&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Índice
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Post 1 - Introducción: &lt;a href="https://dev.to/pedrochaparro/buscador-de-videos-con-opensearch-y-react-parte-1-introduccion-4gfd"&gt;Aquí&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Post 2 - Recolección de los datos: &lt;a href="https://dev.to/pedrochaparro/buscador-de-videos-con-opensearch-y-react-parte-2-recoleccion-de-datos-5b6l"&gt;Aquí&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Post 3 - Limpieza y almacenamiento de los datos: &lt;a href="https://dev.to/pedrochaparro/buscador-de-videos-con-opensearch-y-react-parte-3-limpieza-y-almacenamiento-de-los-datos-5e21"&gt;Aquí&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Post 4 - Desarrollo de la API (🚧 Trabajando en ello...) &lt;/li&gt;
&lt;li&gt;Post 5 - Desarrollo del cliente Web (🚧 Trabajando en ello...) &lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Introducción
&lt;/h2&gt;

&lt;p&gt;A través de los diferentes posts vamos a desarrollar un sitio web full-stack que nos permitirá &lt;strong&gt;buscar vídeos de una manera rápida y obtener resultados coherentes con el texto ingresado&lt;/strong&gt;. En la siguiente imagen se puede ver el resultado final del front-end funcional:  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Ob6h2E6b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n0dka5hungdy60ymukwd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Ob6h2E6b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n0dka5hungdy60ymukwd.png" alt="Resultado final" width="880" height="686"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Stack tecnológico
&lt;/h2&gt;

&lt;p&gt;A continuación se presenta el listado de los lenguajes de programación y herramientas que usaremos en los diferentes pots: &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;⚠️ Nota:&lt;/strong&gt; Las definiciones se presentan de manera sencilla, con el fin de facilitar el entendimiento del proyecto a realizar, para más información recomiendo ver los sitios web / documentación oficial de los lenguajes / herramientas.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Go / Golang&lt;/strong&gt;: Mediante este lenguaje de programación haremos uso del paquete &lt;a href="https://pkg.go.dev/github.com/PChaparro/go-youtube-scraper"&gt;youtubescraper&lt;/a&gt; para obtener la url, miniatura, título, descripción y tags de vídeos publicados en la plataforma Youtube.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Python&lt;/strong&gt;: Este lenguaje será utilizado para hacer una limpieza sencilla de los datos, insertar los datos en &lt;em&gt;open search&lt;/em&gt; y desarrollar los &lt;em&gt;endpoints&lt;/em&gt; de la API que será consultada por el front-end.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Open search&lt;/strong&gt;: Es el motor de búsquedas y análisis que nos permitirá &lt;strong&gt;almacenar los datos y realizar búsquedas semánticas&lt;/strong&gt;, lo cual se explicará más adelante. &lt;a href="https://opensearch.org/versions/opensearch-2-3-0.html"&gt;Más información&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Sentence transformers&lt;/strong&gt;: Este paquete de python nos permitirá &lt;strong&gt;transformar los vídeos y las búsquedas en vectores&lt;/strong&gt;, los cuales serán usados para encontrar vídeos similares semánticamente a las búsquedas realizadas por los usuarios. &lt;a href="https://pypi.org/project/sentence-transformers/"&gt;Más información&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;React:&lt;/strong&gt; Es la librería que utilizaremos para el desarrollo del &lt;em&gt;front-end&lt;/em&gt;, lo que nos permitirá dividir responsabilidades entre componentes.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  ¿Por qué vamos a utilizar Open Search y Sentence Transformers?
&lt;/h2&gt;

&lt;p&gt;La funcionalidad más importante del sitio web a desarrollar es la búsqueda de vídeos a partir de textos. Si bien, podríamos utilizar cualquier otra base de datos como Postgres o MongoDB para almacenar los datos y buscar el texto ingresado por el usuario en el título, descripción y tags de los diferentes vídeos para obtener los resultados, esto podría no arrojar resultados precisos en muchos escenarios, como en los siguientes dos ejemplos: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Un error de digitación:&lt;/strong&gt; En caso de hacer una búsqueda exacta del texto ingresado por el usuario, una frase con un error como "&lt;em&gt;Crate web sites&lt;/em&gt;" (Falta la &lt;strong&gt;e&lt;/strong&gt; en &lt;em&gt;Create&lt;/em&gt;) podría no obtener resultados a pesar de que en la base de datos existan cientos o miles de registros con la frase "&lt;em&gt;Create web sites&lt;/em&gt;".&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Una búsqueda por significado:&lt;/strong&gt; Un usuario podría realizar una búsqueda como "&lt;em&gt;I don´t know which new video game i should buy&lt;/em&gt;", claramente, podemos inferir que lo que desea encontrar son recomendaciones de videojuegos, pero una base de datos "convencional" podría no encontrar resultados para su búsqueda debido a que &lt;strong&gt;no hay textos similares en los títulos, descripciones o tags de ningún vídeo&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Como los dos ejemplos anteriores, existen muchos más, por ejemplo, las búsquedas en diferentes lenguajes. Como solución, la "libería" &lt;em&gt;sentence transformers&lt;/em&gt; ofrece diferentes modelos de inteligencia artificial entrenados con &lt;strong&gt;millones o billones&lt;/strong&gt; de datos para extraer los significados de textos a un vector de longitud fija y que, según el modelo escogido, pueden soportar &lt;strong&gt;más de 50 lenguajes diferentes&lt;/strong&gt;. &lt;a href="https://www.sbert.net/docs/pretrained_models.html"&gt;Más información&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A partir del vector generado por &lt;em&gt;sentence transformers&lt;/em&gt; se pueden aplicar algoritmos como el &lt;a href="https://en.wikipedia.org/wiki/Cosine_similarity"&gt;coseno de similitud&lt;/a&gt; o &lt;a href="https://www.ibm.com/topics/knn"&gt;K-NN&lt;/a&gt; que permiten encontrar vectores similares, y, por tanto, &lt;strong&gt;vídeos similares a la semántica / "significado" del texto ingresado&lt;/strong&gt;. Es justo aquí donde &lt;em&gt;Open Search&lt;/em&gt; ofrece una gran ventaja, ya que cuenta con un &lt;a href="https://opensearch.org/docs/latest/search-plugins/knn/index/"&gt;plugin del algoritmo K-NN &lt;/a&gt; que nos permitirá realizar búsquedas rápidas y acertadas de manera sencilla. &lt;/p&gt;

&lt;h2&gt;
  
  
  Final
&lt;/h2&gt;

&lt;p&gt;Con esto se concluye esta primer parte, que solamente sirve como contextualización, te invito a continuar con la siguiente.&lt;/p&gt;

&lt;h2&gt;
  
  
  Referencias
&lt;/h2&gt;

&lt;p&gt;Para consultar las referencias dirigirse a cada uno de los enlaces que aparencen dentro o al final de los diferentes párrafos.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>go</category>
      <category>python</category>
      <category>react</category>
    </item>
    <item>
      <title>Web Scraping y Concurrencia - GO</title>
      <dc:creator>Pedro Chaparro </dc:creator>
      <pubDate>Fri, 28 Oct 2022 19:50:15 +0000</pubDate>
      <link>https://dev.to/pedrochaparro/reducir-tiempo-de-web-scraping-con-concurrencia-go-2ca9</link>
      <guid>https://dev.to/pedrochaparro/reducir-tiempo-de-web-scraping-con-concurrencia-go-2ca9</guid>
      <description>&lt;h2&gt;
  
  
  Recursos
&lt;/h2&gt;

&lt;p&gt;Esta implementación fue realizada gracias al trabajo colaborativo. El código fuente y los aportes de cada persona se encuentran en &lt;a href="https://github.com/PedroChaparro/PI202202-alako-data/tree/_GoScraper/scraper-data-pedroandres/go"&gt;Github&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducción
&lt;/h2&gt;

&lt;p&gt;Uno de los métodos más conocidos para la obtención de información desde sitios públicos en internet es el &lt;strong&gt;web scraping&lt;/strong&gt;, que, a partir de diversos métodos como el uso de selectores css o expresiones regulares &lt;strong&gt;permite obtener los textos presentes en el HTML del sitio web.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;En este caso, el objetivo de la implementación del web scraping es la obtención de títulos, descripciones, etiquetas, enlaces y "miniaturas" de vídeos publicados en la plataforma YouTube &lt;strong&gt;con fines netamente de aprendizaje.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Inicialmente, se realizaron 3 implementaciones en los lenguajes de programación &lt;code&gt;Ruby&lt;/code&gt; (&lt;a href="https://github.com/PedroChaparro/PI202202-alako-data/tree/main/scraper-data-whayner"&gt;ver más información&lt;/a&gt;), &lt;code&gt;JavaScript&lt;/code&gt; (&lt;a href="https://github.com/PedroChaparro/PI202202-alako-data/tree/main/scraper-data-pedroandres"&gt;ver más información&lt;/a&gt;) y &lt;code&gt;Python&lt;/code&gt; (&lt;a href="https://github.com/PedroChaparro/PI202202-alako-data/tree/main/scraper-data-silvia"&gt;ver más información&lt;/a&gt;). A pesar de que las tres implementaciones requerían tiempos relativamente bajos para completar la recolección, se identificó una posible oportunidad de mejora utilizando el lenguaje &lt;strong&gt;&lt;code&gt;Go&lt;/code&gt;&lt;/strong&gt;, esto debido a su &lt;strong&gt;soporte para la concurrencia&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementación y resultados
&lt;/h2&gt;

&lt;p&gt;Para la obtención de los datos requeridos se escogieron diferentes búsquedas (Ejm. &lt;em&gt;How to create websites&lt;/em&gt;, &lt;em&gt;Colombian music&lt;/em&gt;) y se obtuvieron, al menos, 140 enlaces de los vídeos resultantes de cada búsqueda empleando el paquete &lt;a href="https://go-rod.github.io"&gt;&lt;code&gt;go-rod&lt;/code&gt;&lt;/a&gt; para inicializar un navegador sin interfaz gráfica (&lt;em&gt;headless&lt;/em&gt;) y &lt;strong&gt;realizar de manera automática el scroll hasta tener el número mínimo de vídeos y obtener los enlaces.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;El siguiente paso fue iterar los enlaces obtenidos y, con ayuda del paquete &lt;a href="https://github.com/remeh/sizedwaitgroup"&gt;&lt;code&gt;SizedWaitGroup&lt;/code&gt;&lt;/a&gt;, &lt;strong&gt;iniciar de manera concurrente la ejecución de funciones para obtener los datos de cada enlace.&lt;/strong&gt; A pesar de que &lt;code&gt;Go&lt;/code&gt; ofrece de manera estándar el paquete &lt;code&gt;sync&lt;/code&gt; con el que se pueden crear WaitGroups, &lt;strong&gt;se optó por el paquete &lt;code&gt;SizedWaitGroup&lt;/code&gt; para evitar el consumo excesivo de recursos&lt;/strong&gt; al limitar el número de &lt;code&gt;GoRoutines&lt;/code&gt; concurrentes. &lt;/p&gt;

&lt;p&gt;Al modificar el límite de &lt;code&gt;GoRoutines&lt;/code&gt; concurrentes, se obtuvieron los siguientes resultados: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vfBUt-cl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/22d80l6hyov9hxj511j2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vfBUt-cl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/22d80l6hyov9hxj511j2.png" alt="Tiempo de ejecución requerido según el número de GoRoutines concurrentes" width="600" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Como se observa en el gráfico, con tan solo pasar de una &lt;code&gt;GoRoutine&lt;/code&gt; concurrente (Ejecución secuencial) a dos, el tiempo de ejecución &lt;strong&gt;se reduce de 113.6039s a 61.4714s, es decir, un 45.8897% apróximadamente&lt;/strong&gt;, y al utilizar ocho &lt;code&gt;GoRoutines&lt;/code&gt;, &lt;strong&gt;se reduce a 28.7748s, lo cual es 74.6709% menos en comparación al tiempo inicial.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Para finalizar, así se ven en la consola las ejecuciones con un límite de 1 y 8 &lt;code&gt;GoRoutines&lt;/code&gt; respectivamente (Los primeros 20 segundos corresponden al tiempo para hacer el &lt;em&gt;scroll&lt;/em&gt; con el &lt;em&gt;&lt;code&gt;web-driver&lt;/code&gt;&lt;/em&gt;`):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hcPCJKbs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3j0d4ub6xxssjcnug1ut.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hcPCJKbs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3j0d4ub6xxssjcnug1ut.gif" alt="Ejecución con límite de 1 GoRoutine" width="880" height="495"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--umQ68jm_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rtp9c0cksxn3jrbz20rj.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--umQ68jm_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rtp9c0cksxn3jrbz20rj.gif" alt="Ejecución con límite de 8 GoRoutines" width="880" height="495"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Referencias
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Google (2022) Sync, sync package - sync - Go Packages. pkg.go.dev. Available at: &lt;a href="https://pkg.go.dev/sync"&gt;https://pkg.go.dev/sync&lt;/a&gt; (Accessed: October 28, 2022). &lt;/li&gt;
&lt;li&gt;Ionos (2020) ¿Qué es el web scraping?, IONOS Digital Guide. Ionos. Available at: &lt;a href="https://www.ionos.es/digitalguide/paginas-web/desarrollo-web/que-es-el-web-scraping/"&gt;https://www.ionos.es/digitalguide/paginas-web/desarrollo-web/que-es-el-web-scraping/&lt;/a&gt; (Accessed: October 28, 2022). &lt;/li&gt;
&lt;li&gt;Manqueros, R. (2021) How to properly handle concurrency and parallelism with Golang. Medium. Available at: &lt;a href="https://medium.com/analytics-vidhya/how-to-properly-handle-concurrency-and-parallelism-with-golang-89dd054b739f"&gt;https://medium.com/analytics-vidhya/how-to-properly-handle-concurrency-and-parallelism-with-golang-89dd054b739f&lt;/a&gt; (Accessed: October 28, 2022). &lt;/li&gt;
&lt;li&gt;Nikolov, M.A. (2015) Concurrent map and slice types in go, Concurrent map and slice types in Go – Marin Atanasov Nikolov – A place about Open Source Software, Operating Systems and some random thoughts. Marin Atanasov Nikolov . Available at: &lt;a href="https://dnaeon.github.io/concurrent-maps-and-slices-in-go/"&gt;https://dnaeon.github.io/concurrent-maps-and-slices-in-go/&lt;/a&gt; (Accessed: October 28, 2022). &lt;/li&gt;
&lt;li&gt;Remeh (2019) Sizedwaitgroup, sizedwaitgroup package - github.com/remeh/sizedwaitgroup - Go Packages. pkg.go.dev. Available at: &lt;a href="https://pkg.go.dev/github.com/remeh/sizedwaitgroup"&gt;https://pkg.go.dev/github.com/remeh/sizedwaitgroup&lt;/a&gt; (Accessed: October 28, 2022). &lt;/li&gt;
&lt;li&gt;Skakun, V. (2022) The Best Programming Languages for Web Scraping. scrape-it. Available at: &lt;a href="https://scrape-it.cloud/blog/web-scraping-languages"&gt;https://scrape-it.cloud/blog/web-scraping-languages&lt;/a&gt; (Accessed: October 28, 2022).&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>go</category>
      <category>webscraping</category>
      <category>programming</category>
      <category>algorithms</category>
    </item>
  </channel>
</rss>
