Home
Destreggiarsi in un flusso di dati con Python

21 Novembre 2019

Destreggiarsi in un flusso di dati con Python

di

A chiunque programmi capita di trovarsi a che fare con un flusso ingente di dati che non è possibile scartare immediatamente e vanno salvati, per essere poi organizzati e ripuliti al meglio. Il linguaggio Python contiene strumenti utili a questo scopo.

Il problema: un incessante flusso di dati

Molti sistemi generano una serie continua di file di dati. Possono essere file di log prodotti da un server di commercio elettronico o da un normale processo; feed di informazioni sui prodotti inviati da un server; feed automatizzati di elementi per la pubblicità online; dati storici su scambi commerciali eccetera. Spesso si tratta di normali file di testo, non compressi, contenenti dati grezzi ma con un valore potenziale, che quindi non è possibile semplicemente eliminare a fine giornata. I file si accumulano giorno per giorno fino al punto che gestirli manualmente diventa impossibile, per non parlare dello spazio che occupano.

Lo scenario: un feed di prodotti, in arrivo da chissà dove

Una situazione tipica che ho incontrato è rappresentata dai feed quotidiani di dati sui prodotti. Consideriamo l’esempio di un feed di prodotti che provenga da un fornitore. Il file arriva una volta al giorno con una riga per ciascun articolo fornito dall’azienda. Ogni riga prevede dei campi con il codice del prodotto del fornitore (SKU, Stock-Keeping Unit); una breve descrizione dell’articolo, il costo dell’articolo, le dimensioni e il peso; lo stato dell’articolo (disponibile o in ordine, per esempio) e probabilmente varie altre cose, a seconda del tipo di articolo.

Oltre a questo semplice file informativo, potremmo ottenere anche altri file legati ai prodotti, con informazioni più dettagliate o anche qualcosa di differente. In tal caso, finiremo per avere molti file, tutti con lo stesso nome, che arrivano quotidianamente e planano tutti nella stessa directory per poter essere elaborati.

Ora supponiamo di ricevere ogni giorno tre file correlati: item_info.txt, item_attributes.txt e related_items.txt. Questi tre file arrivano e devono essere elaborati quotidianamente. Se l’elaborazione è l’unico requisito, non dovremo preoccuparci più di tanto; possiamo anche lasciare che i file di una giornata sostituiscano i precedenti. Ma che cosa accade se non possiamo sbarazzarci dei vecchi file? Potremmo aver bisogno di conservare i dati grezzi nel caso in cui vi fosse una verifica sull’accuratezza delle operazioni. Oppure potremmo aver bisogno di registrare le variazioni nei dati nel corso del tempo. Qualsiasi sia il motivo, la necessità di conservare i file ci obbliga a elaborarli in qualche modo.

L’operazione più semplice che possiamo fare è contrassegnare i file con la data di ricezione e poi trasferirli in una cartella di archiviazione. In tal modo, ciascun nuovo set di file potrà essere ricevuto, elaborato, rinominato e poi spostato in modo che l’intero processo possa ripetersi senza perdere mai nulla.

Dopo alcune ripetizioni, la struttura di directory potrebbe avere il seguente aspetto:

working/← Directory di lavoro principale, con i file pronti per l’elaborazione
item_info.txt
item_attributes.txt
related_items.txt
archive/← Subdirectory per l’archiviazione dei file elaborati
item_info_2017-09-15.txt
item_attributes_2017-09-15.txt
related_items_2017-09-15.txt
item_info_2016-07-16.txt
item_attributes_2017-09-16.txt
related_items_2017-09-16.txt
item_info_2017-09-17.txt
item_attributes_2017-09-17.txt
related_items_2017-09-17.txt

Riflettiamo sui passi necessari per svolgere questa operazione. Innanzitutto dobbiamo rinominare i file, in modo da aggiungere la data attuale al nome del file. Per farlo, dobbiamo ottenere il nome dei file da rinominare e poi eliminare l’estensione. Eseguita questa operazione, dobbiamo aggiungere una stringa che si basa sulla data corrente, concatenare di nuovo l’estensione e quindi applicare il cambio di nome al file, per spostarlo infine nella directory di archiviazione.

Possiamo ottenere il nome di un file in vari modi. Se siamo sicuri che i nomi siano esattamente identici e non vi sono molti file, potremmo anche pensare di scriverli manualmente nello script. Un modo più sicuro, tuttavia, è utilizzare i moduli pathlib e glob dell’oggetto che definisce il percorso, nel seguente modo:

>>> import pathlib
>>> cur_path = pathlib.Path(".")
>>> FILE_PATTERN = "*.txt"
>>> path_list = cur_path.glob(FILE_PATTERN)
>>> print(list(path_list))
[PosixPath('item_attributes.txt'), PosixPath('related_items.txt'),
 PosixPath('item_info.txt')]

