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/scrapfly.py : transport de secours via l'API Scrapfly (anti-bot,5# rendu JS optionnel). Utilisé quand l'accès direct échoue (403/429/HTML6# au lieu de JSON). Chaque appel consomme des crédits : on ne l'emploie7# qu'en dernier recours et on mémorise les échecs définitifs.8# -----------------------------------------------------------------------------9from __future__ import annotations1011import json12import os13import threading14import time1516import requests1718API = "https://api.scrapfly.io/scrape"19_LOCK = threading.Lock()20_MIN_INTERVAL = 0.521_last = [0.0]222324def available() -> bool:25 return bool(os.environ.get("SCRAPFLY_API_KEY"))262728def scrapfly_get(url: str, render_js: bool = False, timeout: int = 150) -> tuple[int, str]:29 """GET via Scrapfly. Retourne (status_code_amont, contenu)."""30 key = os.environ.get("SCRAPFLY_API_KEY")31 if not key:32 raise RuntimeError("SCRAPFLY_API_KEY manquant (voir .env)")33 with _LOCK:34 wait = _MIN_INTERVAL - (time.time() - _last[0])35 if wait > 0:36 time.sleep(wait)37 _last[0] = time.time()38 params = {"key": key, "url": url, "asp": "true", "country": "ca"}39 if render_js:40 params["render_js"] = "true"41 r = requests.get(API, params=params, timeout=timeout)42 r.raise_for_status()43 res = r.json().get("result", {})44 return int(res.get("status_code") or 0), res.get("content") or ""454647def scrapfly_json(url: str, render_js: bool = False) -> dict | list:48 status, content = scrapfly_get(url, render_js=render_js)49 if status != 200:50 raise RuntimeError(f"scrapfly upstream {status} pour {url}")51 return json.loads(content)52