Malfonction API meteo france

Bonjour à tous,
Comme beaucoup j’attendais avec impatience l’ouverture en opendata des données de météofrance mais je bute sur un problème avec l’API des données d’observations sur la route « /station/horaire ».
En effet cette requête permet normalement de pouvoir récupérer les données en fonction de l’id_station (ce qui fonctionne très bien) mais aussi de la date, le problème étant que peut importe la date que j’envoi à l’API je me retrouve avec les même valeurs pour tout les champs.
L’API étant très peu documenté il est même compliqué de comprendre à quoi correspondent les valeurs que l’on recupère notamment les dates.
Est-ce que quelqu’un qui aurait un petit peu de temps pourrait tester de son côté pour voir si il a le même probleme :slight_smile: ?

PS: voici quelques log pour mieux comprendre

01/02/24 14:48:14 - [INFO] - Getting token
01/02/24 14:48:14 - [INFO] - Token received, duration time: 3600 seconds
01/02/24 14:48:15 - [INFO] - Calling: https://public-api.meteofrance.fr/public/DPObs/v1/station/horaire?id_station=31011001&date=2023-01-01T12:00:00Z&format=json
01/02/24 14:48:15 - [INFO] - Temperature received for the 01/1/2023 at 12:00 , Tmin: 15.7, Tmax: 16.4, T: 16.3
01/02/24 14:48:15 - [INFO] - Calling: https://public-api.meteofrance.fr/public/DPObs/v1/station/horaire?id_station=31011001&date=2023-02-01T12:00:00Z&format=json
01/02/24 14:48:15 - [INFO] - Temperature received for the 01/2/2023 at 12:00 , Tmin: 15.7, Tmax: 16.4, T: 16.3
01/02/24 14:48:15 - [INFO] - Calling: https://public-api.meteofrance.fr/public/DPObs/v1/station/horaire?id_station=31011001&date=2023-03-01T12:00:00Z&format=json
01/02/24 14:48:15 - [INFO] - Temperature received for the 01/3/2023 at 12:00 , Tmin: 15.7, Tmax: 16.4, T: 16.3
01/02/24 14:48:15 - [INFO] - Calling: https://public-api.meteofrance.fr/public/DPObs/v1/station/horaire?id_station=31011001&date=2023-04-01T12:00:00Z&format=json
01/02/24 14:48:15 - [INFO] - Temperature received for the 01/4/2023 at 12:00 , Tmin: 15.7, Tmax: 16.4, T: 16.3
01/02/24 14:48:15 - [INFO] - Calling: https://public-api.meteofrance.fr/public/DPObs/v1/station/horaire?id_station=31011001&date=2023-05-01T12:00:00Z&format=json
01/02/24 14:48:15 - [INFO] - Temperature received for the 01/5/2023 at 12:00 , Tmin: 15.7, Tmax: 16.4, T: 16.3
01/02/24 14:48:15 - [INFO] - Calling: https://public-api.meteofrance.fr/public/DPObs/v1/station/horaire?id_station=31011001&date=2023-06-01T12:00:00Z&format=json
01/02/24 14:48:15 - [INFO] - Temperature received for the 01/6/2023 at 12:00 , Tmin: 15.7, Tmax: 16.4, T: 16.3
01/02/24 14:48:16 - [INFO] - Calling: https://public-api.meteofrance.fr/public/DPObs/v1/station/horaire?id_station=31011001&date=2023-07-01T12:00:00Z&format=json
01/02/24 14:48:16 - [INFO] - Temperature received for the 01/7/2023 at 12:00 , Tmin: 15.7, Tmax: 16.4, T: 16.3
01/02/24 14:48:16 - [INFO] - Calling: https://public-api.meteofrance.fr/public/DPObs/v1/station/horaire?id_station=31011001&date=2023-08-01T12:00:00Z&format=json
01/02/24 14:48:16 - [INFO] - Temperature received for the 01/8/2023 at 12:00 , Tmin: 15.7, Tmax: 16.4, T: 16.3
01/02/24 14:48:16 - [INFO] - Calling: https://public-api.meteofrance.fr/public/DPObs/v1/station/horaire?id_station=31011001&date=2023-09-01T12:00:00Z&format=json
01/02/24 14:48:16 - [INFO] - Temperature received for the 01/9/2023 at 12:00 , Tmin: 15.7, Tmax: 16.4, T: 16.3
01/02/24 14:48:16 - [INFO] - Calling: https://public-api.meteofrance.fr/public/DPObs/v1/station/horaire?id_station=31011001&date=2023-10-01T12:00:00Z&format=json
01/02/24 14:48:16 - [INFO] - Temperature received for the 01/10/2023 at 12:00 , Tmin: 15.7, Tmax: 16.4, T: 16.3
01/02/24 14:48:16 - [INFO] - Calling: https://public-api.meteofrance.fr/public/DPObs/v1/station/horaire?id_station=31011001&date=2023-11-01T12:00:00Z&format=json
01/02/24 14:48:16 - [INFO] - Temperature received for the 01/11/2023 at 12:00 , Tmin: 15.7, Tmax: 16.4, T: 16.3
01/02/24 14:48:16 - [INFO] - Calling: https://public-api.meteofrance.fr/public/DPObs/v1/station/horaire?id_station=31011001&date=2023-12-01T12:00:00Z&format=json
01/02/24 14:48:16 - [INFO] - Temperature received for the 01/12/2023 at 12:00 , Tmin: 15.7, Tmax: 16.4, T: 16.3

