# ----------------------------------------------------------------------------- # Fabri-Ka — Agrégateur de produits québécois # Auteur : Simon-Pierre Boucher — contact@spboucher.ai # connectors/base.py : connecteur générique par plateforme ecommerce. # Contrairement à Lou-Ka (1 connecteur / site), Fabri-Ka utilise # 1 connecteur / PLATEFORME : le même code Shopify sert des centaines # de boutiques, piloté par le registre data/stores.json. # ----------------------------------------------------------------------------- from __future__ import annotations import os import time import requests from ..schema import Product USER_AGENT = ("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) " "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126 Safari/537.36 " "FabriKaBot/1.0 (+https://www.fabri-ka.com/bot; contact@spboucher.ai)") FIRECRAWL_API = "https://api.firecrawl.dev/v1/scrape" class BaseConnector: """Connecteur de plateforme : instancié avec la fiche registre d'une boutique.""" platform: str = "" request_delay: float = 0.5 timeout: int = 30 max_pages: int = 200 # garde-fou (200 pages x 250 = 50 000 produits) def __init__(self, store: dict) -> None: self.store = store self.store_id = store["id"] self.base = (store.get("url") or f"https://{store['id']}").rstrip("/") self.session = requests.Session() self.session.headers["User-Agent"] = USER_AGENT self.session.headers["Accept-Language"] = "fr-CA,fr;q=0.9,en;q=0.8" self._last = 0.0 def get(self, url: str, **kw) -> requests.Response: wait = self.request_delay - (time.time() - self._last) if wait > 0: time.sleep(wait) resp = self.session.get(url, timeout=self.timeout, **kw) self._last = time.time() resp.raise_for_status() return resp def get_rendered(self, url: str) -> str: """HTML rendu (JavaScript exécuté) via Firecrawl — sites SPA/anti-bot.""" key = os.environ.get("FIRECRAWL_API_KEY") if not key: raise RuntimeError("FIRECRAWL_API_KEY manquant (voir .env)") resp = requests.post( FIRECRAWL_API, json={"url": url, "formats": ["html"]}, headers={"Authorization": f"Bearer {key}"}, timeout=120, ) resp.raise_for_status() return (resp.json().get("data") or {}).get("html", "") def fetch(self) -> list[Product]: # pragma: no cover - interface raise NotImplementedError