martedì 22 settembre 2020

Verbali consiglio

 Rendere ricercabile il XXI secolo.

da: http://blog.ondata.it/rendere-ricercabile-il-xxi-secolo/

Uno degli ultimi post del blog di Internet Archive ha come titolo “Can You Help us Make the 19th Century Searchable?“.
In questo si sottolineano le difficoltà nel rendere ricercabili testi ad esempio del 1850, per i quali l’unico modo per farlo è utilizzare il riconoscimento ottico dei caratteri (OCR). Su testi del XIX secolo purtroppo non funziona sempre bene.
Un testo del XXI secolo nasce invece già pronto alle ricerche testuali, ma purtroppo alle volte viene trasformato in non accessibile, con diverse barriere alla sua fruizione. E avviene anche con file prodotti dalla Pubblica Amministrazione, con decreti legge, pubblicazioni in albo pretorio, verbali, ecc..
Il 4 settembre 2020 sono stati pubblicati i verbali delle riunioni del “Comitato Tecnico Scientifico (CTS) con competenza di consulenza e supporto alle attività di coordinamento per il superamento dell’emergenza epidemiologica dovuta alla diffusione del Coronavirus“: sono ad oggi 95 file in formato PDF, molti di questi sono stati pubblicati come raccolte immagini, non è possibile pertanto effettuare ricerche testuali e presentano delle barriere di accessibilità e fruizione.
Fabio Pietrosanti ha notato la cosa e li ha resi accessibili, grazie (qui i riferimenti).
Le normative vigenti (da tempo) indicano che la PA non deve pubblicare documenti in queste modalità. Ma sopratutto a nostro avviso è essenziale e propedeutico che qualsiasi documento che abbia a che fare con scelte che hanno un impatto sulla vita delle personenon debba contenere barriere.

************************************

metodo  ($ pip install pdfminer.six) 

https://stackoverflow.com/questions/26494211/extracting-text-from-a-pdf-file-using-pdfminer-in-python/61857301#61857301

**********************************

from pdfminer.high_level import extract_text

text = extract_text('report.pdf')

text_file = open("sample.txt", "w")

n = text_file.write(text)

text_file.close()




risultato:


sabato 16 marzo 2019

scraping dei documenti pdf dall'Albo Pretorio tipo Maggioli con google sheets

Analizzando con Ispeziona Elemento una pagina d'arrivo della comunicazione dell'Albo Pretorio, si osservano le seguenti caratteristiche.



l'XPath per arrivare alla tabella dei documenti è:
//*[@id='p_p_id_jcitygovalbopubblicazioni_WAR_jcitygovalbiportlet_']/div/div[2]/div/div[2]/table/tr

gli apici singoli servono a non far confondere googlesheet

l'XPath qui sopra serve a listare le intestazioni della tabella:



TitoloDescrizioneScarica
det_00262_15-03-2019.pdf - originale pdfDocumento principale
parere tecnico - firma digitaleAllegato parte integrante
Attestazione e regolarità digitale - firma digitaleAllegato parte integrante
Determinazione n. 00262/2019Allegato parte integrante

per listare invece i link a ciascun documento occorre tenere presente che l'Xpath
//*[@id='p_p_id_jcitygovalbopubblicazioni_WAR_jcitygovalbiportlet_']/div/div[2]/div/div[2]/table/tr[2]/td[3]/a/@href
genererà due link , uno per il doc firmato e uno per quello non firmato


per passare da un doc all'altro cambia il tr[XX], ma il td resta sempre il terzo td[3]
alcuni doc hanno un solo doc, altri due: firmato oppure no
il secondo è quello non firmato, m l'id dei due è lo stesso
nell'Xpath non bisogna mettere il livello tbody del table, anche se IspezionaElemento include tbody
nell'Xpath id usare apici singoli, i doppi servono a importxml di google

il fatto che siano 4, è riportato nella pagina dell'Albo Pretorio di partenza , xpath: //td[5]
=IMPORTxML(A1;"//td[5]")
dove A1 era al solito:
http://crema.trasparenza-valutazione-merito.it/web/trasparenza/papca-ap/-/papca/igrid/1489180872/1489180071

