Recentemente abbiamo parlato dell'enorme successo di Python all'interno della community di sviluppatori open source. Oggi invece vogliamo parlarvi di una libreria Python recentemente rilasciata: Camelot. Essa si occupa di estrarre i dati presenti in una tabella all'interno di un documento PDF.
PDF (Portable Document Format) è nato in seno al The Camelot Project, che è partito dal formato PostScript per creare quello che oggi è il formato più diffuso per i documenti online. Nasce infatti come soluzione universale per condividere documenti tra più sistemi operativi senza problemi di compatibilità, rendering di font e formattazione del testo. Senza considerare anche il pieno supporto ai sistemi di stampa moderni. Di base PDF incapsula i vari componenti del documento per creare un file che sia perfettamente leggibile e stampabile nel medesimo modo ovunque.
Il file PDF infatti va a definire in modo netto le istruzioni di visualizzazione ed il posizionamento dei caratteri del testo tramite un sistema di coordinate molto precise. Lo stesso vale per le immagini e per le tabelle. Infatti è possibile realizzare senza problemi dei fogli elettronici anche in PDF, così che la formattazione delle tabelle ed il loro contenuto non venga stravolto da terminale a terminale. Tuttavia il file PDF non ha la rappresentazione interna della struttura di una tabella, ecco perché per i tool è difficile estrarre automaticamente dati da esse per poi analizzarli.
Camelot è nato proprio per risolvere tale problematica. Questa libreria Python open source dispone di una comoda interfaccia da shell in grado di estrarre automaticamente i dati intrappolati nelle tabelle dei file PDF. Camelot ha a disposizione due metodi di parsing dei dati chiamati Stream e Lattice.
Stream è utilizzato per eseguire il parsing delle tabelle che presentano spazi bianchi tra le celle, in sostanza va ad analizzare questi spazi tra il testo per formare una simulazione della struttura delle tabelle. Stream è stato concepito usando come base la funzionalità di PDFMinr che raggruppa i caratteri di una pagina in parole e frasi usando i margini. Dopo aver ottenuto le parole su una pagina, esse vengono raggruppate in righe in base alle loro coordinate "y". Successivamente Stream tenta di indovinare il numero di colonne che la tabella potrebbe contenere, calcolando il numero di parole in ogni riga.
Di default Stream tratta l'intera pagina del documento PDF come se fosse un'unica tabella, questa soluzione non è il massimo quando si ha a che fare con più tabelle nella stessa pagina con differente numeri di colonne. Il rilevamento automatico di più tabelle per pagina è una feature che però dovrebbe essere implementata nel prossimo futuro.
Lattice invece ha un impostazione più "deterministica" e non basa il proprio lavoro su stime o supposizioni come Stream. Lattice può essere usato per analizzare tabelle che hanno linee demarcate tra le celle, inoltre è capace di analizzare automaticamente più tabelle presenti su una pagina. Lattice inizia il processo di parsing convertendo la pagina PDF in un'immagine tramite ghostscript e dopo averla elaborata ottiene i segmenti di linee orizzontali e verticali applicando una serie di trasformazioni morfologiche (erosione e dilatazione) sfruttando OpenCV.
Installare Camelot è molto semplice, ad esempio si può usare il package manager pip:
sudo apt-get install python3-tk ghostscript
pip install camelot-py
Stesso discorso per quanto riguarda il suo utilizzo:
import camelot
tables = camelot.read_pdf('documentodiprova.pdf')
tables
A seguito di tali comandi otterremo una Tablelist, da questo oggetto sarà possibile ottenere i dati che ci interessano. Possiamo quindi leggere i dati estratti in questo modo:
tables[0].df
Oppure estrarli nel formato che preferiamo:
tables[0].to_csv('documentodiprova.csv')
tables[0].to_html('documentodiprova.html')
tables[0].to_excel('documentodiprova.excel')
tables[0].to_json('documentodiprova.json')
Nel caso stessimo operando con un PDF su più pagine sarà possibile specificare quali pagine andare a leggere:
camelot.read_pdf('documentodiprova.pdf', pages='10,12,14')