Ora possiamo considerare i percorsi individuati da FILE_PATTERN e applicare le modifiche richieste. Ricordiamo che bisogna concatenare la data al nome di ciascun file e poi spostare i file rinominati in una cartella di archiviazione. Utilizzando pathlib, l’intera operazione può avere il seguente aspetto.

import datetime
import pathlib
FILE_PATTERN = "*.txt"← Imposta il pattern per individuare i file e la directory di archiviazione
ARCHIVE = "archive"← La directory “archive” deve esistere perché questo codice funzioni
if __name__ == '__main__':
date_string = datetime.date.today().strftime("%Y-%m-%d")← Utilizza l’oggetto date della
libreria datetime per creare una stringa basata sulla data odierna
cur_path = pathlib.Path(".")
paths = cur_path.glob(FILE_PATTERN)
for path in paths:
new_filename = "{}_{}{}".format(path.stem, date_string, path.suffix)
new_path = cur_path.joinpath(ARCHIVE, new_filename)← Crea un nuovo percorso dal percorso corrente, per la directory di archiviazione, e poi il nuovo nome di file
path.rename(new_path)← Rinomina (e sposta) il file in un unico passo

Vale la pena di notare che gli oggetti Path semplificano molto questa operazione, perché evitano ogni forma di parsing del testo per separare il nome del file dal suo suffisso. Questa operazione è inoltre più semplice di quanto potremmo aspettarci, poiché il metodo rename in effetti è anche in grado di spostare un file, utilizzando un percorso che includa la nuova posizione.

Questo script è molto semplice e svolge l’operazione in modo efficace con poche righe di codice. Nei prossimi paragrafi considereremo situazioni più complesse.

Una organizzazione migliore

La soluzione descritta nel paragrafo precedente funziona, ma presenta alcuni svantaggi. Per esempio, mentre i file si accumulano, la loro gestione può diventare problematica, perché nel corso di un anno avremo 365 set di file correlati, tutti nella stessa directory e potremo accedere a ognuno di essi solamente osservando il loro nome. Se i file arrivano più frequentemente o se il set di file contiene un maggior numero di file, la confusione può diventare ingestibile.

Per contenere questo problema, possiamo cambiare il modo in cui archiviare i file. Invece di modificare i nomi di file per includere le date in cui sono stati ricevuti, possiamo creare una directory distinta per ciascun set di file e nominare tale directory in base alla data di ricezione. La struttura di directory potrebbe avere il seguente aspetto:

working/← Cartella di lavoro principale, con i file aggiornati, da elaborare
item_info.txt
item_attributes.txt
related_items.txt
archive/← Subdirectory principale, per l’archiviazione dei file elaborati
2016-09-15/ ←|
item_info.txt |
item_attributes.txt |
related_items.txt |Subdirectory distinte
2016-09-16/ ←|per ogni giornata
item_info.txt |di ricezione dei file
item_attributes.txt |
related_items.txt |
2016-09-17/ ←|
item_info.txt
item_attributes.txt
related_items.txt

Il vantaggio è che ciascun set di file viene raggruppato insieme. Indipendentemente da quanti set di file riceveremo e da quanti file saranno contenuti nel set, sarà facile trovare tutti i file che servono in un determinato momento.

Naturalmente, l’archiviazione dei file per subdirectory non richiede molto più lavoro rispetto alla prima soluzione. L’unico passo aggiuntivo consiste nel creare il nome della subdirectory prima di rinominare il file. Il seguente script rappresenta un modo per svolgere questa operazione.

import datetime
import pathlib
FILE_PATTERN = "*.txt"
ARCHIVE = "archive"
if __name__ == '__main__':
date_string = datetime.date.today().strftime("%Y-%m-%d")
cur_path = pathlib.Path(".")
new_path = cur_path.joinpath(ARCHIVE, date_string)
new_path.mkdir()← Notate che questa directory deve essere creata una sola volta,
prima di trasferirvi i file
paths = cur_path.glob(FILE_PATTERN)
for path in paths:
path.rename(new_path.joinpath(path.name))

Questa soluzione raggruppa i file correlati, il che semplifica la loro gestione: tutto il set di file si troverà raggruppato in una sola directory.

Risparmiare spazio su disco: compressione e potatura

Finora abbiamo svolto unicamente la gestione dei gruppi di file ricevuti. Col tempo, tuttavia, i file di dati si accumulano fino al punto in cui la quantità di spazio occupata diviene un problema. Una possibilità consiste nel comprare un disco più capiente. Consideriamo però il fatto che l’incremento di spazio in realtà non risolve il problema, semplicemente lo pospone.

