Contexte
MidJourney est une communauté de centaines de milliers d'artistes IA qui publient des prompts et des images. La donnée est ouverte mais éparpillée. Aucun outil ne permet de répondre à des questions simples comme "qui sont les meilleurs créateurs de style cyberpunk ?" ou "quelles catégories explosent ces 30 derniers jours ?".
J'ai construit Tokenverse pour répondre à ces questions à grande échelle : scraping, enrichissement par LLM, et exposition d'analytics utilisateur (leaderboards, profils enrichis, tendances temporelles) via un JSON distribué sur CDN.
Le challenge technique
Le vrai défi n'était pas le scraping — c'était l'orchestration. 7 étapes séquentielles (scrape → download → tag GPT → JSON → analytics → upload CDN → cleanup) à exécuter en parallèle sur des centaines d'utilisateurs sans saturer les rate limits API (OpenAI : 3 req/s par tier, Cloudflare R2 : illimité mais coût upload). Solution : queue manager avec locks SQLite.
Le flow opérationnel — en 7 étapes
Le pipeline transforme une URL de profil MidJourney en analytics utilisateur exposées via CDN. Chaque étape verrouille sa zone via un queue manager SQLite pour qu'un même profil ne soit jamais traité en double :
- 1. Input — un
user_id+ une URL de profil MidJourney à traiter. - 2. Scraping — Puppeteer extrait les prompts et images publics du profil.
- 3. Download multi-format — images ET vidéos téléchargées. Chaque image est régénérée en 4 tailles (thumb 320, preview 640, detail 1280, full) via Sharp.js + PIL. Les vidéos sont transcodées dans les formats adaptés au frontend (poster + mp4 optimisé).
- 4. Tagging IA — GPT-4 classifie chaque image dans une taxonomie de 51 catégories sémantiques (3 calls parallèles, retry exponentiel sur rate limit).
- 5. Build du JSON — données structurées par parts de 1000 images, pré-indexées pour requêtage rapide côté frontend.
- 6. Analytics utilisateur — calcul du profil enrichi : leaderboards, scores de productivité/diversité, tendances temporelles par catégorie.
- 7. Distribution CDN — upload Cloudflare R2 (gzip), exposition via CDN global. Latence frontend < 100 ms.
user_id + URL MidJourney"] A --> B["2. Scraping Puppeteer
prompts + images"] B --> C["3. Download multi-format
4 tailles via Sharp.js + PIL"] C --> D["4. Tagging GPT-4
51 catégories sémantiques
images + vidéos"] D --> E["5. Build JSON structuré
parts de 1000 images"] E --> F["6. Analytics utilisateur
leaderboards / scores / tendances"] F --> G["7. Upload Cloudflare R2
gzip + CDN global"] G --> H["Frontend Lovable
latence < 100 ms"] Q[("Queue Manager
SQLite locks")] B -.lock.-> Q D -.lock.-> Q G -.lock.-> Q
Stack technique
- Node.js 18+ avec Puppeteer headless
- Python + PIL (image processing)
- Sharp.js (compression multi-format)
- 4 tailles : thumb 320 / preview 640 / detail 1280 / full
- GPT-4 via API OpenAI
- Taxonomie 51 catégories sémantiques
- 3 calls GPT en parallèle (rate limit safe)
- Retry exponentiel sur 429
- Master orchestrator (Node.js)
- Queue manager + locks SQLite
- Better-sqlite3 (synchrone, performance)
- Parallélisme contrôlé : 1 scrape, 3 GPT, 3 uploads
- Cloudflare R2 (S3-compatible) avec gzip
- JSON par parts de 1000 images
- CDN global, latence < 100 ms
- Frontend no-code Lovable consommateur
3 décisions d'architecture clés
1. JSON pré-indexé vs API REST
La tentation était de construire une vraie API. Mauvaise idée : trop de latence, trop de coût d'infra. J'ai opté pour des JSON pré-calculés et pré-indexés stockés sur R2, organisés par parts de 1000 images. Le frontend pioche directement le JSON correspondant à la vue demandée. Latence < 100 ms, coût marginal proche de zéro, scalabilité illimitée.
2. SQLite locks pour la concurrence, pas Redis
Pour empêcher 2 jobs de tagger le même user en parallèle, on pourrait utiliser Redis. J'ai préféré
better-sqlite3 en synchrone avec un table "locks" — moins exotique, zéro dépendance externe,
suffisamment performant pour cette charge. C'est le pattern Salesforce SOQL avec FOR UPDATE
appliqué à un pipeline local.
3. Image processing en 4 formats à la source
Plutôt que de redimensionner à la volée côté frontend (coût bandwidth, latence, SEO), je génère les 4 formats à l'étape 2 du pipeline et je les uploade tous sur R2. Le frontend choisit le bon format selon le contexte d'affichage. C'est une optimisation prématurée assumée — elle paie dès les premiers utilisateurs.
Résultats
Lien direct avec Salesforce
Tokenverse est un mini-Einstein. Voici la cartographie :
- Classification GPT-4 en 51 catégories = Einstein Classification Apps / Picklist intelligent automatique.
- Orchestration 7 étapes + queue manager = Flow Salesforce avec Subflows + Platform Events.
- Profils utilisateur enrichis avec scores = Custom Objects + Rollup Summary Fields.
- Leaderboards / tendances temporelles = Reports & Dashboards avec filtres dynamiques.
- SQLite locks pour idempotence = pattern SOQL FOR UPDATE dans Apex pour éviter les races.
- JSON distribué sur R2 CDN = Salesforce CDN / Connect REST API avec cache aggressif.
Lessons learned
- L'orchestration vaut souvent plus que le code métier — 60% du code de Tokenverse est de la plomberie (queue, retry, lock, logs). C'est aussi 60% de la valeur.
- Documenter sa taxonomie comme un contrat — j'ai changé les 51 catégories 4 fois avant de stabiliser. Chaque changement = re-tag de tout l'historique. À éviter en prod.
- JSON statique > API REST pour les analytics — règle universelle : si tes données changent moins d'une fois par minute, sers-les en static. Tu économises 90% des coûts.
- Le projet est aujourd'hui en pause — l'écosystème MidJourney a fermé certains accès publics. Le code reste solide et redéployable en 1-2 jours sur un autre dataset.