i 4 doc si estraggono usando come _query di importxml queste:
//*[@id='p_p_id_jcitygovalbopubblicazioni_WAR_jcitygovalbiportlet_']/div/div[2]/div/div[2]/table/tr[2]/td[3]/a/@href
//*[@id='p_p_id_jcitygovalbopubblicazioni_WAR_jcitygovalbiportlet_']/div/div[2]/div/div[2]/table/tr[3]/td[3]/a/@href
//*[@id='p_p_id_jcitygovalbopubblicazioni_WAR_jcitygovalbiportlet_']/div/div[2]/div/div[2]/table/tr[4]/td[3]/a/@href
//*[@id='p_p_id_jcitygovalbopubblicazioni_WAR_jcitygovalbiportlet_']/div/div[2]/div/div[2]/table/tr[5]/td[3]/a/@href

questi xpath generano 7 link a pdf: il primo un solo link, gli altri due ciascuno.

Si osserva che i link sono molto lunghi, ma differiscono solo in due posizioni, utile a sapersi per automatizzare lo scraping:

  • dove c'è scritto false come valore di signed (dal carattere 308)
  • dove c'è l'identificativo del documento (lo stesso per signed e non signed) : dal carattere 367 se c'era true o dal carattere 368 se c'era false:
=STRINGA.ESTRAI(F15;SE(H15="firmato";367;368);7)
o meglio:
=STRINGA.ESTRAI(F13;SE(STRINGA.ESTRAI(F13;308;5)="true&";367;368);7)
dove F13 contiene il link al documento e viene restituito l'id del documento:

