spb/fabri-ka Public
Agrégateur de produits québécois — www.fabri-ka.com
HTML 57.9%
Python 18.6%
TypeScript 15.6%
CSS 7.8%
1# -----------------------------------------------------------------------------2# Fabri-Ka — Agrégateur de produits québécois3# Auteur : Simon-Pierre Boucher — contact@spboucher.ai4# connectors/base.py : connecteur générique par plateforme ecommerce.5# Contrairement à Lou-Ka (1 connecteur / site), Fabri-Ka utilise6# 1 connecteur / PLATEFORME : le même code Shopify sert des centaines7# de boutiques, piloté par le registre data/stores.json.8# -----------------------------------------------------------------------------9from __future__ import annotations1011import os12import time1314import requests1516from ..schema import Product1718USER_AGENT = ("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "19 "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126 Safari/537.36 "20 "FabriKaBot/1.0 (+https://www.fabri-ka.com/bot; contact@spboucher.ai)")2122FIRECRAWL_API = "https://api.firecrawl.dev/v1/scrape"232425class BaseConnector:26 """Connecteur de plateforme : instancié avec la fiche registre d'une boutique."""2728 platform: str = ""29 request_delay: float = 0.530 timeout: int = 3031 max_pages: int = 200 # garde-fou (200 pages x 250 = 50 000 produits)3233 def __init__(self, store: dict) -> None:34 self.store = store35 self.store_id = store["id"]36 self.base = (store.get("url") or f"https://{store['id']}").rstrip("/")37 self.session = requests.Session()38 self.session.headers["User-Agent"] = USER_AGENT39 self.session.headers["Accept-Language"] = "fr-CA,fr;q=0.9,en;q=0.8"40 self._last = 0.04142 def get(self, url: str, **kw) -> requests.Response:43 wait = self.request_delay - (time.time() - self._last)44 if wait > 0:45 time.sleep(wait)46 resp = self.session.get(url, timeout=self.timeout, **kw)47 self._last = time.time()48 resp.raise_for_status()49 return resp5051 def get_rendered(self, url: str) -> str:52 """HTML rendu (JavaScript exécuté) via Firecrawl — sites SPA/anti-bot."""53 key = os.environ.get("FIRECRAWL_API_KEY")54 if not key:55 raise RuntimeError("FIRECRAWL_API_KEY manquant (voir .env)")56 resp = requests.post(57 FIRECRAWL_API,58 json={"url": url, "formats": ["html"]},59 headers={"Authorization": f"Bearer {key}"},60 timeout=120,61 )62 resp.raise_for_status()63 return (resp.json().get("data") or {}).get("html", "")6465 def fetch(self) -> list[Product]: # pragma: no cover - interface66 raise NotImplementedError67