Je m’excuse, j’ai recu la reponse du support de meteofrance et je n’utilisé simplement pas la bonne API :sweat_smile:.

1 « J'aime »

Bonjour @I3UX;
J’ai le même problème. J’ai vu qu’il y a une autre route qui pourrait le faire ( /paquet/stations/horaire ) mais j’obtiens un csv avec seulement le header (pas de données). Quelle a été ta solution ?
Merci

Bonjour @pierreloicq ,
J’ai finalement utilisé cette API METEO FRANCE – L’API DonneesPubliquesClimatologie. Elle fonctionne avec un systeme de commande qu’il faut demander au serveur de preparer puis à récuperer. Attention ta commande ne peux pas depasser une période de plus de 1ans :confused: . En plus l’API à quelque mal fonction au niveau de sa gateway et peut te renvoyer des erreur http 502 sans raison (La meme requete peut aboutir ou non :man_shrugging:).
J’espere avoir répondu à ta question.
Bonne journée,
IEUX!

1 « J'aime »

Merci beaucoup, ça marche ! :heart_eyes:

Bonjour,

Je vous écris car je suis confronté à un problème de collecte de donnée automatique via l’ API de meteofrance.En effet, je fais une ingestion de données pour collecter les données de la précipitation journalière de plusieurs stations actives en utilisant les APIs:

– BASE_URL_STATIONS = « https://public-api.meteofrance.fr/public/DPClim/v1/liste-stations/quotidienne »
– BASE_URL_COMMANDE = « https://public-api.meteofrance.fr/public/DPClim/v1/commande-station/quotidienne »
– BASE_URL_FICHIER = « https://public-api.meteofrance.fr/public/DPClim/v1/commande/fichier »

tout en automatisant le processus de l’ingestion en collectant les données mois par mois pour une liste de stations et créer afin une base de donnée pour pour l’utilisant finale.

Mais je suis confronté à un problème de de code 410 et code 201.