http://crema.trasparenza-valutazione-merito.it/web/trasparenza/papca-ap?p_p_id=jcitygovalbopubblicazioni_WAR_jcitygovalbiportlet&p_p_lifecycle=2&p_p_state=pop_up&p_p_mode=view&p_p_resource_id=downloadAllegato&p_p_cacheability=cacheLevelPage&_jcitygovalbopubblicazioni_WAR_jcitygovalbiportlet_downloadSigned=true&_jcitygovalbopubblicazioni_WAR_jcitygovalbiportlet_id=7761769&_jcitygovalbopubblicazioni_WAR_jcitygovalbiportlet_action=mostraDettaglio&_jcitygovalbopubblicazioni_WAR_jcitygovalbiportlet_fromAction=recuperaDettaglio7761769
http://crema.trasparenza-valutazione-merito.it/web/trasparenza/papca-ap?p_p_id=jcitygovalbopubblicazioni_WAR_jcitygovalbiportlet&p_p_lifecycle=2&p_p_state=pop_up&p_p_mode=view&p_p_resource_id=downloadAllegato&p_p_cacheability=cacheLevelPage&_jcitygovalbopubblicazioni_WAR_jcitygovalbiportlet_downloadSigned=true&_jcitygovalbopubblicazioni_WAR_jcitygovalbiportlet_id=7761770&_jcitygovalbopubblicazioni_WAR_jcitygovalbiportlet_action=mostraDettaglio&_jcitygovalbopubblicazioni_WAR_jcitygovalbiportlet_fromAction=recuperaDettaglio7761770
http://crema.trasparenza-valutazione-merito.it/web/trasparenza/papca-ap?p_p_id=jcitygovalbopubblicazioni_WAR_jcitygovalbiportlet&p_p_lifecycle=2&p_p_state=pop_up&p_p_mode=view&p_p_resource_id=downloadAllegato&p_p_cacheability=cacheLevelPage&_jcitygovalbopubblicazioni_WAR_jcitygovalbiportlet_downloadSigned=false&_jcitygovalbopubblicazioni_WAR_jcitygovalbiportlet_id=7761770&_jcitygovalbopubblicazioni_WAR_jcitygovalbiportlet_action=mostraDettaglio&_jcitygovalbopubblicazioni_WAR_jcitygovalbiportlet_fromAction=recuperaDettaglio7761770
http://crema.trasparenza-valutazione-merito.it/web/trasparenza/papca-ap?p_p_id=jcitygovalbopubblicazioni_WAR_jcitygovalbiportlet&p_p_lifecycle=2&p_p_state=pop_up&p_p_mode=view&p_p_resource_id=downloadAllegato&p_p_cacheability=cacheLevelPage&_jcitygovalbopubblicazioni_WAR_jcitygovalbiportlet_downloadSigned=true&_jcitygovalbopubblicazioni_WAR_jcitygovalbiportlet_id=7761771&_jcitygovalbopubblicazioni_WAR_jcitygovalbiportlet_action=mostraDettaglio&_jcitygovalbopubblicazioni_WAR_jcitygovalbiportlet_fromAction=recuperaDettaglio7761771
http://crema.trasparenza-valutazione-merito.it/web/trasparenza/papca-ap?p_p_id=jcitygovalbopubblicazioni_WAR_jcitygovalbiportlet&p_p_lifecycle=2&p_p_state=pop_up&p_p_mode=view&p_p_resource_id=downloadAllegato&p_p_cacheability=cacheLevelPage&_jcitygovalbopubblicazioni_WAR_jcitygovalbiportlet_downloadSigned=false&_jcitygovalbopubblicazioni_WAR_jcitygovalbiportlet_id=7761771&_jcitygovalbopubblicazioni_WAR_jcitygovalbiportlet_action=mostraDettaglio&_jcitygovalbopubblicazioni_WAR_jcitygovalbiportlet_fromAction=recuperaDettaglio7761771
http://crema.trasparenza-valutazione-merito.it/web/trasparenza/papca-ap?p_p_id=jcitygovalbopubblicazioni_WAR_jcitygovalbiportlet&p_p_lifecycle=2&p_p_state=pop_up&p_p_mode=view&p_p_resource_id=downloadAllegato&p_p_cacheability=cacheLevelPage&_jcitygovalbopubblicazioni_WAR_jcitygovalbiportlet_downloadSigned=true&_jcitygovalbopubblicazioni_WAR_jcitygovalbiportlet_id=7761772&_jcitygovalbopubblicazioni_WAR_jcitygovalbiportlet_action=mostraDettaglio&_jcitygovalbopubblicazioni_WAR_jcitygovalbiportlet_fromAction=recuperaDettaglio7761772
http://crema.trasparenza-valutazione-merito.it/web/trasparenza/papca-ap?p_p_id=jcitygovalbopubblicazioni_WAR_jcitygovalbiportlet&p_p_lifecycle=2&p_p_state=pop_up&p_p_mode=view&p_p_resource_id=downloadAllegato&p_p_cacheability=cacheLevelPage&_jcitygovalbopubblicazioni_WAR_jcitygovalbiportlet_downloadSigned=false&_jcitygovalbopubblicazioni_WAR_jcitygovalbiportlet_id=7761772&_jcitygovalbopubblicazioni_WAR_jcitygovalbiportlet_action=mostraDettaglio&_jcitygovalbopubblicazioni_WAR_jcitygovalbiportlet_fromAction=recuperaDettaglio7761772

link al gsheet delle prove: 



venerdì 15 marzo 2019

da Albo Pretorio a AlboPOP a Facebook Page

Questo post descrive come estrarre informazioni dall'Albo Pretorio di Crema (formato "Maggioli") per ottenere un AlboPOP (RSS feed in formato specificato dalla Comunità di AlboPOP) e successivamente, sfruttando IFTTT, caricare le notizie da RSS in una pagina Facebook apposita.

Passo 1: da Albo Pretorio a AlboPOP

Partenza: video tutorial : dove si spiegano i concetti fondamentali



Il foglio googlesheet coi campi riempiti partendo dall'Albo "tipo Maggioli".


Lo script integrato nel foglio (Copyright 2016 Matteo Fortini) ci dice cosa serve:

/* This script assumes to use a spreadsheet with this form:
 *
 * - A sheet named "clean" in which there are three columns, with a header in row 1:
 *   - TITLE: the title of the record
 *   - PUBDATE: the publication date in a format parseable by JavaScript's Date() function
 *   - HREF: the link to the news
 * - A sheet named "meta" in which:
 *   - cell G1 will be used as the RSS feed title
 *   - cell G2 will be used as the RSS feed URL
 *   - cell G3 will be used as the RSS feed description
 *
 * The ID for the spreadsheet is a code that can be extracted from GDrive URL, e.g.
 * for the URL https://docs.google.com/spreadsheets/d/1VgASeOEyGKvpvMa8yWHBWsobgQw4hUK42xtMQvlO7Po/edit
 * the ID is 1VgASeOEyGKvpvMa8yWHBWsobgQw4hUK42xtMQvlO7Po

 */

QUINDI: ci sono 3 fogli.
Il primo ("raccolta"), non toccare, serve a caricare dall'Albo Pretorio le informazioni. Userà la cella B1 del terzo foglio come indirizzo dove andare a prendere le informazioni.
Il secondo ("clean") è compilato automaticamente dal gsheet
Il terzo ("meta") va compilato a mano inserendo le informazioni relative al Comune.

Nel terzo foglio bisogna semplicemente mettere nella casella B1 l'URL dell'Albo Pretorio.
Compilare poi i campi della colonna G (latitudine, longitudine e il codice istat per G12 si trovano su wikipedia , pagina del Comune), tranne che per il campo G2 dove occorre prima andare allo script.
Menu Strumenti=> Editor di Script
Nello script occorre 
1) sostituire nella riga 38 il codice identificativo con quello del googlesheet in uso (occorre copiarlo dalla casella in alto nel browser, dall'URL della pagina aperta, come spiegato nel video):

ID_SPREADSHEET="1T1twNaCm1iFoy92StwmZpdpcz5tpeqIROXQafWOXuJM";

2) Menu Pubblica => Distribuisci Come Applicazione Web : copiare l'URL proposto, tornare nel Foglio e incollarlo nella casella G2 del foglio metaQUESTO URL E' IL FEED RSS , che bisognerà poi copiare , nel passo 2 (vedi sotto), nella casella del trigger della applet IFTTT, e che si comunica alla Community di AlboPOP se si desidera includere l'AlboPOP (RSS) nella lista dei Comuni seguiti.


