Intégration d'Elasticsearch et Vue.js pour une recherche de produits en temps réel

Mise en place d'un moteur de recherche full-text

Ce guide détaille la création d'une application de recherche de produits en utilisant Elasticsearch pour l'indexation et les requêtes full-text, couplé à une interface frontend réactive développée avec Vue.js. L'objectif est de permettre une recherche instantanée avec mise en évidence des termes correspondants.

1. Préparation du jeu de données

Voici la structure des données simulées qui seront indexées. Les champs ont été standardisés en anglais pour faciliter l'intégration technique.

Name Description Price Stock Brand
Smart Watch Smratwatch with health tracking and notifications. 199.99 1000 TechWatch
4K Smart TV 4K resolution smart TV offering excellent picture quality. 699.99 500 VisionTech
Wireless Earbuds Noise-cancelling wireless earbuds for premium audio. 149.99 800 AudioMasters
Laptop High-performence laptop with a fast processor. 999.99 300 TechLaptops
Digital Camera High-resolution digital camera with multiple shooting modes. 449.99 200 PhotoPro
Portable Charger Portable power bank for mobile devices. 29.99 2000 PowerBoost
Wireless Router High-speed wireless router for large networks. 79.99 400 NetSpeed
Gaming Console Gaming console supporting various games and entertainment. 399.99 100 GameZone
Phone Case Durable phone case compatible with multiple models. 19.99 1500 PhoneGuard
Sneakers High-performance sneakers for various sports. 79.99 800 SportsTech
4K Monitor 4K UHD monitor providing exceptional image quality. 599.99 150 UltraView
Smart Home Hub Smart home device for centralized automation control. 249.99 300 SmartLiving

2. Script d'ingestion de données en Python

Le script suivant utilise la bibliothèque officielle elasticsearch pour lire un fichier CSV et indexer les documents en masse via l'API helpers.bulk, ce qui est plus performant que des insertions unitaires.

import csv
from elasticsearch import Elasticsearch, helpers

# Initialisation du client Elasticsearch
client_es = Elasticsearch(hosts=["http://localhost:9200"])

# Vérification de la connexion au cluster
if not client_es.ping():
    raise ConnectionError("Impossible d'établir une connexion avec Elasticsearch.")

csv_file_path = 'products_dataset.csv'

def generate_document_actions():
    """Générateur pour préparer les actions d'indexation."""
    with open(csv_file_path, mode='r', encoding='utf-8') as file:
        reader = csv.DictReader(file)
        for row in reader:
            yield {
                "_index": "product_catalog",
                "_id": row['sku_id'],
                "_source": {
                    "name": row['name'],
                    "description": row['description'],
                    "price": float(row['price']),
                    "stock": int(row['stock']),
                    "brand": row['brand']
                }
            }

# Exécution de l'indexation en masse
try:
    success_count, errors = helpers.bulk(
        client_es, 
        generate_document_actions(), 
        refresh=True
    )
    print(f"Indexation terminée : {success_count} documents insérés avec succès.")
    if errors:
        print(f"Erreurs rencontrées : {errors}")
except Exception as e:
    print(f"Échec de l'opération d'indexation : {e}")

3. Interface de recherche avec Vue.js

L'interface frontend est construite avec Vue 3 en utilisant l'API de composition. Elle interroge directement l'API REST d'Elasticsearch, gère la recherche floue et affiche les résultats avec la mise en évidence (highlight) des termes recherchés.


<html lang="fr">
<head>
    <meta charset="UTF-8">
    <title>Recherche de Produits</title>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
    <style>
        body { font-family: Arial, sans-serif; background: #f4f7f6; padding: 20px; }
        .search-container { max-width: 800px; margin: 0 auto; }
        input[type="text"] { width: 100%; padding: 10px; font-size: 16px; margin-bottom: 20px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; }
        .product-card { background: #fff; padding: 15px; margin-bottom: 15px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
        .product-title { font-size: 1.2em; font-weight: bold; color: #333; }
        .product-title em { color: #e74c3c; font-style: normal; font-weight: bold; }
        .product-details { margin-top: 8px; color: #666; }
        .price { color: #27ae60; font-weight: bold; }
    </style>
</head>
<body>
    <div id="app" class="search-container">
        <h2>Catalogue de Produits</h2>
        <input 
            type="text" 
            v-model="searchTerm" 
            @input="executeSearch" 
            placeholder="Rechercher un produit par nom..."
        >
        
        <div v-if="results.length > 0">
            <div v-for="item in results" :key="item.id" class="product-card">
                <div class="product-title" v-html="item.highlightedName"></div>
                <div class="product-details">
                    <p><strong>Description:</strong> {{ item.description }}</p>
                    <p><strong>Marque:</strong> {{ item.brand }}</p>
                    <p><strong>Prix:</strong> <span class="price">{{ item.price }} €</span></p>
                    <p><strong>Stock disponible:</strong> {{ item.stock }} unités</p>
                </div>
            </div>
        </div>
        <p v-else-if="searchTerm && !isLoading">Aucun produit trouvé.</p>
    </div>

    <script>
        const { createApp, ref } = Vue;

        createApp({
            setup() {
                const searchTerm = ref('');
                const results = ref([]);
                const isLoading = ref(false);

                const executeSearch = async () => {
                    if (!searchTerm.value.trim()) {
                        results.value = [];
                        return;
                    }

                    isLoading.value = true;
                    const esQuery = {
                        query: {
                            match: {
                                "name": {
                                    "query": searchTerm.value,
                                    "fuzziness": "AUTO"
                                }
                            }
                        },
                        highlight: {
                            fields: {
                                "name": {}
                            }
                        }
                    };

                    try {
                        const response = await fetch('http://localhost:9200/product_catalog/_search', {
                            method: 'POST',
                            headers: { 'Content-Type': 'application/json' },
                            body: JSON.stringify(esQuery)
                        });
                        const data = await response.json();
                        
                        results.value = data.hits.hits.map(hit => ({
                            id: hit._id,
                            highlightedName: hit.highlight?.name?.[0] || hit._source.name,
                            description: hit._source.description,
                            brand: hit._source.brand,
                            price: hit._source.price,
                            stock: hit._source.stock
                        }));
                    } catch (error) {
                        console.error('Erreur lors de la requête Elasticsearch:', error);
                    } finally {
                        isLoading.value = false;
                    }
                };

                return {
                    searchTerm,
                    results,
                    isLoading,
                    executeSearch
                };
            }
        }).mount('#app');
    </script>
</body>
</html>

Étiquettes: Elasticsearch vuejs Python full-text-search rest-api

Publié le 19 juillet à 09h12