Veillez voir mon logs: [2025-01-16, 09:48:19 CET] {logging_mixin.py:137} INFO - Station=29113001, cmde=790937235188, code=410. Pause spéciale de 5s…
[2025-01-16, 09:48:29 CET] {logging_mixin.py:137} INFO - Station=29113001, cmde=790937235188, code=410. Pause spéciale de 5s…
[2025-01-16, 09:48:39 CET] {logging_mixin.py:137} INFO - Station=29113001, cmde=790937235188, code=410. Pause spéciale de 5s…
[2025-01-16, 09:48:49 CET] {logging_mixin.py:137} INFO - Station=29113001, cmde=790937235188, code=410. Pause spéciale de 5s…
[2025-01-16, 09:48:54 CET] {precipitation_dags.py:147} ERROR - [skip] station=29113001, commande=790937235188 introuvable (après 10 tentatives).
[2025-01-16, 09:48:54 CET] {logging_mixin.py:137} INFO - [29113001] Commande pour 2015-04-01T00:00:00Z → 2015-05-01T00:00:00Z
[2025-01-16, 09:49:00 CET] {logging_mixin.py:137} INFO - Station=29113001, cmde=790937331838, tentative=1, code=201. Patience (fichier non prêt)…
[2025-01-16, 09:49:05 CET] {logging_mixin.py:137} INFO - Station=29113001, cmde=790937331838, code=410. Pause spéciale de 5s…

Est ce que vous avez une solution pour régler ces problèmes? ainsi que des conseils?

Je ne sais pas si ces API sont favorables à l’ingestion automatisée pour extraire ces données.

Cordialement,

J’utilisais cette API dans mon ancien travail donc je ne pourrais pas fournir de sample de code, mais j’avais automatisé la récupération des données avec un script python.
Le code 201 signifie que la requête à bien abouti et qu’une ressource à été créée par contre je n’ai jamais été confronté à une 410, mes conseils pour cette API un poil capricieuse sont de ne pas hésiter à englober les fonctions de requêtes dans des boucles (avec un timeout et un delay) tant que le bon code http n’est pas reçu et de bien lire la doc qui est un peu cachée ici

PS: Le code 410 est peut être lié au « trou » de données de certaines stations, des fois les stations sont inactives pendant un certain temps ce qui m’a déjà posé problème lorsque j’utilisais l’API, le serveur n’arrivait pas à créer le fichier car il n’y avait simplement pas de données

Bonjour @I3UX oui mais de manière manuelle , on trouve qu’il y a des données mais le problème se trouve lors de l’ingestion. j’ai une question importante: je vous met une partie de mon code pour savoir si le chemin est correcte pour la collecte de données de précipitation par station.

BASE_URL_STATIONS = "https://public-api.meteofrance.fr/public/DPClim/v1/liste-stations/quotidienne"
BASE_URL_COMMANDE = "https://public-api.meteofrance.fr/public/DPClim/v1/commande-station/quotidienne"
BASE_URL_FICHIER  = "https://public-api.meteofrance.fr/public/DPClim/v1/commande/fichier"

# Paramètres date : on récupère depuis 2015-01-01 jusqu'à aujourd'hui
START_DATE = datetime(2015, 1, 1)
END_DATE   = datetime.now()

# Liste des stations "actives" identifiées manuellement
ACTIVE_STATIONS = [
    "29018001", "29022001", "29041002", "29075001", "29082001", "29105800",
    "29113001", "29120001", "29155005", "29163003", "29168001", "29170001",
    "29178001", "29179001", "29214001", "29216001", "29249002", "29263002",
    "29264001", "29276001", "29277001", "29293001"
]

OUTPUT_DIR = "/opt/airflow/data"
os.makedirs(OUTPUT_DIR, exist_ok=True)