Passo 2: da AlboPOP a pagina Facebook
Creare la pagina Facebook dell'AlboPOP.
Tenere aperto facebook sulla nuova pagina (in cui verranno postate in automatico le comunicazioni dell'Albo Pretorio).

Andare su IFTTT, registrarsi e creare una applet (NewApplet) scegliendo come innesco (cliccare IF this)  FEED RSS, poi "new  feed item" e incollare  l'URL del RSS copiato sopra   e poi create trigger e come azione (THEN THAT)  FacebookPage.
Aggiungere ingrediente al campo  linkURL cliccando  EntryURL
Aggiungere nel campo messaggio l'EntryTitle e altre info a piacere che si vogliono vedere nel post.




salvare (SAVE) la applet (che sarà attiva , ON, verde).

domenica 9 dicembre 2018

farsi mandare una email dall'Albo Pretorio di Crema



per scrapare albo pretorio Maggioli con google sheet fondamentalmente si usa <td>


le formule useranno ImportXML:


andare nel proprio drive : https://drive.google.com/drive/my-drive
cliccare Nuovo e poi Fogli Google
dare un titolo

quindi mettere nelle caselle i seguenti import (con l'uguale = davanti) :
cella A1:          http://crema.trasparenza-valutazione-merito.it/web/trasparenza/papca-ap/-/papca/igrid/1489180872/1489180071
cella A3:                                                                   =IMPORTxML(A1;"//td[1]")
cella B3 (occupa 3 colonne B-C-D): =IMPORTxML(A1;"//td[2]")
cella E3: =IMPORTxML(A1;"//td[3]")
cella F3(occupa 3 colonne F-G-H): =IMPORTxML(A1;"//td[4]")
cella I3: =IMPORTxML(A1;"//td[5]")
cella K3: =IMPORTxML(A1;"//td[5]/a[1]/@href")

la cella k3 riporta il link, per cui @href all'interno del primo dei tag <a> dopo il quinto <td>



l'aggiornamento automatico del foglio si otterrà così, nelle impostazioni del menu file:


A questo punto il foglio si aggiorna da solo, ma occorrerebbe tutte le volte entrare in drive per leggere l'albo pretorio. Meglio farsi spedire il foglio via email.

Occorre inserire uno script che manda via email le righe. (Strumenti ==> edit script).
Mettere l'indirizzo email destinazione nella casella K1 dl foglio perchè sarà lì che andrà a prelevarla lo script.
Cominciamo da una riga per prova:

/**
 * manda la mail all'indirizzo contenuto in una certa casella.
 */

function sendEmailToMe() {
  var sheet = SpreadsheetApp.getActiveSheet();
  var startRow = 1; // prima riga
  var numRows = 20; // numero righe da processare
  
  var dataRange = sheet.getRange(startRow, 1, numRows, 12);
  // importa il range.
  var data = dataRange.getValues();
  
  
  var emailAddress = data[0][10]; // casella 1,11 a.k.a. K1 contiene indirizzo
  
  for (i in data) {
    var row = data[i];
    var message = row; // riga = contenuto di tutte le celle
    var subject = 'Sending emails from a Spreadsheet';
    
  }
  MailApp.sendEmail(emailAddress, subject, message);
}

Run lo script : la prima volta Google chiederà di essere autorizzato , rispondere autorizzando ciò che chiede. Lanciare e verificare nella propria posta che arrivi il messaggio.
Passiamo a tutte le righe (Maggioli di solito 20) :

/**
 * manda la mail all'indirizzo contenuto in una certa casella.
 */

function sendEmailToMe() {
  var sheet = SpreadsheetApp.getActiveSheet();
  var startRow = 1; // prima riga
  var numRows = 20; // numero righe da processare
  
  var dataRange = sheet.getRange(startRow, 1, numRows, 12);
  // importa il range.
  var data = dataRange.getValues();
  
  
  var emailAddress = data[0][10]; // casella 1,11 a.k.a. K1 contiene indirizzo
  var subject = 'Sending emails from a Spreadsheet';
  var message = "";
  for (i in data) {
    var row = data[i];
    message += row; // riga = contenuto di tutte le celle
    message += " \n"; // vado a capo
       
  }
  MailApp.sendEmail(emailAddress, subject, message);
}

Vogliamo che la cosa sia automatica.
Bisogna decidere ogni quanto far spedire la email dallo spreadsheet, cioè ogni quanto attivare spedisci email (il trigger cioè attivatore)


aggiungere "attivatore" cliccando + blu

scegliere attivatore basato su orario
se si mette ogni ora, verrà spedita ogni ora. Oppure ogni giorno ad una certa ora:
Salvare.

**************************************************************
Se il messaggio nel  formato Maggioli non va bene, si può fare un secondo foglio nel file Spreadsheet organizzandolo a piacere (con le info essenziali ad esempio, si aggiorneranno anch'esse automaticamente) e farsi spedire quello.
Ecco lo script modificato che mostra come saltare dal primo foglio (quello con l'indirizzo email in casella K1) al secondo foglio (quello coi dati da mettere nel messaggio della email):

function sendEmailToMe() {
  
/**
 * manda la mail all'indirizzo contenuto in una certa casella.
 */
  
  var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  
  SpreadsheetApp.setActiveSheet(spreadsheet.getSheets()[0]); // primo FOGLIO
  var sheet = SpreadsheetApp.getActiveSheet();
  var startRow = 1; // prima riga
  var numRows = 1; // numero righe da processare
  
  var dataRange = sheet.getRange(startRow, 1, numRows, 12);
  // importa il range fino a 12 colonne.
  var data = dataRange.getValues();
  
  
  var emailAddress = data[0][10]; // casella arancione, nota bene 1,11 a.k.a. K1 , contiene indirizzo
  var subject = 'Albo Pretorio Crema oggi';
  var message = "";
  
  // passo al secondo foglio
    
  SpreadsheetApp.setActiveSheet(spreadsheet.getSheets()[1]); // SECONDO FOGLIO
  sheet = SpreadsheetApp.getActiveSheet();
  numRows = 20; // numero righe da processare
  dataRange = sheet.getRange(startRow, 1, numRows, 12);
  // importa il range.
  var data2 = dataRange.getValues();
  
  for (i in data2) {
    var row = data2[i];
    message += row; // aggiungo a message una riga = contenuto di tutte le celle della riga 
    message += " \n\n\n"; // a capo 3 volte per separare
  }
  
  // mando email con dentro message
  MailApp.sendEmail(emailAddress, subject, message);
  
}

Giocandoci ci si può far spedire solo righe con contenuti che interessano ad esempio.

Variante con lista di email nel terzo foglio , lista anche compilabile da cosiddetto "Modulo Google".
Invece che leggere dalla casella K1, legge da una lista di email nel terzo foglio.


lo script diventa (okkio che la prima colonna ce la mette il modulo automativamente e va saltata nel leggere gli indirizzi a cui destinare le email):

/**
 * manda la mail agli indirizzi caricati con un Modulo, foglio spostato in terza posizione.
 */

function sendEmailToList() {
  var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = SpreadsheetApp.getActiveSheet();
  SpreadsheetApp.setActiveSheet(spreadsheet.getSheets()[0]); // primo FOGLIO

  var startRow = 1; // prima riga
  var numRows = 1; // numero righe da processare
  
  var dataRange = sheet.getRange(startRow, 1, numRows, 12);
  // importa il range fino a 12 colonne.
  var data = dataRange.getValues();
  
  
  var emailAddress = data[0][10]; // casella 1,11 a.k.a. K1 contiene indirizzo
  var subject = 'Albo Pretorio - Crema ultime 20 notizie a Lista Iscritti da lucasc';
  var message = "";
  
  // passo al secondo foglio
    
  SpreadsheetApp.setActiveSheet(spreadsheet.getSheets()[1]); // SECONDO FOGLIO
  sheet = SpreadsheetApp.getActiveSheet();
  numRows = 20; // numero righe da processare
  dataRange = sheet.getRange(startRow, 1, numRows, 12);
  // importa il range.
  var data2 = dataRange.getValues();
  
  for (i in data2) {
    var row = data2[i];
    message += row; // riga = contenuto di tutte le celle
    message += " \n\n\n"; // a capo
  }
  
  // passo al  foglio del modulo , 
    
  SpreadsheetApp.setActiveSheet(spreadsheet.getSheets()[2]); // attiva terzo FOGLIO, collegato al Modulo
  sheet = SpreadsheetApp.getActiveSheet();
  numRows = sheet.getLastRow() -1; // numero email  da processare
  startRow = 2; // prima riga, salta riga intestazioni
  dataRange = sheet.getRange(startRow, 1, numRows, 2); // ultima riga è vuota, da qui -1
  // importa il range.
  var data3 = dataRange.getValues();
  for (i in data3) {
    emailAddress = data3[i][1]; // salta intestazioni Modulo google, salta colonna data registrazione
    
    MailApp.sendEmail(emailAddress, subject, message);
    Logger.log(i);  // per controllare e debuggare : menu Visualizza -> log
    Logger.log('indirizzo: ' + data3[i][1]);
  }
}

Ovviamente funziona anche come prima (un indirizzo destinatario)  mettendo a mano un indirizzo nella prima casella al posto di pippo.@libero.it, che diventa un caso particolare monoindirizzo. Se la spedizione non va a buon fine (es: indirizzo inesistente), viene recapitata una email al mittente con la segnalazione.

Link al foglio google finale, che ho condiviso:
https://docs.google.com/spreadsheets/d/124hFSaAgbLur57aBQ0c04ZfRTA54flFd8fbW6J4hWIY/edit?usp=sharing