Compressione dei file

Un approccio che possiamo considerare è la compressione. Vi sono vari modi per comprimere un file o un set di file, ma in generale questi metodi sono simili. In questo paragrafo considereremo l’archiviazione dei file di una giornata in un unico file ZIP. Se i file sono principalmente di testo e piuttosto estesi, il risparmio in termini di spazio può essere impressionante.

Per questo script utilizzeremo per ciascun file ZIP la stringa della data con l’estensione .zip. Con il listato precedente abbiamo creato una nuova directory nella directory di archiviazione, per arrivare a una struttura di directory come la seguente:

working/← Directory di lavoro principale, dove vengono elaborati i file correnti; 
archive/   questi file vengono poi archiviati e rimossi dopo l’elaborazione.
2016-09-15.zip|File compressi, ognuno dei quali contiene
2016-09-16.zip|i file item_info.txt, attribute_info.text
2016-09-17.zip|e related_items.txt quotidiani.

Ovviamente, per utilizzare dei file ZIP dovremo modificare alcuni dei passi che abbiamo impiegato in precedenza.

Un’importante aggiunta nel nuovo file script è l’importazione della libreria zipfile e poi il codice per creare un nuovo oggetto file ZIP nella directory di archiviazione. Dopo di ciò possiamo utilizzare l’oggetto file ZIP per scrivere i file di dati nel nuovo file .zip. Infine, poiché in realtà non spostiamo più i file, dovremo rimuovere i file originali dalla directory di lavoro. Una soluzione possibile ha il seguente aspetto.

import datetime
import pathlib
import zipfile← Importa la libreria zipfile
FILE_PATTERN = "*.txt"
ARCHIVE = "archive"
if __name__ == '__main__':
date_string = datetime.date.today().strftime("%Y-%m-%d")
cur_path = pathlib.Path(".")
paths = cur_path.glob(FILE_PATTERN)
Crea il percorso verso il file ZIP nella directory
zip_file_path = cur_path.joinpath(ARCHIVE, date_string + ".zip")← di archiviazione
zip_file = zipfile.ZipFile(str(zip_file_path), "w")← Apre il nuovo oggetto file ZIP
in modalità di scrittura; str() è necessaria per convertire un percorso in una stringa
for path in paths:
zip_file.write(str(path))← Scrive il file corrente sul file ZIP
path.unlink()← Rimuove il file corrente dalla directory di lavoro

Potatura di file

La compressione dei file di dati all’interno di archivi zipfile consente di risparmiare grandi quantità di spazio e potrebbe anche essere tutto ciò di cui abbiamo bisogno. Ma se abbiamo una grande quantità di file, oppure file che non vengono compressi poi così tanto (come nel caso delle immagini JPEG), potremmo trovarci comunque a esaurire lo spazio disponibile. Potremmo anche scoprire che i dati non cambiano molto e che quindi non è davvero necessario conservare una copia archiviata di tutti i set di dati, anche se distanti nel tempo. In pratica, anche se può essere utile conservare i dati quotidiani della settimana o del mese scorso, può essere interessante l’idea di liberare spazio rimuovendo i file meno recenti. Per i dati più vecchi di qualche mese, può essere anche accettabile conservare solo un set di file ogni settimana o magari un set al mese.

Il processo di eliminazione dei file non più necessari consiste in pratica in una sorta di potatura. Supponiamo che, dopo avere ricevuto per alcuni mesi un insieme di file di dati quotidiani che abbiamo archiviati in un file ZIP, ci venga detto di conservare solo un file alla settimana di tutti i file che sono più vecchi di un mese.

Questo semplice script di potatura rimuove tutti i file non più necessari, in questo caso tutti tranne un file alla settimana quando la loro anzianità è superiore al mese. Nella progettazione di questo script, è utile porsi due tipi di domande.

  • Dal momento che occorre salvare un file ogni settimana, è il caso di scegliere un giorno specifico della settimana?
  • Con quale frequenza deve essere eseguita questa potatura? Quotidianamente, settimanalmente o mensilmente? Se decidiamo che la potatura debba essere svolta quotidianamente, potrebbe aver senso combinare l’operazione con lo script di archiviazione. Se, al contrario, dobbiamo eseguire la potatura solo una volta alla settimana o al mese, le due operazioni dovranno trovarsi in script distinti.

Per questo esempio, per semplificare, predisporremo uno script di potatura distinto che possa essere eseguito in qualsiasi momento e che elimini i file non necessari. Inoltre, supponiamo di voler conservare solo i file ricevuti di martedì più vecchi di un mese. Ecco un esempio di script di potatura.

