1avergne

Importer plusieurs fichiers en parallèle avec PowerQuery

2022-10-03

J’ai eu envie de faire un rapport sur la météo et ça tombe bien, Météo France donne accès à l’historique d’une cinquantaine de stations sur 15 ans. On peut télécharger les données ici pour une date ou pour un mois précis.

image]

Je rencontre deux problématiques :

En téléchargeant un premier fichier j’identifie le format du nom du fichier : l’année et le mois sont indiqués dans l’adresse.

donneespubliques.meteofrance.fr/donnees_libres/Txt/Synop/Archive/synop.202210.csv.gz

image

Il suffit de changer l’année et le mois pour accéder aux données d’une autre période. Avec un langage procédural ce serai facile de récupérer tout l’historique dans une boucle :

$i = 0
while($i -lt 12){
    $m = ((Get-Date).AddMonths(-1 * $i)) | Get-Date -Format "yyyyMM"
    $uri = "https://donneespubliques.meteofrance.fr/donnees_libres/Txt/Synop/Archive/synop." + $m + ".csv.gz"
    Invoke-RestMethod $uri -Method 'GET' -OutFile $("synop." + $m + ".csv.gz") 
    $i = $i + 1
}

Mais dans PowerQuery, il n’y a pas de boucle. Il faut donc procéder en 3 étapes :

La liste des fichiers

Disons que l’on veuille récupérer 3 ans d’historique, soit 36 mois :

= List.Generate(() > 0, each _ <= 36, each _ + 1)
= Table.AddColumn(#"Colonnes renommées", "Code Mois", each DateTime.ToText(Date.AddMonths(Date.EndOfMonth(DateTime.LocalNow()), 0 - [Offset]), "yyyyMM"))

image

= Table.AddColumn(#"Chemin Fichier ajouté", "Fichier", each Web.Contents("https://donneespubliques.meteofrance.fr", [RelativePath = [Chemin Fichier]]))

image

Traiter le fichier

Les fichiers récupérés sont des CSV compressés en ZIP. Il faut les décompresser pour ensuite les traiter. Pour cela on utilise l’instruction Binary.Decompress (je pense que le nom est assez explicite). Une fois le fichier lisible, on le traite comme un CSV et on remonte la première ligne en en-tête.

Tout ce processus doit être encapsulé dans une fonction pour pouvoir être appelé pour chaque ligne de notre première requête :

(ZIPFile) =>
let
    Source = Binary.Decompress(ZIPFile, Compression.GZip),
    Read = Csv.Document(Source, [Delimiter=";", Encoding=1252, QuoteStyle=QuoteStyle.None]),
    Header = Table.PromoteHeaders(Read, [PromoteAllScalars=true])
in
    Header

On appelle la fonction Unzip histoire de rester original.

image

Appeler la fonction pour chaque fichier

On a la liste des fichiers (téléchargés en binaire) et une fonction pour les lire, il ne reste plus qu’à utiliser tout ça.

Dans la première requête, on crée une nouvelle colonne en appelant une fonction personnalisée : la fonction Unzip crée précédemment ! La fonction prend en paramètre la colonne des fichiers en binaire.

image

On peut à présent développer la nouvelle colonne pour récupérer le contenu de chaque fichier dans la même table.

image

Le script complet de la requête PowerQuery est :

let
    Source = List.Generate(() > 0, each _ <= 36, each _ + 1),
    #"Converti en table" = Table.FromList(Source, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
    #"Colonnes renommées" = Table.RenameColumns(#"Converti en table",Column1),
    #"Code Mois ajouté" = Table.AddColumn(#"Colonnes renommées", "Code Mois", each DateTime.ToText(Date.AddMonths(Date.EndOfMonth(DateTime.LocalNow()), 0 - [Offset]), "yyyyMM")),
    #"Chemin Fichier ajouté" = Table.AddColumn(#"Code Mois ajouté", "Chemin Fichier", each "donnees_libres/Txt/Synop/Archive/synop." & [Code Mois] &".csv.gz"),
    #"Fichier ajouté" = Table.AddColumn(#"Chemin Fichier ajouté", "Fichier", each Web.Contents("https://donneespubliques.meteofrance.fr", [RelativePath = [Chemin Fichier]])),
    #"Fonction personnalisée appelée" = Table.AddColumn(#"Fichier ajouté", "Unzip", each Unzip([Fichier])),
    #"Autres colonnes supprimées" = Table.SelectColumns(#"Fonction personnalisée appelée",{"Unzip"}),
    #"Unzip développé" = Table.ExpandTableColumn(#"Autres colonnes supprimées", "Unzip", {"numer_sta", "date", "pmer", "tend", "cod_tend", "dd", "ff", "t", "td", "u", "vv", "ww", "w1", "w2", "n", "nbas", "hbas", "cl", "cm", "ch", "pres", "niv_bar", "geop", "tend24", "tn12", "tn24", "tx12", "tx24", "tminsol", "sw", "tw", "raf10", "rafper", "per", "etat_sol", "ht_neige", "ssfrai", "perssfrai", "rr1", "rr3", "rr6", "rr12", "rr24", "phenspe1", "phenspe2", "phenspe3", "phenspe4", "nnuage1", "ctype1", "hnuage1", "nnuage2", "ctype2", "hnuage2", "nnuage3", "ctype3", "hnuage3", "nnuage4", "ctype4", "hnuage4", ""}, {"numer_sta", "date", "pmer", "tend", "cod_tend", "dd", "ff", "t", "td", "u", "vv", "ww", "w1", "w2", "n", "nbas", "hbas", "cl", "cm", "ch", "pres", "niv_bar", "geop", "tend24", "tn12", "tn24", "tx12", "tx24", "tminsol", "sw", "tw", "raf10", "rafper", "per", "etat_sol", "ht_neige", "ssfrai", "perssfrai", "rr1", "rr3", "rr6", "rr12", "rr24", "phenspe1", "phenspe2", "phenspe3", "phenspe4", "nnuage1", "ctype1", "hnuage1", "nnuage2", "ctype2", "hnuage2", "nnuage3", "ctype3", "hnuage3", "nnuage4", "ctype4", "hnuage4", "Colonne1"})
in
    #"Unzip développé"

La même en mieux

Si on souhaite améliorer la requête et se conformer aux bonnes pratiques :

= Table.AddColumn(#"Personnalisée ajoutée", "Fichier", each Web.Contents(#"Base Uri", [RelativePath = Text.Replace(#"Complement Uri", "<mois>", [Code Mois])]))

image