#!/usr/bin/env python3 """Enrichissement des boutiques : logo, image de couverture, description. Pour chaque boutique du registre : télécharge la page d'accueil, extrait - logo : apple-touch-icon > icon (plus grande taille) > og:logo - couverture : og:image - description : meta description > og:description - nom d'affichage : og:site_name (si plus propre que le nom actuel) Écrit dans la table stores (colonnes logo_url, cover_url, description_meta). Cache disque data/enrich_cache/.json ; Scrapfly en secours pour les 403. """ import concurrent.futures as cf import json import os import re import sys from urllib.parse import urljoin import requests ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, ROOT) CACHE = os.path.join(ROOT, "data", "enrich_cache") os.makedirs(CACHE, exist_ok=True) for line in open(os.path.join(ROOT, ".env")).read().splitlines(): if "=" in line and not line.startswith("#"): k, _, v = line.partition("=") os.environ.setdefault(k.strip(), v.strip()) from fabrika import db as fdb # noqa: E402 from fabrika.connectors.scrapfly import scrapfly_get, available # noqa: E402 HDRS = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36", "Accept-Language": "fr-CA,fr;q=0.9,en;q=0.8"} LINK_RE = re.compile(r']+>', re.I) META_RE = re.compile(r']+>', re.I) def attr(tag, name): m = re.search(name + r'\s*=\s*["\']([^"\']+)["\']', tag, re.I) return m.group(1) if m else None def extract(base_url, html): head = html[:120000] logo, cover, desc, site_name = None, None, None, None icons = [] for tag in LINK_RE.findall(head): rel = (attr(tag, "rel") or "").lower() href = attr(tag, "href") if not href: continue if "apple-touch-icon" in rel: icons.append((200 if not attr(tag, "sizes") else int((attr(tag, "sizes") or "180x").split("x")[0] or 180), href)) elif rel in ("icon", "shortcut icon"): sizes = attr(tag, "sizes") or "32x32" try: s = int(sizes.split("x")[0]) except ValueError: s = 32 if not href.endswith(".ico"): icons.append((s, href)) if icons: icons.sort(reverse=True) logo = urljoin(base_url, icons[0][1]) for tag in META_RE.findall(head): prop = (attr(tag, "property") or attr(tag, "name") or "").lower() content = attr(tag, "content") if not content: continue if prop == "og:image" and not cover: cover = urljoin(base_url, content) elif prop in ("description", "og:description") and not desc: desc = content.strip()[:400] elif prop == "og:site_name" and not site_name: site_name = content.strip()[:80] elif prop == "og:logo" and not logo: logo = urljoin(base_url, content) return {"logo_url": logo, "cover_url": cover, "description_meta": desc, "site_name": site_name} def enrich_domain(store): dom = store["id"] cpath = os.path.join(CACHE, dom + ".json") if os.path.exists(cpath): return json.load(open(cpath)) url = store.get("url") or f"https://{dom}" html = "" try: r = requests.get(url, headers=HDRS, timeout=20, allow_redirects=True) if r.status_code == 200: html, url = r.text, r.url except Exception: pass if not html and available(): try: status, content = scrapfly_get(url) if status == 200: html = content except Exception: pass rec = {"domain": dom} if html: rec.update(extract(url, html)) # fallback logo : favicon.ico si rien trouvé if not rec.get("logo_url"): rec["logo_url"] = urljoin(url, "/favicon.ico") json.dump(rec, open(cpath, "w")) return rec def main(only_live=True): con = fdb.connect() # colonnes d'enrichissement for col in ("logo_url", "cover_url", "description_meta"): try: con.execute(f"ALTER TABLE stores ADD COLUMN {col} TEXT") except Exception: pass con.commit() rows = [dict(r) for r in con.execute( "SELECT id, url, product_count FROM stores ORDER BY product_count DESC")] con.close() if only_live: pass # on enrichit tout le registre, les vivantes d'abord (tri ci-dessus) print(f"enrichissement de {len(rows)} boutiques…") done = 0 results = [] with cf.ThreadPoolExecutor(16) as ex: for rec in ex.map(enrich_domain, rows): results.append(rec) done += 1 if done % 200 == 0: print(f" {done}/{len(rows)}", flush=True) con = fdb.connect() n = 0 for rec in results: if rec.get("logo_url") or rec.get("description_meta"): con.execute("UPDATE stores SET logo_url=?, cover_url=?, description_meta=? WHERE id=?", (rec.get("logo_url"), rec.get("cover_url"), rec.get("description_meta"), rec["domain"])) n += 1 con.commit(); con.close() print(f"boutiques enrichies: {n}/{len(results)}") if __name__ == "__main__": main()