from datetime import datetime, timedelta
import pathlib
import zipfile
FILE_PATTERN = "*.zip"
ARCHIVE = "archive"
ARCHIVE_WEEKDAY = 1
if __name__ == '__main__':
cur_path = pathlib.Path(".")
zip_file_path = cur_path.joinpath(ARCHIVE)
paths = zip_file_path.glob(FILE_PATTERN)
current_date = datetime.today()← Estrae un oggetto datetime dalla data corrente
for path in paths:
name = path.stem← path.stem restituisce il nome del file senza l’estensione
path_date = datetime.strptime(name, "%Y-%m-%d")← strptime suddivide una stringa
in un oggetto datetime basato sulla stringa di formattazione
path_timedelta = current_date - path_date← Sottraendo una data da un’altra
si ottiene un oggetto timedelta
if path_timedelta > timedelta(days=30) and path_date.weekday() !=
ARCHIVE_WEEKDAY:← timedelta(days=30) crea un oggetto timedelta di 30 giorni;
path.unlink() il metodo weekday() restituisce un intero per il giorno
della settimana, dove il lunedì corrisponde allo 0

Il codice mostra come utilizzare insieme le librerie datetime e pathlib per potare i file in base alla data, utilizzando solo poche righe di codice. Poiché il nome dei file di archiviazione deriva dalla data in cui sono stati ricevuti i dati, è possibile utilizzare il metodo glob, estrarre il nome e utilizzare strptime per ottenere un oggetto datetime. Successivamente potremo utilizzare degli oggetti timedelta di datetime e il metodo weekday() per trovare l’età di un file e il giorno della settimana e infine rimuovere (unlink) i file che non servono più.

Che cosa abbiamo imparato

  • Il modulo pathlib può semplificare notevolmente le operazioni sui file, come la ricerca del nome e dell’estensione, l’operazione di spostamento e di ridenominazione e la ricerca tramite caratteri jolly.
  • Aumentando il numero e la complessità dei file, è importante adottare soluzioni di archiviazione automatizzate e Python offre vari modi per crearle.
  • Possiamo risparmiare grandi quantità di spazio su disco comprimendo e potando i file di dati.

Questo articolo richiama contenuti dal capitolo 20 di Python.

unsplash-logoImmagine di 云 陌

L'autore

  • Naomi Ceder
    Naomi Ceder insegna e utilizza Python dal 2001 e attualmente è presidente del consiglio di amministrazione della Python Software Foundation. Relatrice di livello internazionale su temi quali la comunità Python, l'inclusione e la diversità in campo tecnologico, dirige un team di sviluppo open source per Dick Blick Art Materials.

Iscriviti alla newsletter

Novità, promozioni e approfondimenti per imparare sempre qualcosa di nuovo

Gli argomenti che mi interessano:
Iscrivendomi dichiaro di aver preso visione dell’Informativa fornita ai sensi dell'art. 13 e 14 del Regolamento Europeo EU 679/2016.

Corsi che potrebbero interessarti

Tutti i corsi
Big-data-analytics-iniziare-bene-cover Corso Online

Big Data Analytics - Iniziare Bene

con Andrea De Mauro

Credi che i Big Data siano una grande opportunità ma che spesso se ne parli a sproposito? Excel ti sta stretto e vorresti fare di più? Andrea De Mauro ti aiuta a fare chiarezza e ti insegna a muovere i primi passi nell'analisi dei Big Data.

Content_Design-fare-una-strategia–cover Corso Online

Content Design - Fare una Strategia

con Nicola Bonora

Progettare contenuti che facciano sposare felicemente obiettivi aziendali e bisogni delle persone: un sogno? No, realtà: Nicola Bonora ti spiega quali processi, metodi e strumenti usare.

Mora-Agile_Sviluppo_e_Management-home2 Corso In aula

Agile, sviluppo e management: iniziare bene

con Fabio Mora

Non sei soddisfatto delle gestione dei tuoi progetti software? Vuoi scoprire come i metodi agili possono cambiare il tuo modo di lavorare? Il corso di Fabio Mora è quello che ti serve.


Libri che potrebbero interessarti

Tutti i libri

Python

Guida alla sintassi, alle funzionalità avanzate e all'analisi dei dati

46,65

66,89€ -30%

37,91

39,90€ -5%

26,99

di Naomi Ceder

Analisi del linguaggio con Python

Imparare a processare testo e audio con le librerie open source

23,15

32,89€ -30%

18,91

19,90€ -5%

12,99

di Serena Sensini

Imparare a programmare con Python

Il manuale per programmatori dai 13 anni in su

28,75

39,99€ -28%

23,75

25,00€ -5%

14,99

di Maurizio Boscaini


Articoli che potrebbero interessarti

Tutti gli articoli