def monthly_ranges(start_dt, end_dt):
    """
    Générateur de créneaux mensuels :
    (2015-01-01 -> 2015-02-01), (2015-02-01 -> 2015-03-01), etc.
    On s’arrête lorsque le prochain début >= end_dt.
    """
    current = start_dt
    while current < end_dt:
        next_month = (current.month % 12) + 1
        next_year  = current.year + (current.month // 12)
        dt_next = datetime(next_year, next_month, 1, 0, 0, 0)  # 1er jour du mois suivant
        yield (current, min(dt_next, end_dt))
        current = dt_next

def fetch_all_stations(departement_id):
    """
    Récupère la liste de *toutes* les stations du département,
    puis on filtrera par ACTIVE_STATIONS pour ne garder que nos stations d'intérêt.
    """
    print("[fetch_all_stations] Récupération de la liste des stations...")
    headers = {
        "accept": "*/*",
        "apikey": API_KEY
    }
    params = {"id-departement": departement_id}
    try:
        resp = requests.get(BASE_URL_STATIONS, headers=headers, params=params)
        resp.raise_for_status()
        stations_list = resp.json()  # c'est un tableau JSON
        print(f" => {len(stations_list)} stations disponibles dans le département {departement_id}")
        return stations_list
    except Exception as e:
        logging.error(f"Erreur lors de la récupération des stations dep={departement_id} : {e}")
        return []

def fetch_precipitation_monthly(station_id, start_dt, end_dt):
    """
    Pour 1 station, on boucle mois par mois de start_dt à end_dt.
    À chaque mois :
      1) GET /commande-station/quotidienne?id-station=... => on récupère un 'cmde_id'
      2) GET /commande/fichier?id-cmde=... => le CSV (avec réessais si code=201/204 ou ConnectionError)
      3) on parse le CSV => on accumule en mémoire (liste de dicts).
    """
    headers = {
        "accept": "*/*",
        "apikey": API_KEY
    }
    all_rows = []

    for (deb, fin) in monthly_ranges(start_dt, end_dt):
        date_deb_str = deb.strftime("%Y-%m-%dT%H:%M:%SZ")
        date_fin_str = fin.strftime("%Y-%m-%dT%H:%M:%SZ")

        print(f"[{station_id}] Commande pour {date_deb_str} -> {date_fin_str}")

        # 1) Commander le CSV (asynchrone)
        params = {
            "id-station": station_id,
            "date-deb-periode": date_deb_str,
            "date-fin-periode": date_fin_str
        }
        try:
            resp_cmd = requests.get(BASE_URL_COMMANDE, headers=headers, params=params)
            resp_cmd.raise_for_status()
            data_cmd = resp_cmd.json()
            cmde_id  = data_cmd["elaboreProduitAvecDemandeResponse"]["return"]
        except Exception as e:
            logging.error(f"Erreur commande station={station_id} : {e}")
            continue

        # 2) Télécharger le CSV, en essayant jusqu'à 20 fois
        dl_params = {"id-cmde": cmde_id}
        max_attempts = 10
        csv_raw = None

        for attempt in range(1, max_attempts + 1):
            # On attend plus longtemps qu'avant (5s) pour donner à l'API
            # le temps de générer effectivement le fichier
            time.sleep(5)

            try:
                resp_file = requests.get(BASE_URL_FICHIER, headers=headers, params=dl_params)
            except requests.exceptions.ConnectionError as e:
                logging.error(f"[KO] station={station_id}, cmde={cmde_id}, ConnectionError: {e}")
                # On retente la boucle
                continue

            if resp_file.status_code == 200:
                # Fichier prêt => on le récupère
                csv_raw = resp_file.text
                break
            elif resp_file.status_code in (201, 204):
                print(f"Station={station_id}, cmde={cmde_id}, tentative={attempt}, "
                      f"code={resp_file.status_code}. Patience (fichier non prêt)...")
                continue
            elif resp_file.status_code in (410, 500):
                # On peut, par exemple, attendre encore plus si 410/500
                print(f"Station={station_id}, cmde={cmde_id}, code={resp_file.status_code}. Pause spéciale de 5s...")
                time.sleep(5)
                # On retente
                continue
            else:
                logging.error(f"[KO] station={station_id}, cmde={cmde_id}, code={resp_file.status_code}")
                break

        if not csv_raw:
            # On n'a jamais eu de code=200 => skip ce mois
            logging.error(f"[skip] station={station_id}, commande={cmde_id} introuvable (après {max_attempts} tentatives).")
            continue

        # 3) Parser le CSV
        f = StringIO(csv_raw)
        reader = csv.reader(f, delimiter=';')

        for row in reader:
            # On suppose row[0]=station_id, row[1]=YYYYMMDD, row[2]=RR...
            if len(row) < 3:
                continue
            stid    = row[0]
            yyyymmdd = row[1]
            rr_val   = row[2]

            # On ne garde que la station voulue
            if stid == station_id and yyyymmdd.isdigit():
                yyyy = yyyymmdd[0:4]
                mm   = yyyymmdd[4:6]
                dd   = yyyymmdd[6:8]
                date_str = f"{yyyy}-{mm}-{dd}"

                all_rows.append({
                    "station": station_id,
                    "date": date_str,
                    "RR": rr_val
                })

        print(f" => {len(all_rows)} lignes cumulées (après ce mois).")
        time.sleep(1)  # petite pause pour ne pas saturer l'API

    return all_rows

NB: Toutes les stations listées sont ouvertes avec les données disponible. Mais j’ai des difficultés pour l’ingestion de manière automatique.

La solution à ton problème se trouve dans la doc de l’api, MeteofranceWeb, la requête pour recupérer la commande renvoi une 201 quand elle abouti correctement, RTFM, voila un petit fix pour ton code :slight_smile: .

if resp_file.status_code == 201:
                # Fichier prêt => on le récupère
                csv_raw = resp_file.text
                break
            elif resp_file.status_code == 204:
                print(f"Station={station_id}, cmde={cmde_id}, tentative={attempt}, "
                      f"code={resp_file.status_code}. Patience (fichier non prêt)...")
                continue

Rebonjour @I3UX , merci pour le retour. Mais ça ne résoud pas vraiment le problème car ça donne toujours des erreurs. Pour avoir l’esprit net vis a vis du code,j’ai fait ça: curl -X GET "https://public-api.meteofrance.fr/public/DPClim/v1/liste-stations/quotidienne?id-departement=29&apikey=VOTRE_API_KEY" en mettant mon API_KEY. Mais j’ai comme reponse: {"code":"900901","message":"Invalid Credentials","description":"Invalid Credentials. Make sure you have provided the correct security credentials"} et egalement dans mon API il n y a vraiment pas d’erreur et tout est nickel. Je ne comprends pas pourquoi cette erreur meme le fait de pingler ping public-api.meteofrance.fr, il n’ y a pas de reponse , on a comme reponse

Envoi d’une requête 'ping' sur public-api.meteofrance.fr [137.129.43.104] avec 32 octets de données :
Délai d’attente de la demande dépassé.
Délai d’attente de la demande dépassé.
Délai d’attente de la demande dépassé.
Délai d’attente de la demande dépassé.

Statistiques Ping pour 137.129.43.104:
    Paquets : envoyés = 4, reçus = 0, perdus = 4 (perte 100%).

Mais le ping avec les autres sites ça passe nickel.
Meteo france n’a vraiment pas de service pour qu’on les contacte car on veut savoir le probleme en fait. Meme sur github il n y a rien pour ouvrir un issue. Est ce que je peux avoir quelques solutions là? Merci de l’aide.

Je pense que les serveurs api de meteofrance sont derrière un pare feu qui bloque les ping.

Pour la requete curl l’API key se passe dans le header d’apres la doc

curl -X 'GET' \
  'https://public-api.meteofrance.fr/public/DPClim/v1/liste-stations/quotidienne?id-departement=29' \
  -H 'accept: */*' \
  -H 'apikey: YOUR_API_KEY'

Le script fonctionne bien de mon côté avec mon fix

[29018001] Commande pour 2015-01-01T00:00:00Z -> 2015-02-01T00:00:00Z
 => 32 lignes cumulées (après ce mois).
[29018001] Commande pour 2015-02-01T00:00:00Z -> 2015-03-01T00:00:00Z
 => 61 lignes cumulées (après ce mois).

à partir de là je ne peux plus faire grand chose pour toi :frowning:

Tu peux contacter le support ici ils repondent assez vite dans mes souvenirs.
Bonne soirée !

Salut @I3UX , merci pour l’aide,

Problème résolu.
Cordialement,