From 0f5c6dc1c664cdc844ee04431626b2e0fe2539d7 Mon Sep 17 00:00:00 2001 From: Michael SCHAL Date: Sat, 29 Nov 2025 11:05:01 +0100 Subject: [PATCH] feat: introduce `NewSearchService` to orchestrate e-commerce searches and scrape product details. --- app/core/search_config.py | 70 ++++++++++++++++++++++++++++++++-- app/services/search_service.py | 12 ++++-- 2 files changed, 75 insertions(+), 7 deletions(-) diff --git a/app/core/search_config.py b/app/core/search_config.py index 3ab2a72..0005c96 100644 --- a/app/core/search_config.py +++ b/app/core/search_config.py @@ -50,11 +50,75 @@ USER_AGENT_POOL = [ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36", # Firefox Windows "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:133.0) Gecko/20100101 Firefox/133.0", - # Edge Windows - "wait_selector": "a.product-card-click-wrapper[href^='/p/']", - "category": "Grande Surface", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0", +] + +def get_random_user_agent() -> str: + return random.choice(USER_AGENT_POOL) + +# === SITE CONFIGURATIONS === +SITE_CONFIGS = { + # === MAGASINS DISCOUNT === + "gifi.fr": { + "name": "Gifi", + "search_url": "https://www.gifi.fr/resultat-recherche?q={query}", + "product_selector": "a.link, .product-item a.link, .product-tile a.link", + "wait_selector": ".product-item, .product-tile, .products-grid", + "category": "Discount", "requires_proxy": False, }, + "stokomani.fr": { + "name": "Stokomani", + "search_url": "https://www.stokomani.fr/search?options%5Bprefix%5D=last&q={query}", + "product_selector": "a[href^='/products/']", + "wait_selector": "a[href^='/products/']", + "category": "Discount", + "requires_proxy": False, + }, + "bmstores.fr": { + "name": "B&M", + "wait_selector": ".product-card, .search-results", + "category": "Discount", + "requires_proxy": False, + }, + "lafoirfouille.fr": { + "name": "La Foir'Fouille", + "search_url": "https://www.lafoirfouille.fr/catalogsearch/result/?q={query}", + "product_selector": ".product-item a.product-item-link, .product-item-info a", + "wait_selector": ".products-grid, .product-items", + "category": "Discount", + "requires_proxy": False, + }, + "centrakor.com": { + "name": "Centrakor", + "search_url": "https://www.centrakor.com/catalogsearch/result/?q={query}", + "product_selector": ".product-item a.product-item-link, .product-item-info a", + "wait_selector": ".products-grid, .product-items", + "category": "Déco & Maison", + "requires_proxy": False, + "pre_search_selector": "text=\"C'est parti !\"", + }, + "lincroyable.fr": { + "name": "L'Incroyable", + "search_url": "https://www.lincroyable.fr/recherche-query={query}/?", + "product_selector": "a[href^='/p']", + "wait_selector": ".products, .product-miniature", + "category": "Déco & Maison", + "requires_proxy": False, + }, + "action.com": { + "name": "Action", + "search_url": "https://www.action.com/fr-fr/search/?q={query}", + "product_selector": "a[href^='/fr-fr/p/']", + "wait_selector": "a[href^='/fr-fr/p/']", + "category": "Discount", + "requires_proxy": False, + }, + # === GRANDES SURFACES === + "e.leclerc": { + "name": "E.Leclerc", + "search_url": "https://www.e.leclerc/recherche?q={query}", + "product_selector": "a[href*='/fp/'][href*='-']:not([href*='promo'])", # === E-COMMERCE GÉNÉRALISTE === "amazon.fr": { "name": "Amazon France", diff --git a/app/services/search_service.py b/app/services/search_service.py index ecaf7f1..9f6b084 100644 --- a/app/services/search_service.py +++ b/app/services/search_service.py @@ -382,10 +382,14 @@ async def search_products( active_producers = len(generators) async def producer(gen): - async for item in gen: - await queue.put(item) - await queue.put(None) # Sentinel - + try: + async for item in gen: + await queue.put(item) + except Exception as e: + logger.error(f"Error in search producer: {e}") + finally: + await queue.put(None) # Sentinel + # Start producers for gen in generators: asyncio.create_task(producer(gen))