
Je pensais que l’ingénierie des données consistait simplement à écrire des scripts. J’ai eu tort.
Pipeline ETL, je pensais avoir une assez bonne maîtrise de ce qu’était réellement l’ingénierie des données. Vous extrayez des données quelque part, vous les nettoyez, vous les chargez dans un endroit utile. ETL. Assez simple.
Pour le contexte, je suis un analyste de données qui essaie de passer à l’ingénierie des données. J’ai documenté publiquement ce voyage, en commençant par une feuille de route d’auto-apprentissage de 12 mois que j’ai élaborée plus tôt cette année. L’étape la plus récente de ce voyage a consisté à créer mon premier pipeline ETL à partir de zéro à l’aide de l’API GitHub, dont j’ai parlé ici sur TDS. Ce pipeline a fonctionné. Il a extrait les données, les a nettoyées et les a enregistrées au format CSV. J’en étais content.
J’ai donc décidé d’aller plus loin, de le rendre plus « prêt pour la production », comme aime à le dire Internet. Ce qui s’est passé ensuite m’a vraiment surpris. Non pas parce que les choses se sont brisées, mais à cause de ce que la rupture a révélé.
Le pipeline original
Le pipeline d’origine était basique, ce qui était bien car c’était le but. Extrayez les données de l’API GitHub, faites un peu de nettoyage, enregistrez le tout dans un fichier CSV. Cela a parfaitement fonctionné pour ce qu’il était : un exercice d’apprentissage. Mais un fichier CSV et un script unique ne correspondent pas à la manière dont fonctionne l’ingénierie des données dans le monde réel. Je voulais découvrir ce que le « monde réel » signifiait réellement dans la pratique, j’ai donc décidé d’aller plus loin et de voir ce qui se passait.
Voici le pipeline original complet pour tous ceux qui n’ont pas lu l’article précédent :
import requests
from datetime import datetime, timedelta
url = "https://api.github.com/search/repositories"
params = {
"q": "language:python created:>2025-04-22",
"sort": "stars",
"order": "desc",
"per_page": 30
}
response = requests.get(url, params=params)
data = response.json()
import pandas as pd
repos = []
for repo in data['items']:
repos.append({
"name": repo['name'],
"owner": repo['owner']['login'],
"stars": repo['stargazers_count'],
"forks": repo['forks_count'],
"language": repo['language'],
"description": repo['description'],
"url": repo['html_url'],
"created_at": repo['created_at']
})
df = pd.DataFrame(repos)
df_clean = df.dropna(subset=['description'])
df_clean = df_clean.copy()
df_clean['viral'] = df_clean['stars'].apply(lambda x: 'Yes' if x > 50000 else 'No')
df_clean = df_clean.sort_values('stars', ascending=False).reset_index(drop=True)
df_clean.to_csv('github_trending_repos.csv', index=False)
print("Pipeline complete. File saved.")
Simple, lisible et ça marche. Mais dès que vous essayez de l’exécuter plus d’une fois, ou d’y revenir le lendemain, les fissures commencent à apparaître.
Premier mur : le pipeline n’avait aucune mémoire
La première mise à niveau a été simple. Au lieu de sauvegarder dans un fichier CSV, j’ai chargé les données dans une base de données SQLite. SQLite n’est encore qu’un seul fichier, mais il se comporte comme une véritable base de données. Vous pouvez l’interroger, vérifier ce qu’il contient déjà et construire dessus correctement. C’était comme un petit changement. Ce n’était pas le cas.
J’ai exécuté le pipeline une fois et j’ai obtenu 22 pensions. Ensuite, je l’ai exécuté une deuxième fois sans rien changer et j’ai vérifié la base de données.
Total rows: 44 Unique repos: 22 Duplicates: 22
Honnêtement, je ne m’y attendais pas. Je pensais que cela pourrait arriver, mais je n’ai jamais vraiment pensé que cela arriverait. Mais je suis content que ce soit le cas, car c’était la première fois que je voyais mon pipeline se briser. Et ce que cela révélait était simple mais important : le script n’avait pas de mémoire. Chaque fois qu’il s’exécutait, il recommençait complètement et ajoutait aveuglément tout ce qu’il trouvait. Aucun avertissement, aucune erreur. Juste « Pipeline terminé » comme si tout allait bien.
C’est là que je suis tombé sur un concept appelé idempotence.
L’idempotence est un mot sophistiqué pour désigner une idée simple. Si quelque chose s’est déjà produit, cela ne devrait pas se reproduire. Dans le contexte d’un pipeline de données, cela signifie que l’exécution de votre pipeline une ou dix fois devrait toujours produire le même résultat. Pas de lignes supplémentaires, pas de doublons, pas de corruption silencieuse de vos données.
Le correctif était simple en principe. Avant d’insérer quoi que ce soit dans la base de données, le pipeline vérifie désormais si cet enregistrement existe déjà. Si c’est le cas, il le supprime d’abord, puis insère la nouvelle version. Un petit changement de mentalité, mais cela change complètement la fiabilité de votre pipeline.
Et voici la partie qui m’a marqué : un script de base n’y pensera jamais tout seul. Vous devez l’intégrer délibérément. Ce n’est plus du script. C’est de l’ingénierie.
Deuxième mur : les données ont disparu du jour au lendemain
Le deuxième mur était moins technique et plus déstabilisant.
Quand j’ai fermé Colab pour la nuit et que je suis revenu le lendemain, il y avait ce sentiment de malaise. J’ai dû tout recommencer à zéro en espérant que rien ne se soit cassé, même si tout avait parfaitement fonctionné la veille. La base de données que j’avais soigneusement construite venait de disparaître. Et je me suis souvenu qu’avant ce projet, j’avais en fait eu du mal à trouver mon fichier de pipeline ETL d’origine. J’ai passé du temps à le chercher avant de finalement le faire. Ce sentiment de presque perdre votre travail reste avec vous.
Je savais qu’il devait y avoir une meilleure façon. Un véritable pipeline ne peut pas dépendre de la présence de quelqu’un pour le réexécuter chaque matin. Les données doivent vivre dans un endroit qui survit au-delà de la session.
Le correctif ici consistait à monter Google Drive directement dans Colab et à pointer la connexion à la base de données là-bas plutôt que vers l’environnement temporaire Colab. Un changement de ligne :
conn = sqlite3.connect('/content/drive/MyDrive/github_repos.db')
La base de données réside désormais dans Google Drive. Fermez la session, redémarrez le runtime, ouvrez entièrement un nouveau notebook. Les données sont toujours là, en attente.
Mais ce correctif a révélé quelque chose de plus important. Si la persistance des données nécessite déjà de réfléchir à l’endroit où se trouvent les choses et à la manière dont elles survivent, que se passe-t-il lorsque vous avez besoin que le pipeline s’exécute automatiquement tous les jours sans que vous y touchiez du tout ?
Troisième mur : personne ne peut appuyer sur « Courir » pour toujours
Le troisième mur était celui qui m’excitait le plus, ce qui me surprenait.
Même avec le problème de doublon résolu et la base de données résidant en toute sécurité dans Google Drive, quelqu’un doit toujours ouvrir le bloc-notes et appuyer sur Exécuter. Colab n’est pas un serveur. C’est un environnement interactif. Il ne se réveille pas à 3 heures du matin, n’extrait pas de nouvelles données de l’API GitHub et ne se rendort pas. Ce n’est pas pour cela qu’il a été construit.
Et quand j’y ai réfléchi d’un point de vue réel, cela a immédiatement cliqué. Dans une entreprise réelle, personne n’est en alerte à minuit en attendant d’exécuter un script. Le pipeline doit fonctionner tout seul. Selon un horaire. De manière fiable. Que quelqu’un regarde ou non.
C’est là qu’interviennent des outils comme Apache Airflow, Prefect et les tâches cron basées sur le cloud. Ce ne sont pas des scripts Python. Ce sont des systèmes qui vivent sur des serveurs, gèrent les plannings, gèrent les pannes, envoient des alertes en cas de problème et conservent un historique de chaque exécution.
Honnêtement, la planification est le concept que je suis le plus impatient d’explorer ensuite, car c’est là que l’ingénierie des données commence à ressembler à un véritable travail d’infrastructure.
Parcourir les changements clés
Permettez-moi de passer en revue les trois modifications réelles que j’ai apportées au pipeline et ce que chacune fait.
1. Échanger CSV contre SQLite
# Before
df_clean.to_csv('github_trending_repos.csv', index=False)
# After
conn = sqlite3.connect('github_repos.db')
df_clean.to_sql('repos', conn, if_exists='append', index=False)
conn.close()
L’enregistrement au format CSV convient pour une analyse unique. Mais un CSV n’est qu’un fichier texte. Vous ne pouvez pas l’interroger facilement et il n’évolue pas correctement à mesure que vos données augmentent. SQLite est une véritable base de données, ce qui signifie que vous pouvez y exécuter des requêtes SQL, vérifier ce qui s’y trouve déjà et construire dessus correctement. Même simplicité, bien plus de capacités.
2. Résoudre le problème de doublon
cursor.execute('''
DELETE FROM repos WHERE url IN (SELECT url FROM repos_temp)
''')
cursor.execute('''
INSERT INTO repos SELECT * FROM repos_temp
''')
C’est la solution à l’idempotence. Avant d’insérer quoi que ce soit, le pipeline vérifie si ce référentiel existe déjà dans la base de données en utilisant son URL comme identifiant unique. Si c’est le cas, il le supprime d’abord, puis insère la nouvelle version. Ainsi, quel que soit le nombre d’exécutions du pipeline, vous obtenez toujours des données propres et non dupliquées.
3. Données persistantes sur Google Drive
# Before
conn = sqlite3.connect('github_repos.db')
# After
conn = sqlite3.connect('/content/drive/MyDrive/github_repos.db')
C’est une ligne mais ça change tout. Au lieu d’enregistrer la base de données dans l’environnement temporaire Colab où elle disparaît à la fermeture de la session, elle est enregistrée directement sur Google Drive. Fermez le notebook, redémarrez le runtime, revenez demain. Vos données sont toujours là et vous attendent.
Voici le pipeline entièrement mis à niveau regroupant les trois modifications :
import requests
import pandas as pd
import sqlite3
from datetime import datetime, timedelta
# Extract
yesterday = (datetime.now() - timedelta(days=1)).strftime('%Y-%m-%d')
url = "https://api.github.com/search/repositories"
params = {
"q": f"language:python created:>{yesterday}",
"sort": "stars",
"order": "desc",
"per_page": 30
}
response = requests.get(url, params=params)
data = response.json()
# Transform
repos = []
for repo in data['items']:
repos.append({
"name": repo['name'],
"owner": repo['owner']['login'],
"stars": repo['stargazers_count'],
"forks": repo['forks_count'],
"language": repo['language'],
"description": repo['description'],
"url": repo['html_url'],
"created_at": repo['created_at']
})
df = pd.DataFrame(repos)
df_clean = df.dropna(subset=['description'])
df_clean = df_clean.copy()
df_clean['viral'] = df_clean['stars'].apply(lambda x: 'Yes' if x > 50000 else 'No')
df_clean = df_clean.sort_values('stars', ascending=False).reset_index(drop=True)
# Load
conn = sqlite3.connect('/content/drive/MyDrive/github_repos.db')
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS repos (
name TEXT, owner TEXT, stars INTEGER, forks INTEGER,
language TEXT, description TEXT, url TEXT,
created_at TEXT, viral TEXT, loaded_at TEXT
)
''')
df_clean['loaded_at'] = datetime.now().strftime('%Y-%m-%d')
df_clean.to_sql('repos_temp', conn, if_exists='replace', index=False)
cursor.execute('''
DELETE FROM repos WHERE url IN (SELECT url FROM repos_temp)
''')
cursor.execute('''
INSERT INTO repos SELECT * FROM repos_temp
''')
conn.commit()
conn.close()
print("Pipeline complete. Duplicates handled.")
Alors, qu’est-ce que l’ingénierie des données, vraiment ?
Si vous m’aviez demandé ce qu’était l’ingénierie des données après avoir construit mon premier pipeline ETL, j’aurais répondu qu’il s’agissait principalement d’écrire des scripts. Extraire, transformer, charger. Répéter. Voilà à quoi cela ressemblait vu de l’extérieur.
Mais après avoir poussé ce pipeline plus loin et l’avoir vu se briser de trois manières différentes, j’y pense différemment maintenant. L’ingénierie des données consiste à créer des systèmes fiables, et pas seulement des scripts qui s’exécutent. Il y a une différence. Un script fait ce que vous lui dites, une fois, quand vous le lui dites. Un système gère les pannes, se souvient de ce qu’il a déjà fait, conserve les données au-delà d’une seule session et s’exécute selon un planning sans que personne ne le surveille.
Idempotence, persistance, ordonnancement. Aucun de ces concepts n’est apparu lorsque j’exécutais mon pipeline une fois dans un notebook. Ils ne se sont révélés que lorsque j’ai essayé de les faire fonctionner comme quelque chose de réel.
Et dans une vraie entreprise, vous ne pouvez pas vous permettre de vous tromper. Les données produites par votre pipeline sont utilisées pour prendre des décisions. S’il est plein de doublons, s’il disparaît du jour au lendemain ou s’il ne s’exécute que lorsque quelqu’un se souvient d’appuyer sur un bouton, il ne s’agit pas d’un pipeline de données. C’est un handicap.
J’ai encore beaucoup à apprendre. La planification est le prochain mur vers lequel je me dirige. Mais je m’y dirige maintenant en sachant que l’ingénierie des données n’a jamais consisté uniquement à écrire des scripts. J’ai juste dû casser quelques choses pour voir ça.
Il s’agit d’une série continue d’ingénierie de données. Suivez-moi pendant que je documente chaque étape du voyage, y compris les parties qui ne se passent pas bien.
Connectez-vous avec moi sur LinkedIn, YouTubeet Gazouillement.



