© 2026 Thibaud Geisler - SIRET 880 419 122 00036
Gateway REST Python/FastAPI qui agrège Beatport, Bandcamp et SoundCloud derrière un contrat de sortie unique, utilisée par une dizaine de DJs. Accès fiable à des sources sans API publique, nettoyage des titres centralisé, suivi de l'usage par API key. En production.

Plusieurs de mes outils ont besoin des mêmes métadonnées musicales : morceaux, artistes, labels, dates de sortie, BPM, tonalité. À commencer par une application desktop qui re-tagge les fichiers audio, utilisée par une dizaine de DJs de mon entourage. Et un service de collecte sur les artistes et les labels (en cours de développement).
Le problème : aucune plateforme ne couvre le catalogue en entier. J'en interroge donc trois, chacune pour ce qu'elle fait le mieux :
J'ai donc construit une gateway : elle interroge les trois et renvoie toujours le même format, quelle que soit celle qui a répondu.
Mon rôle : conception, développement et exploitation, seul.
L'application de tagging est utilisée par une dizaine de DJs, chacun avec sa propre API key. Cela permet de révoquer un accès sans toucher aux autres et de suivre l'usage de chacun. Sentry ne suffisait pas : il remonte les erreurs, mais pas l'usage.
Solutions : une API key par utilisateur, révocable individuellement, sans ajouter de base de données. Pour le suivi, une stack de logs self-hosted (VictoriaLogs, Grafana) : 30 jours d'historique par API key, un dashboard d'usage et une alerte quand un utilisateur n'obtient plus que des erreurs. Côté sécurité, une revue complète sans faille majeure et un rate limit testé en charge.
Une route répondait en code 200, avec une liste bien formée, sauf qu'il manquait une partie des résultats. La page source annonçait un nombre d'éléments faux : 5 annoncés pour 355 réels, 1 pour 736, 0 pour 322.
Impossible pour l'appelant de s'en rendre compte : pas d'erreur, une réponse valide, juste des éléments qui manquaient à l'appel.
Solutions : quinze combinaisons testées pour trouver d'où venait le problème, puis passage par un autre canal d'accès, correctement paginé. J'ai écarté le quick fix, qui consistait à lire une page dont le compteur est juste : il plafonnait à 21 éléments, donc il réduisait l'erreur au lieu de la supprimer.
Les titres renvoyés par les plateformes contiennent du bruit : mentions promotionnelles, label et référence catalogue collés au titre, artistes mêlés au nom du morceau. Dans un premier temps, j'ai laissé ce nettoyage à chaque application consommatrice. Le problème, c'est qu'elles le refaisaient toutes à leur façon. Des titres pouvaient encore être pollués et surtout beaucoup de code était dupliqué.
Solutions : j'ai centralisé le nettoyage dans la gateway, la seule à voir les champs structurés des plateformes. Quand la source fournit une donnée structurée, elle prime toujours sur l'analyse du titre. Le texte d'origine reste disponible : rien n'est perdu si le nettoyage se trompe. Un jeu de 914 titres réels vérifie les règles à chaque exécution des tests.
Quelle que soit la plateforme interrogée, les routes renvoient les mêmes objets. Une application qui consomme l'API écrit sa logique une seule fois et change de source sans rien réécrire.
Défis techniques : chaque source a ses propres structures et ses propres conventions. L'une range les remixeurs dans des champs dédiés, l'autre se contente d'un nom de label en texte libre. S'y ajoutent trois systèmes de pagination impossibles à réconcilier, dont un sans total fiable et un autre qui n'en a aucun.
Solutions : un schéma pivot commun, avec une règle simple : on n'invente rien. Un champ que la source ne fournit pas ressort vide, jamais rempli au jugé. La pagination passe par un cursor opaque que l'appelant renvoie tel quel sans le lire, ce qui le protège des changements de la source. Enfin, une liste vide n'est jamais une erreur : l'appelant distingue ainsi « cette source n'a rien » de « cette source est cassée ».
Toutes les routes Beatport obtenaient leur token en lisant une page du site web de Beatport. Un soir, ce site web a été bloqué par un challenge anti-bot pendant près de deux heures : toute la source est tombée, alors que l'API de données de Beatport, hébergée à part, répondait toujours. Avant de toucher au code, j'ai vérifié depuis quatre connexions différentes que le blocage touchait tout le monde et pas seulement mon serveur.
Solutions : plutôt que d'attendre le retour à la normale, j'ai supprimé cette dépendance : le token s'obtient par un autre chemin, qui ne passe plus par le site web. J'ai aussi refusé de garder l'ancien chemin en fallback, puisqu'il dépendait du même site web.
Le vrai risque pour un scraper, c'est la panne muette : une source change sa structure et l'extraction casse sans que personne ne le voie.
Solutions : les erreurs remontent dans Sentry, regroupées sur une clé qui inclut la source et le code renvoyé. Une source cassée déclenche donc une seule alerte au lieu de plusieurs centaines. Les données sont nettoyées avant l'envoi et la capture des variables locales est désactivée, sinon la configuration se retrouve dans les stack traces avec une API key en clair. Enfin, un request ID suit tout le parcours, de l'alerte jusqu'au log correspondant.
L'API OAuth limite sévèrement le nombre de tokens qu'on peut générer. En créer un à chaque requête entrante suffit à épuiser le quota dès le premier pic de trafic.
Solutions : un service de token centralisé, avec cache in-memory, refresh anticipé et déduplication quand plusieurs requêtes en demandent un en même temps. Un circuit breaker mémorise la panne pendant trente secondes et répond sans solliciter le réseau ; passé ce délai, une seule requête retente réellement l'appel. Enfin, un cache réseau peut fausser l'âge d'un token : je me fie donc à l'heure d'expiration renvoyée par le serveur plutôt qu'à une durée du type « valable une heure ».
Un appel passait en curl, mais était refusé depuis Python. Pourtant, j'utilisais la même IP et les mêmes headers. Le facteur discriminant n'était donc pas l'IP mais le fingerprint TLS du client HTTP.
Solutions : un changement de client HTTP, appliqué une fois pour toutes à la session partagée par tous les appels sortants. J'ai documenté dès le départ la limite de cette approche : elle agit sur la couche transport, pas sur un challenge JavaScript. Quand un incident est arrivé plus tard, cette note m'a évité de chercher au mauvais endroit.
Code source disponible sur demande (non public par choix stratégique).