SlideShare a Scribd company logo
1 of 40
Download to read offline
UNIVERSITÀ DEGLI STUDI DI TRIESTE
Dipartimento di Ingegneria e Architettura
Corso di laurea triennale in Ingegneria dell’Informazione
MIGRAZIONE DI UN SITO WEB MEDIANTE
TECNICA DI ESTRAZIONE DATI
AUTOMATIZZATA
Laureando: Relatore:
GIULIO ZORZOLI PROF. ING. ALBERTO BARTOLI
ANNO ACCADEMICO 2018/2019
1
Indice
Introduzione 3
Presentazione caso di studio 4
Analisi dei requisiti 5
Progettazione 6
Scelta del linguaggio di programmazione e delle librerie 6
Studio delle pagine HTML per estrarne i dati 6
File Robots.txt 6
File Sitemap 7
Pagine web da cui estrarre i dati di interesse 7
Roadmap 7
Realizzazione 8
Creazione ambiente virtuale 8
Estrazione Dati 8
Manipolazione Dati 12
Aggiunta di link 12
Differenziazione tra journal e conference 13
Aggiunta flag membri del laboratorio 14
Aggiunta delle date 14
Download/upload file allegati 15
Conversione da Json a spreadsheet 17
Creazione delle tabelle 17
Conclusioni 21
Problemi Riscontrati 21
Commento 22
Sitografia 23
Appendice 23
2
Introduzione
Il sito web del laboratorio di "Machine Learning" dell'Università degli Studi di Trieste è stato creato
con l'applicativo web “Google Sites” versione "Classic" (anno 2008).
Durante il periodo di tirocinio presso il laboratorio il candidato si è occupato della migrazione del
sito utilizzando la nuova versione di "Google Sites" chiamata "New"[14] (anno 2016).
La migrazione risulta necessaria a causa dei seguenti motivi:
• La versione "Classic" è deprecata e non sarà più supportata dai servizi Google a partire
dall'anno 2021 [17].
• Le pagine della versione "Classic" sono fornite mediante protocollo HTTP mentre quelle
della versione "New" mediante protocollo HTTPS.
• Il design delle pagine della versione "New" presenta elementi e regole CSS moderni
conferendo al sito web la proprietà di "Responsive" ed un look meno obsoleto.
Sono da rispettare i seguenti vincoli di progetto:
1. Per la realizzazione del nuovo sito web è necessario mantenere l'utilizzo della piattaforma
Google Sites.
2. Le informazioni delle pagine "Pubblications" e "News" presentano le liste degli articoli
pubblicati dai membri del laboratorio e delle notizie riguardanti il laboratorio stesso.
La migrazione non deve causare la perdita delle informazioni già esistenti. Inoltre, è
necessaria una soluzione per l'inserimento di nuove pubblicazioni o news.
3. L'unico spazio di web hosting utilizzabile è Google Drive.
Applicando i passaggi descritti nella documentazione [18] è stato constatato che non è possibile
utilizzare lo strumento di migrazione automatico fornito da Google. Dato l'elevato numero di
informazioni presenti nel sito web il candidato ha progettato e sviluppato un Web Crawler, una
soluzione software che automatizza l'estrazione dei dati analizzando il codice sorgente HTML di
una pagina web in modo programmatico.
Poichè non è possibile ospitare un database su Google Drive sorge il problema di quale strutture
dati utilizzare per memorizzare i dati estratti delle pagine "Pubblicazioni" e "News". La soluzione
proposta prevede l'integrazione del sito con Awesome Table[2], un applicativo web che genera delle
views a partire dai dati inseriti in uno spreadsheet Google.
Per raggiungere il risultato sono state affrontate le seguenti operazioni da parte del laureando:
• Studio delle tecnologie software attuali e individuazione dei linguaggi e librerie più adatti
alla funzionalità di web scraping.
• Analisi della struttura e sintassi HTML delle pagine web generate dalla piattaforma Google
Sites e individuazione delle regole Regex necessarie per la corretta estrazione delle
informazioni.
• Progettazione e scrittura del Web Crawler in Python (ver 3.6) utilizzando la libreria
BeautifulSoup [1] che facilita la navigazione e la ricerca dei dati all'interno del DOM.
• Esecuzione del programma, estrazione delle informazioni e inserimento di queste in una
struttura dati dizionario esportata in un file JSON. Creazione degli spreadsheet Google a
partire da questi dati.
• Trasferimento automatizzato delle immagini e dei file allegati mediante interrogazione delle
API [5] del servizio Google Drive.
• Creazione delle pagine statiche del nuovo sito web utilizzando elementi nativi Google Sites
New. Realizzazione delle porzioni dinamiche mediante scrittura di codice HTML, Javascript
e CSS.
3
Al termine del periodo di tirocinio del laureando il nuovo sito web risulta pronto per la produzione e
si trova al seguente indirizzo web https://sites.google.com/view/tesi-zorzoli.
Tutto il codice all'interno del presente documento è stato scritto dall'autore salvo diversamente
specificato.
Presentazione caso di studio
Il sito web del laboratorio di “Machine Learning” dell’Università degli Studi di Trieste è stato
realizzato con l’applicativo web “Google Sites Classic” (anno 2008) e utilizza Google Drive come
servizio di Web Hosting. Esso è costituito da 6 pagine web statiche principali (“Home”, “News”,
“People”, “Pubblications”, “Data and Tools”, “Student Opportunities”) che sono fornite mediante
protocollo HTTP.
Per aggiungere nuove informazioni i membri del laboratorio utilizzano gli elementi nativi Google
Sites Classic modificando la pagina web preesistente o creandone direttamente una nuova.
Le operazioni di routine consistono nella modifica del testo delle pagine statiche (p.e. aggiunta di
personale nella sezione "People") e nella creazione ex novo di pagine web nel momento di
pubblicazione di un nuovo articolo (sezione "Publications") o di una notizia inerente al laboratorio
(sezione "News").
Nell’anno 2016 l’applicativo Google Sites Classic è stato aggiornato alla versione New. I nuovi
costrutti HTML e direttive CSS conferiscono alle pagine web create un look più moderno.
Le pagine web create con questa versione sono fornite mediante protocollo HTTPS (HTTP over
TLS) sicchè la connessione TCP tra web client dell'utente e web server Google ha le proprietà di
Segretezza, Autenticazione, Integrità.
La versione Google Sites Classic risulta deprecata e le pagine web create con tale applicativo non
saranno più supportate dai servizi Google a partire dall'anno 2021 [17].
Si vuole, inoltre, che le informazioni relative alle pagine "Publications" e "News" non siano più
fornite staticamente ma dinamicamente. In questo modo è facilitato l'inserimento di nuovi dati ed è
possibile l'utilizzo di tali dati da parte di siti web di terzi.
A causa di questi motivi risulta necessario migrare il sito web del laboratorio ad una nuova
versione.
Sono da rispettare i seguenti vincoli tecnologici:
1. Sviluppo del nuovo sito web mediante utilizzo di Google Sites New.
2. Utilizzo di Google Drive come unico servizio di Web Hosting.
3. Integrazione del nuovo sito web con l'applicativo Awesome Table.
4. Utilizzo di Spreadsheet Google per alimentare le pagine web "Publications" e "News"
dinamicamente.
Il candidato nel periodo di tirocinio presso il laboratorio si è occupato dello studio, progettazione e
realizzazione di una soluzione per migrare il sito web senza perdita di informazioni e rispettando i
vincoli tecnologici sopra elencati.
4
Analisi dei requisiti
Inizialmente il laureando si è preoccupato di studiare l'ambiente Google, gli applicativi web da esso
offerti e la struttura del sito web da migrare. Applicando i passaggi descritti nella documentazione
[18] è stato constatato che non è possibile utilizzare lo strumento di migrazione automatico fornito
da Google. Tale strumento ha funzionalità limitate; non può trasferire tutti i contenuti e costrutti
utilizzati, la disposizione di testo e immagini non viene rispettata, gli aspetti grafici mantenuti non
sono soddisfacenti. Nel caso del sito in esame l'opzione "Converti nella nuova versione di Sites"
non è presente nel pannello di controllo. Risulta quindi necessaria una migrazione dei contenuti da
parte del candidato.
Il numero di articoli pubblicati ammonta a 95 e quello delle news a 43. Risulta quindi tedioso e
problematico estrarre i dati presenti in 138 pagine web e inserirli manualmente in una struttura dati.
La soluzione proposta prevede l'automazione di queste operazioni mediante la progettazione e
programmazione da parte del candidato di un Web Crawler, un programma software che scansiona
sequenzialmente il codice HTML sorgente di una pagina web e, utilizzando le espressioni regolari,
estrae i dati di interesse inserendoli in una struttura dati. In accordo con il relatore è stato deciso di
scrivere dei web crawler ad hoc per le pagine web del sito in esame. Non si è reso quindi il codice
generalizzabile e indipendente dal sito web da analizzare. Alcuni elementi, quali i link delle pagine
web e la struttura delle stringhe da analizzare mediante regole regex, sono cablati nel codice.
Per rispettare il vincolo n° 1 egli ha studiato l'applicativo Google Sites New ed ha accertato di poter
creare pagine web sia utilizzando puro codice HTML che elementi nativi della piattaforma. Il
candidato ha individuato un problema di retrocompatibilità con i dispositivi Apple iOS versione
12.3.1 e precedenti che non permette il corretto scorrimento delle pagine web su dispositivi touch
screen per pagine generate con porzioni di codice HTML. E' stato quindi scelto di realizzare il
nuovo sito con soli elementi nativi Google Sites New.
Poichè l'unico servizio di web hosting utilizzabile è Google Drive il laureando ha appurato di poter
ospitare il nuovo sito, i file statici e gli allegati su tale servizio e di poterne trasferire i permessi di
proprietà in un secondo momento ad un altro utente. In fase di produzione è stata riscontrata quindi
una limitazione della piattaforma che non permette il trasferimento di ownership per file formato
pdf o zip. Il team del laboratorio avrebbe dovuto effettuare prima il download dei 121 file allegati,
poi l'upload di tali file sull'account Google Drive del laboratorio e infine sostituire l'hash
identificativo di ogni file nei link presenti nelle pubblicazioni. Data la complessità dell'operazione è
stato deciso di lasciare il candidato come proprietario di tutti i file allegati caricati su Google Drive
di formato pdf.
Per soddisfare il vincolo n° 3 è stata analizzata "Awesome Table"[2], una Web Application che
genera codice HTML per rappresentare tabelle a partire dai dati inseriti in uno spreadsheet Google.
Per modificare l'aspetto grafico delle tabelle generate il candidato ha utilizzato lo strumento web
nativo fornito da Awesome Table e ha appurato di dover scrivere porzioni di codice HTML, CSS e
Javascript. L'operazione di migrazione prevede quindi di utilizzare questo applicativo per
rappresentare i dati delle pubblicazioni e delle news del laboratorio, salvati in due spreadsheet
differenti.
5
Progettazione
Scelta del linguaggio di programmazione e delle librerie.
Il candidato ha esperienza nella programmazione con il linguaggio Python. Egli ha scelto, quindi, di
sviluppare il Web Crawler, gli script di manipolazione dati e gli script di download/upload
automatizzato degli allegati con questo linguaggio.
Le librerie utilizzate sono le seguenti:
1. Requests [6] v2.21.0 per creare connessione HTTP tra client e server. Questa libreria è
determinante per poter visualizzare il contenuto di pagine web mediante terminale.
2. BeautifulSoup4 [1] v4.7.1 per poter estrarre dati da una pagina HTML o XML. Questa
libreria è il cuore del Web Crawler. Essa memorizza la struttura ad albero del DOM di una
pagina web e permette la navigazione attraverso i suoi nodi. Di ogni nodo è possibile
visionare il tipo, il tag, il contenuto, il contenuto dei nodi figli o genitori ed effettuare
ricerche di elementi chiave.
3. Pandas [3] v0.24.1. Questa libreria inserisce i dati presenti all'interno di un dizionario
Python (una struttura dati non ordinata le cui informazioni sono associate ad una parola
chiave) in uno spreadsheet formato .xlsx. E' stata utilizzata per convertire i dati estratti dal
Web Crawler nel formato richiesto dagli Spreadsheet Google.
4. Google-auth [7] v1.6.3 per poter interagire con le API Google [5] utilizzate per l'upload su
Google Drive dei file allegati alle pagine web. Questa libreria utilizza altre librerie native
Python come Pickle per potere salvare le credenziali dell'utente sul filesystem e autenticare
l'utente con le credenziali Google. Essa è stata sviluppata da Google stessa.
5. Re [8] per poter leggere e manipolare espressioni regolari. Questa libreria viene utilizzata
insieme agli oggetti BeautifulSoup per estrarre informazioni.
6. Json [9] per poter esportare i dizionari Python nel formato JSON.
L'installazione di queste librerie e delle dipendenze può inquinare l'ambiente lavorativo di un
calcolatore. Inoltre si vuole rendere portabile su altri calcolatori il codice scritto dal candidato. Egli
ha quindi deciso di creare un ambiente virtuale mediante il modulo Python Venv[13] e di esportare
la lista delle dipendenze mediante il comando:
Studio delle pagine HTML per estrarne i dati.
File Robots.txt
Scelte le librerie software da utilizzare è stata analizzata la possibilità di utilizzo di Web Crawler
sulle pagine web create con Google Sites. E' stata interrogata la pagina robots.txt[15] che contiene
le regole ed i permessi utilizzati dai programmi automatizzati di estrazione dati. E' stato così
riscontrata la possibilità di utilizzo di un semplice programma "headless" lanciato da terminale che
non deve presentare header User-Agent tipiche di un web browser comune. Il programma non deve
utilizzare il browser di sistema per simulare le azioni di un utente ordinario in quanto le pagine web
6
$ pip freeze > requirements.txt
da esaminare appartengono alla sezione Allowed e non Forbidden. Si è scelto quindi di utilizzare le
librerie sopra elencate piuttosto che sviluppare una soluzione completamente automatizzata col
software Selenium[10].
File Sitemap
Successivamente è stata analizzata la pagina che rappresenta la struttura del sito web ovvero la
Sitemap[16]. Essa è un file in formato XML. E' possibile estrarre da esso tutti i link delle pagine
web delle pubblicazioni e delle news mediante scrittura di un web crawler apposito.
Pagine web da cui estrarre i dati di interesse.
E' stata quindi studiata la sintassi HTML delle pagine web delle pubblicazione e delle news. Sono
state individuate le informazioni di interesse e le espressioni regolari necessarie per la loro corretta
estrazione. In particolare di ogni pubblicazione interessano: gli autori, il titolo, l'abstract, nome e
luogo e data della pubblicazione, link DOI, link Google Scholar, link della pubblicazione, link alle
slide di presentazione. Si vuole inoltre che nello spreadsheet di ogni pubblicazione siano presenti
dei valori booleani che identificano se un membro del laboratorio è autore della pubblicazione.
Di ogni news interessano: titolo, testo della news, data.
Roadmap
Il candidato ha quindi creato una roadmap per la realizzazione della migrazione:
1. Scrittura di 3 Web Crawlers utilizzando la libreria BeautifulSoup per l'estrazione di:
1. Link di tutte le pubblicazioni e di tutte le news dal sito Sitemap.
2. Informazioni sopra citate dalle pagine web delle pubblicazioni.
3. Informazioni sopra citate dalle pagine web delle news.
2. Esportazione delle informazioni estratte dalla struttura dati nativa Python chiamata
dizionario in file formato JSON.
3. Creazione di script per scaricare i 121 allegati formato .pdf sul calcolatore del candidato e
caricarli successivamente sul suo account Google Drive utilizzando la libreria Google-
auth[7].
4. Creazione di script per manipolare i dati estratti. In particolare:
1. Aggiunta di un valore booleano che identifica la presenza o meno di ogni membro del
laboratorio come autore della pubblicazione.
2. Aggiunta di un valore booleano che identifica se la pubblicazione è il risultato di una
conferenza o di un journal.
3. Per le pubblicazioni aggiunta di link che puntano agli allegati presenti nell'account
Google Drive del laureando. Per le news sostituzione dei link assoluti che puntano al
vecchio sito web con link relativi. Lo stesso per le immagini.
4. Aggiunta di timestamp per ogni elemento delle news.
7
5. Creazione di due spreadsheet (1 per le pubblicazioni, 1 per le news) a partire da un file
JSON utilizzando la libreria Pandas.
6. Creazione delle tabelle HTML con AwesomeTable. Scrittura codice HTML, Javascript, CSS
utilizzando il framework Bulma.
7. Creazione delle pagine statiche del nuovo sito web utilizzando elementi nativi Google Sites
New e integrazione con views di AwesomeTable.
Realizzazione
Creazione ambiente virtuale
Tutti gli script sono stati eseguiti sul calcolatore del candidato. Il SO utilizzato è Linux Ubuntu
17.10. E' necessario l'interprete Python v3.6 e il modulo venv. Per creare un ambiente virtuale si
utilizza il seguente comando: Successivamente si attiva l'ambiente virtuale
e si installano tutte le librerie sopra citate
Estrazione Dati
Assicurandosi di avere una connessione Internet attiva, è possibile eseguire il programma main.py
che si occupa della creazione ed esecuzione dei 3 Web Crawlers. Al termine
dell'esecuzione sono disponibili i file "News.json" e "Pubblications.json" nella directory corrente.
Il programma main.py inizialmente crea un web crawler per estrarre i link delle pagine contenenti le
pubblicazioni e le news dalla pagina della sitemap.
Nota: normalmente il link passato come parametro alla classe Urlspider viene inserito dalla linea di
comando. Tuttavia essendo il web crawler scritto ad hoc per una e una sola pagina web si è preferito
inserirlo nel codice sorgente direttamente.
#main.py
from url_spider import UrlSpider
from publication_spider import PublicationSpider
from news_spider import NewsSpider
if __name__ == '__main__':
url_spider = UrlSpider('http://machinelearning.inginf.units.it/system/feeds/sitemap')
url_spider.crawl()
url_spider.parse_urls()
url_spider.get_publications_urls()
url_spider.get_news_urls()
8
$ python -m venv venv
$ source venv/bin/activate $ pip install -r requirements.txt
$ python main.py
Successivamente crea ed esegue un web crawler per ogni pagina di news e esporta i dati nel file
News.json.
out = []
for url in url_spider.news_urls:
news_spider = NewsSpider(url)
data = news_spider.crawl()
out.append(data)
NewsSpider.export_json(out)
Lo stesso per ogni pagina di pubblicazione.
out = []
for url in url_spider.publications_urls:
pub_spider = PublicationSpider(url)
data = pub_spider.crawl()
out.append(data)
PublicationSpider.export_json(out)
I 3 web crawlers scritti hanno metodi simili. Si discute di seguito l'intera classe UrlSpider e i metodi
più rilevanti di PublicationSpider e NewsSpider. Per vedere in dettaglio il codice si rimanda il
lettore all'appendice (pag. 24).
Inizialmente si importano le librerie da utilizzare e si inizializza l'istanza della classe utilizzando il
link inserito come argomento al momento della creazione.
#url_spider.py
import requests
import re
from bs4 import BeautifulSoup as Soup
class UrlSpider(object):
def __init__(self, url=''):
self.uri = url
Si definisce il metodo che:
1. Preleva il contenuto dell'intera sitemap e la salva in un oggetto di tipo nativo BeautifulSoup.
def crawl(self):
req = requests.get(self.uri)
self.soup = Soup(req.content, "lxml")
2. Estrae dalla pagina tutti i link presenti nelle sottosezioni location ("loc") del file XML.
9
def parse_urls(self):
self.urls = [x.contents[0] for x in self.soup.fnd_all("loc")]
3. Estrae dai link solo quelli relativi alle pubblicazioni o alle news utilizzando regole RegEx e
li inserisce in delle variabili di classe.
def get_publications_urls(self):
self.publications_urls = [x for x in self.urls if re.match(r".+/publications/.
+/", x) ]
def get_news_urls(self):
self.news_urls = [x for x in self.urls if re.match(r'.+/news/.+', x)]
Le pagine con le pubblicazioni hanno molte informazioni di interesse. Nonostante siano tutte state
create con Google Sites Classic esse presentano diversa struttura HTML l'una dall'altra. Sono stati
quindi utilizzati costrutti "try,except" per gestire le eccezioni. A titolo di esempio si mostra la
porzione di codice utilizzata per prelevare la data e il luogo della conferenza, se presenti:
#publication_spider.py
...
def parse(self, soup):
data = {}
data["old_url"] = self.url
data["title"] = soup.fnd(id="sites-page-title").contents[0].string
...
# Location and year section
try:
year_and_location = soup.fnd(id="sites-canvas-
main").fnd("li").contents[1].string
year = re.match(r'D+(d+).+', str(year_and_location).strip()).group(1)
data["conference_year"] = unicodedata.normalize("NFKD", year)
location = re.match(r'D+d+, (.+)', str(year_and_location).strip()).group(1)
data["conference_location"] = unicodedata.normalize("NFKD", location)
except (AttributeError, IndexError):
try:
year_and_location = soup.fnd(id="sites-canvas-
main").fnd("li").contents[0].text
year = re.match(r'.+D+(d+).+',
str(year_and_location).strip()).group(1)
data["conference_year"] = unicodedata.normalize("NFKD", year)
location = re.match(r'.+D+d+, (.+)',
str(year_and_location).strip()).group(1)
data["conference_location"] = unicodedata.normalize("NFKD",
location)
except AttributeError:
year_and_location = soup.fnd(id="sites-canvas-main").fnd("li").text
10
year = re.match(r'.+D+(d+).?',
str(year_and_location).strip()).group(1)
data["conference_year"] = unicodedata.normalize("NFKD", year)
try:
location = re.match(r'.+D+d+,(.+)',
str(year_and_location).strip()).group(1)
data["conference_location"] = unicodedata.normalize("NFKD",
location)
except AttributeError:
data["conference_location"] = None
Per estrarre il link di DOI, Google Scholar e pubblicazione si usano le seguenti regole:
...
try:
links = soup.fnd(id="sites-canvas-main").fndAll("li")[2].fndAll("a")
except IndexError:
links = []
data["scholar"] = None
data["doi"] = None
data["publisher"] = None
for link in links:
if (re.match(r'.+scholar.google.+', link['href'])):
data["scholar"] = link['href']
elif (re.match(r'.+doi.org.+', link['href'])):
data["doi"] = link['href']
else:
data["publisher"] = link['href']
Per estrarre gli allegati:
...
try:
div_with_links = soup.fndAll("div", class_="sites-attachments-name")
pdf_links = []
for link in div_with_links:
pdf_links.append(link.a['href'])
except:
pdf_links = []
data["pdf_links"] = pdf_links
Infine per esportare il dizionario nel file formato JSON si definisce e invoca il metodo statico
export_json:
...
11
@staticmethod
def export_json(data):
j = json.dumps(data)
f = open("publications.json","w")
f.write(j)
f.close()
Il file news_spider.py è analogo a pubblication_spider.py. Risulta di interesse la regola per estrarre i
link delle immagini:
# news_spiders.py
class NewsSpider(object):
....
images = []
for image in soup.fnd(id="sites-canvas-main-content").fndAll("img"):
try:
images.append(image['src'])
except:
pass
data["images"] = images
Manipolazione Dati
Le date delle news non sono nel formato desiderato per essere inseriti nello spreadsheet. Esse
presentano una struttura non standard che le fa apparire come tipo stringa piuttosto che tipo Data;
non possono quindi essere utilizzate come parametro di ordinamento. Si vuole inoltre aggiungere
ulteriori informazioni ad ogni pubblicazione e news:
1. Aggiunta di elementi persi durante l'estrazione dati quali hyperlink e immagini.
2. Valore booleano per determinare se la pubblicazione è il risultato dell'esposizione in una
conferenza o in un articolo accademico.
3. Valore booleano per determinare la partecipazione di ogni membro del laboratorio alla
pubblicazione di un articolo.
Sono stati quindi scritti degli script di manipolazione dati.
Aggiunta di link
Per avere il contenuto delle pagine web estratte con il web crawler si perdono elementi nativi
HTML come per esempio gli hyperlink. Il primo script proposto risolve questo probelma
sostituendo delle porzioni di stringa con i link HTML. Si riporta solo la parte di codice per inserire
le immagini nelle news.
#add_links.py
...
def add_images_to_news():
12
out = []
with open("news.json") as news:
data = json.load(news)
for event in data:
try:
if len(event["images"]):
for image in event["images"]:
event["body"] = "<img src="%s">" % image +
event["body"]
except KeyError:
pass
out.append(event)
j = json.dumps(out)
f = open("news.json","w")
f.write(j)
f.close()
Differenziazione tra journal e conference
Ogni pubblicazione può essere il risultato dell'esposizione in una conferenza o in una rivista
accademica. Si vogliono introdurre dei valori booleani che identifichino questa differenziazione.
Lo script proposto risolve questo problema analizzando l'url della pagina da cui la pubblicazione è
stata estratta.
#add_journal_or_conference.py
import json
import re
if __name__ == '__main__':
out = []
with open("publications.json") as f:
data = json.load(f)
for pub in data:
pub['conference'] = 0
pub['journal'] = 0
if re.match(r'.*/international-conference-publications/.*', pub["old_url"]):
pub['conference'] = 1
elif re.match(r'.*/international-journal-publications/.*', pub["old_url"]):
pub['journal'] = 1
out.append(pub)
j = json.dumps(out)
f = open("publications.json","w")
f.write(j)
f.close()
13
Aggiunta flag membri del laboratorio
Per ogni pubblicazione è stata estratta una lista contenente gli autori. Di ogni pubblicazione si vuole
inserire un valore booleano che identifichi se il membro del laboratorio è tra gli autori.
Lo script proposto risolve questo problema analizzando la stringa "authors" e verificando che il
nome degli autori sia contenuto in essa.
#add_users.py
import json
import re
if __name__ == '__main__':
out = []
with open("publications.json") as publications:
data = json.load(publications)
for publication in data:
try:
publication["medvet"] = 1 if re.match(r'.*Medvet.*',
publication["authors"]) else 0
publication["bartoli"] = 1 if re.match(r'.*Bartoli.*',
publication["authors"]) else 0
publication["tarlao"] = 1 if re.match(r'.*Tarlao.*',
publication["authors"]) else 0
publication["de_lorenzo"] = 1 if re.match(r'.*De Lorenzo.*',
publication["authors"]) else 0
publication["talamini"] = 1 if re.match(r'.*Talamini.*',
publication["authors"]) else 0
publication["panflo"] = 1 if re.match(r'.*Panflo.*',
publication["authors"]) else 0
except KeyError:
...
j = json.dumps(out)
f = open("publications.json","w")
f.write(j)
f.close()
Aggiunta delle date
Le date delle pagine news estratte dal web crawler non sono compatibili con il formato utilizzato
dagli spreadsheet Google. Lo script proposto riesamina le date estraendole nuovamente e le
converte utilizzando la libreria Python nativa datetime[12]. A titolo d'esempio la data "Sep 10, 2018,
4:36 PM” viene trasformata in “2018-09-10 16:36:00”.
#add_timestamp_news.py
...
14
from datetime import datetime
if __name__ == '__main__':
...
out = []
with open("news.json") as news:
data = json.load(news)
for url in url_spider.news_urls:
content = requests.get(url).content
soup = Soup(content, "lxml")
date = soup.fnd("span",
class_='announcementsPostTimestamp').fnd("span").text
date = re.sub(r', d+:d+ [A-Z]+', "", date)
datetime_object = datetime.strptime(date, '%b %d,
%Y').strftime('%x')
for event in data:
if event["old_url"] == url:
event["date"] = datetime_object
out.append(event)
j = json.dumps(out)
f = open("news.json","w")
f.write(j)
f.close()
Download/upload file allegati
Ultima operazione di manipolazione dati consiste nel download dei file allegati sul calcolatore del
candidato per poi fare l'upload sull'account Google Drive. Lo script proposto oltre a eseguire queste
operazioni inserisce all'interno del file contenente le pubblicazioni i link che puntano ai file caricati.
Parte di questo script è stato realizzato utilizzando codice d'esempio fornito nella documentazione
delle API Google [5]. Si riportano solo i frammenti di codice rilevanti rimandando il lettore
all'appendice (pag. 32) qualora volesse analizzare il codice in dettaglio.
#download_upload_pdfs.py
...
from urllib.request import urlopen
from urllib.request import urlretrieve
from googleapiclient.discovery import build
from googleapiclient.http import MediaFileUpload
from google_auth_oauthlib.fow import InstalledAppFlow
from google.auth.transport.requests import Request
# If modifying these scopes, delete the fle token.pickle.
SCOPES = ['https://www.googleapis.com/auth/drive']
def download_pdfs():
15
creds = None
# The fle token.pickle stores the user's access and refresh tokens, and is
# created automatically when the authorization fow completes for the frst
# time.
if os.path.exists('token.pickle'):
with open('token.pickle', 'rb') as token:
creds = pickle.load(token)
# If there are no (valid) credentials available, let the user log in.
...
service = build('drive', 'v3', credentials=creds)
...
...
...
...
article_url = publication["pdf_dl_link_2"]
slides_url = publication["pdf_dl_link_1"]
article_id = download_and_upload_to_drive(service,
article_url)
publication['article_dl_link'] = 'https://drive.google.com/uc?
export=download&id=%s' % article_id
slides_id = download_and_upload_to_drive(service,
slides_url)
publication['slides_dl_link'] =
'https://drive.google.com/uc?export=download&id=%s' % slides_id
…
…
def download_and_upload_to_drive(service, url):
remotefle = urlopen(url)
info = remotefle.info()['Content-Disposition']
value, params = cgi.parse_header(info)
flename = params["flename"]
urlretrieve(url, "pdfs//%s" % flename)
folder_id = FOLDER_ID
fle_metadata = {'name': flename,
'parents': [folder_id]
}
media = MediaFileUpload('pdfs/%s' % flename,
mimetype='application/pdf')
fle = service.fles().create(body=fle_metadata,
media_body=media
).execute()
return fle.get('id')
if __name__ == '__main__':
16
download_pdfs()
Conversione da Json a spreadsheet
Il vincolo tecnologico n° 3 impone di utilizzare l'applicativo AwesomeTable per fornire
dinamicamente i dati delle pubblicazioni e news. Tale applicativo necessita che i dati siano inseriti
all'interno di uno spreadsheet Google. Sorge quindi il problema di come inserire i dati estratti
presenti nel file formato JSON in uno spreadsheet. Lo script proposto risolve questo problema
utilizzando la libreria Pandas.
#to_spreadsheet.py
import json
import pandas as pd
if __name__ == '__main__':
with open("publications.json") as f:
data = json.load(f)
pub_dataFrame = pd.DataFrame(data)
pub_dataFrame = pub_dataFrame.sort_values(by=['conference_year'],
ascending=False)
writer = pd.ExcelWriter('publications.xlsx', engine='xlsxwriter',
options={'strings_to_urls': False})
pub_dataFrame.to_excel(writer, sheet_name='Sheet1')
writer.save()
with open("news.json") as f:
data = json.load(f)
pub_dataFrame = pd.DataFrame(data)
pub_dataFrame['date']=pd.to_datetime(pub_dataFrame['date'])
pub_dataFrame = pub_dataFrame.sort_values(by=['date'],
ascending=False)
writer = pd.ExcelWriter('news.xlsx', engine='xlsxwriter',
options={'strings_to_urls': False})
pub_dataFrame.to_excel(writer, sheet_name='Sheet1')
writer.save()
Completata l'esecuzione dello script mediante comando compaiono
sulla directory corrente i file publications.xlsx e news.xlsx. Questi file sono stati caricati
sull'account Google Drive che contiene il nuovo sito web. A queste punto si procede alla creazione
delle tabelle HTML utilizzando l'applicativo web AwesomeTable.
Creazione delle tabelle
Dalla homepage di Awesome Table [19] si seleziona l'opzione new view. Una view è una
rappresentazione di una porzione di pagina web HTML. E' stato deciso di creare 3 views in totale.
17
$ python to_spreadsheet.py
Le prime due hanno come sorgente lo spreadsheet delle pubblicazioni, la terza ha come sorgente lo
spreadsheet delle news. Le 3 views sono così costituite:
1. Contiene tutte le pubblicazioni e alimenta la pagina publications del nuovo sito web.
2. Contiene le pubblicazioni più rilevanti. Questa view è alimentata dallo stesso spreadsheet
del punto 1 ma da un foglio diverso che, utilizzando la funzione filter, rappresenta una
sottoparte delle pubblicazioni. Essa alimenta la home page del nuovo sito web.
3. Contiene tutte le news del laboratorio. Assieme alla view del punto 2 alimenta la homepage
del nuovo sito.
Lo stile generico di rappresentazione delle views può essere modificato utilizzando lo strumento
nativo disponibile dall'applicativo web.
Tra le varie opzioni fornite da AwesomeTable è stato scelto di rappresentare le tabelle come Cards.
Tale rappresentazione risulta più pulita e i testi sono più facili da leggere.
Per poter dare al programmatore la possibilità di personalizzare le views, AwesomeTable consente
di aggiungere codice HTML, CSS e Javascript. Inoltre è possibile includere alcune librerie o
framework sia CSS che Javascript. Il candidato si è occupato di scrivere una struttura HTML
adeguata per le 3 views. Per inserire i dati presenti all'interno di una colonna dello spreadsheet si
usa una sintassi speciale {{ColumnName}}.
Per quanto riguarda il design grafico delle views il candidato ha scelto di utilizzare il framework
CSS Bulma. Integrando Bulma si integrano automaticamente le icone della libreria
FontAwesome[11].
Il laureando ha quindi scritto codice CSS basandosi sulla documentazione di Bulma. Lo stile risulta
pulito e moderno. Si allega codice HTML e CSS in appendice (pag. 35) qualora il lettore sia
interessato.
18
Illustration 1: Rappresentazione delle pubblicazioni mediante Cards
Di default si vuole che la sezione abstract non sia visibile. Si vuole che diventi visibile mediante un
click sulla scritta "abstract". Per ottenere tale effetto grafico è stato scritto il seguente codice in
javascript che modifica le classi html delle sezioni interessate:
// publications.js
function toggleAbstractButton(button){
let card_container = button.parentElement.parentElement;
toggleAbstract(card_container);
}
function toggleAbstract(card_container){
let abs_container = card_container.getElementsByClassName("abstract_container")
[0];
abs_container.classList.toggle("visible");
}
function collapseAbstract(link){
let card_container =
link.parentElement.parentElement.parentElement.parentElement;
toggleAbstract(card_container);
}
Per visualizzare correttamente l'icona che rappresenta la J di journal o la C di conference si sono
utilizzate le seguenti regole HTML e CSS:
<!-- publications.html -->
<div class="container">
<h3 class="title">
<span class="square_icon conference_fag" data-display-
fag="{{conference_fag}}">C</span>
<span class="square_icon journal_fag" data-display-
fag="{{journal_fag}}">J</span>
{{Title}}
...
/* publications.css */
[data-display-fag="0"]{
display: none;
}
[data-display-fag="1"]{
display: inline-block;
}
19
ricordando che {{conference_fag}} e {{journal_fag}} rappresentano i valori booleani presenti
nello spreadsheet publications.
Si allegano screenshot del risultato dell'operazione di graphic design sulle views.
Per le componenti statiche delle pagine web si sono utilizzati gli elementi nativi forniti da Google
Sites New. Si allega screenshot di parte dell'home page.
20
Illustration 2: Homepage. Risultato dell'operazione di design grafico sulle views di pubblicazioni
importanti e news.
Illustration 3: Publications. Risultato dell'operazione di design grafico sulla view delle
pubblicazioni.
Conclusioni
Problemi Riscontrati
Sono stati riscontrati diversi problemi durante la realizzazione della migrazione del sito web. Si
fornisce una lista esaustiva di ogni problema e di come esso è stato risolto:
1. Struttura HTML delle pagine web del sito da migrare che variano da pagina a pagina.
La realizzazione di una soluzione di estrazione dati automatizzata richiede una precisa
struttura HTML delle pagine da analizzare. Le espressioni regolari permettono ad un web
crawler di estrarre una stringa di caratteri, per quanto complessa, ma la struttura HTML deve
essere mantenuta uguale in ogni pagina. L'aggiunta di un singolo carattere non previsto di
spaziatura, quale ad esempio uno spazio o una virgola, possono causare un errore e mandare
in crash l'intero programma. Per raggiungere il risultato finale il candidato ha dovuto
procedere per tentativi successivi evolvendo le espressioni regolari errore dopo errore fino a
sviluppare una espressione capace di estrarre le stesse informazioni da pagine sorgenti con
strutture HTML diverse. Le espressioni regolari hanno richiesto una linea di codice nel caso
migliore (estrazioni link di Google Scholar e DOI) e 20 nel caso peggiore(estrazione
dell'anno e locazione della conferenza).
2. Blocco dei web crawler da parte dei server Google.
Seguendo le regole del file robots.txt il candidato ha appurato di poter realizzare l'estrazione
dati automatizzata. I server Google per prevenire attachi informatici di tipo DDOS limitano
il numero di request che possono essere effettuate da programmi headless. A causa del
problema n° 1 al candidato è capitato di dover effettuare più volte richieste di tipo GET
indirizzate verso il sito da migrare. Inizialmente Google blocca l'operazione di web scraping
richiedendo l'inserimento di un codice Captcha. Il candidato quindi ha utilizzato le prime
21
Illustration 4: Homepage.
iterazioni dei web crawler per salvare la struttura HTML delle pagine web in dei file locali
in modo da poter effettuare le simulazioni di estrazione dati su tale copie.
3. Bug di interazione tra sito e dispositivi con sistema operativo iOS v12.3.1 o precedenti per
pagine generate con snippet HTML ed elementi non nativi Google Sites.
Si vuole che il nuovo sito web sia formato "Responsive" per ogni dispositivo utilizzato.
Inizialmente il candidato ha sviluppato le pagine statiche del sito web in HTML e CSS.
Questo permette maggiori opzioni di personalizzazione per quanto riguarda il design
grafico. Durante la progettazione il candidato ha usufruito del servizio di emulazione
dipositivi nativo di Google Chrome. La visione del sito con ogni dispositivo e sistema
operativo emulato non presenteva nessun tipo di problema. In produzione il candidato ha
provato a visionare il nuovo sito su un iPhone 7 (iOS v12.3.1) ed è stato riscontrato un bug
che non permetteva il corretto scorrimento delle pagine web. Il bug è riproducibile su
qualsiasi dispositivo (smartphone, tablet ecc..) con tale versione di sistema operativo o
precedente. Esso risulta già segnalato [20] agli sviluppatori Google ma non è stato ancora
risolto. Il candidato quindi ha dovuto reimplementare le pagine web utilizzando gli elementi
nativi Google Sites New. In tal mondo il bug non si è manifestato.
4. Impossibilità di trasferimento di ownership per file pdf e zip tra account Google Drive.
Il candidato inizialmente ha utilizzato come servizio di Web Hosting il proprio account
Google Drive. L'idea era quella di trasferire l'ownership in fase di produzione del sito web,
degli spreadsheet, dei file statici (immagini, css, javascript) e degli allegati ai membri del
laboratorio. In produzione il candidato ha scoperto che Google non permette il trasferimento
di ownership di file formato PDF e zip. Il direttore del Laboratorio avrebbe dovuto quindi
personalmente scaricare gli allegati sul proprio calcolatore, eseguire l'upload sull'account
Google Drive del laboratorio e modificare i link degli spreadsheet delle pubblicazioni
utilizzando gli script realizzati dal laureando. Il team del laboratorio ha deciso di lasciare gli
allegati pdf nell'account Google Drive del candidato.
Commento
Nonostante i problemi sopra citati al termine del periodo di tirocinio presso il laboratorio la
migrazione del sito web risulta completata. Gli obiettivi primari, ossia scrittura del nuovo sito web
con Google Sites New e conservazione delle informazioni relative alle pubblicazioni e news,
risultano raggiunti e i vincoli tecnologici sono rispettati.
Il candidato ha sviluppato un totale di 515 righe di codice Python diviso in 11 programmi, 98 di
codice HTML, 91 di codice CSS, 33 di codice Javascript per un totale di 737 linee di codice
effettivo utilizzato (questo conteggio non considera il codice HTML e CSS scritto previa scoperta
del bug grafico iOS).
Per quanto riguarda l'operazione extra di programmazione grafica il candidato non si ritiene
soddisfatto del lavoro compiuto. La progettazione del design grafico non è una scienza esatta e
quindi quantificare e valutare il lavoro svolto in tale ambito risulta opinabile. Il laureando ritiene
tuttavia che il nuovo sito web sia troppo minimale ed essenziale. Egli inizialmente aveva intenzione
di integrare effetti grafici complessi mediante librerie CSS e Javascript ma non aveva previsto il
riscontro del bug grafico dei dispositivi iOS in fase di produzione che limita all'ultilizzo di soli
elementi Google Sites New.
In generale il candidato si ritiene soddisfatto del lavoro svolto. Egli ha avuto l'opportunità di
sviluppare codice in oltre 4 linguaggi di programmazione diversi, di lavorare con tecnologie Google
22
con le quali non aveva familiarità e di approfondire le proprie conoscenze nell'ambito del Data
Mining utilizzando la tecnica di Web Scraping automatizzata.
Il sito web è pronto per la produzione e può essere visionato al seguente URL:
https://sites.google.com/view/tesi-zorzoli.
Sitografia
[1] BeautifulSoup https://www.crummy.com/software/BeautifulSoup/bs4/doc/
[2] Awesome Table https://support.awesome-table.com/hc/en-us
[3] Pandas https://pandas.pydata.org/pandas-docs/stable/
[4] Bulma https://bulma.io/documentation/
[5] Google API https://developers.google.com/drive/api/v3/quickstart/python
[6] Requests https://2.python-requests.org/en/master/
[7] Google Auth https://google-auth-
oauthlib.readthedocs.io/en/latest/reference/google_auth_oauthlib.flow.html
[8] Re https://docs.python.org/3.6/library/re.html
[9] Json https://docs.python.org/3/library/json.html
[10] Selenium https://selenium-python.readthedocs.io/
[11] FontAwesome https://fontawesome.com/how-to-use/on-the-web/referencing-icons/basic-use
[12] Datetime https://docs.python.org/3.6/library/datetime.html
[13] Venv https://docs.python.org/3/library/venv.html
[14] Google Sites New https://support.google.com/sites/?hl=en#topic=7184580
[15] File Robots.txt http://machinelearning.inginf.units.it/robots.txt
[16] File Sitemap http://machinelearning.inginf.units.it/system/feeds/sitemap
[17] Google Suite Updates https://gsuiteupdates.googleblog.com/2019/01/google-sites-
improvements-new-roadmap.html
[18] Google Sites Support https://support.google.com/sites/answer/7035197
[19] Awesome Table Homepage https://awesome-table.com/
[20] Bug Google Sites iOS https://support.google.com/sites/forum/AAAATAKBXDQZjNKM9--
D3A/?hl=en&msgid=DJWBua95AAAJ&gpf=d/msg/sites/ZjNKM9--D3A/DJWBua95AAAJ
Appendice
Si inserisce in questa sezione tutto il codice sorgente scritto.
#main.py
from url_spider import UrlSpider
from publication_spider import PublicationSpider
from news_spider import NewsSpider
if __name__ == '__main__':
url_spider = UrlSpider('http://machinelearning.inginf.units.it/system/feeds/sitemap')
url_spider.crawl()
url_spider.parse_urls()
url_spider.get_publications_urls()
url_spider.get_news_urls()
23
out = []
for url in url_spider.news_urls:
news_spider = NewsSpider(url)
data = news_spider.crawl()
out.append(data)
NewsSpider.export_json(out)
out = []
for url in url_spider.publications_urls:
pub_spider = PublicationSpider(url)
data = pub_spider.crawl()
out.append(data)
PublicationSpider.export_json(out)
#url_spider.py
import requests
import re
from bs4 import BeautifulSoup as Soup
class UrlSpider(object):
def __init__(self, url=''):
self.uri = url
def crawl(self):
req = requests.get(self.uri)
self.soup = Soup(req.content, "lxml")
def parse_urls(self):
self.urls = [x.contents[0] for x in self.soup.fnd_all("loc")]
def get_publications_urls(self):
self.publications_urls = [x for x in self.urls if re.match(r".+/publications/.
+/", x) ]
def get_news_urls(self):
self.news_urls = [x for x in self.urls if re.match(r'.+/news/.+', x)]
def get_urls(self):
return self.urls
#publication_spider.py
import requests
import re
import json
import unicodedata
from bs4 import BeautifulSoup as Soup
24
class PublicationSpider(object):
def __init__(self, url=''):
self.url = url
def crawl(self):
req = requests.get(self.url)
soup = Soup(req.content, "lxml")
return self.parse(soup)
def parse(self, soup):
data = {}
data["old_url"] = self.url
data["title"] = soup.fnd(id="sites-page-title").contents[0].string
print(self.url)
try:
conference_name = soup.fnd(id="sites-canvas-
main").fnd("li").contents[0].string
if conference_name is None:
conference_name = soup.fnd(id="sites-canvas-
main").fnd("li").contents[0].b.string
data["conference_name"] = unicodedata.normalize("NFKD",
conference_name)
except AttributeError:
# This page is diferent from others therefore it needs special
handling.
return {}
# Location and year section
try:
year_and_location = soup.fnd(id="sites-canvas-
main").fnd("li").contents[1].string
year = re.match(r'D+(d+).+', str(year_and_location).strip()).group(1)
data["conference_year"] = unicodedata.normalize("NFKD", year)
location = re.match(r'D+d+, (.+)',
str(year_and_location).strip()).group(1)
data["conference_location"] = unicodedata.normalize("NFKD",
location)
except (AttributeError, IndexError):
try:
year_and_location = soup.fnd(id="sites-canvas-
main").fnd("li").contents[0].text
year = re.match(r'.+D+(d+).+',
str(year_and_location).strip()).group(1)
data["conference_year"] = unicodedata.normalize("NFKD",
year)
location = re.match(r'.+D+d+, (.+)',
str(year_and_location).strip()).group(1)
data["conference_location"] = unicodedata.normalize("NFKD",
location)
25
except AttributeError:
year_and_location = soup.fnd(id="sites-canvas-
main").fnd("li").text
year = re.match(r'.+D+(d+).?',
str(year_and_location).strip()).group(1)
data["conference_year"] = unicodedata.normalize("NFKD",
year)
try:
location = re.match(r'.+D+d+,(.+)',
str(year_and_location).strip()).group(1)
data["conference_location"] =
unicodedata.normalize("NFKD", location)
except AttributeError:
data["conference_location"] = None
data["authors"] = unicodedata.normalize("NFKD", soup.fnd(id="sites-
canvas-main").fndAll("li")[1].text)
try:
links = soup.fnd(id="sites-canvas-main").fndAll("li")[2].fndAll("a")
except IndexError:
links = []
data["scholar"] = None
data["doi"] = None
data["publisher"] = None
for link in links:
if (re.match(r'.+scholar.google.+', link['href'])):
data["scholar"] = link['href']
elif (re.match(r'.+doi.org.+', link['href'])):
data["doi"] = link['href']
else:
data["publisher"] = link['href']
body = soup.fnd(id="sites-canvas-main").div.text.replace("n", "")
data["body"] = unicodedata.normalize("NFKD", body)
try:
div_with_links = soup.fndAll("div", class_="sites-attachments-name")
pdf_links = []
for link in div_with_links:
pdf_links.append(link.a['href'])
except:
pdf_links = []
data["pdf_links"] = pdf_links
try:
div_with_dl_links = soup.fndAll("div", id=re.compile("^attachment-
download-wuid.*"))
pdf_dl_links = []
for link in div_with_dl_links:
26
pdf_dl_links.append('http://machinelearning.inginf.units.it' +
link.a['href'])
except:
pdf_dl_links = []
data["pdf_dl_links"] = pdf_dl_links
links = []
for link in soup.fnd(id="sites-canvas-main-content").fndAll("a"):
try:
links.append((link['href'], link.text))
except KeyError:
pass
data["other_links"] = links
return data
@staticmethod
def export_json(data):
j = json.dumps(data)
f = open("publications.json","w")
f.write(j)
f.close()
#news_spider.py
import json
import re
import requests
import unicodedata
from bs4 import BeautifulSoup as Soup
class NewsSpider(object):
def __init__(self, url=''):
self.url = url
def crawl(self):
req = requests.get(self.url)
soup = Soup(req.content, "lxml")
return self.parse(soup)
def parse(self, soup):
data = {}
data["old_url"] = self.url
data["title"] = soup.fnd(id="sites-page-title").text
print(self.url)
27
data["body"] = unicodedata.normalize("NFKD", soup.fnd(id="sites-canvas-
main-content").text.replace("n", ""))
links = []
for link in soup.fnd(id="sites-canvas-main-content").fndAll("a"):
try:
links.append((link['href'], link.text))
except KeyError:
pass
data["links"] = links
images = []
for image in soup.fnd(id="sites-canvas-main-content").fndAll("img"):
try:
images.append(image['src'])
except:
pass
data["images"] = images
return data
@staticmethod
def export_json(data):
j = json.dumps(data)
f = open("news.json","w")
f.write(j)
f.close()
#add_users.py
import json
import re
if __name__ == '__main__':
out = []
with open("publications.json") as publications:
data = json.load(publications)
for publication in data:
try:
publication["medvet"] = 1 if re.match(r'.*Medvet.*',
publication["authors"]) else 0
publication["bartoli"] = 1 if re.match(r'.*Bartoli.*',
publication["authors"]) else 0
publication["tarlao"] = 1 if re.match(r'.*Tarlao.*',
publication["authors"]) else 0
publication["de_lorenzo"] = 1 if re.match(r'.*De Lorenzo.*',
publication["authors"]) else 0
publication["talamini"] = 1 if re.match(r'.*Talamini.*',
publication["authors"]) else 0
publication["panflo"] = 1 if re.match(r'.*Panflo.*',
publication["authors"]) else 0
except KeyError:
28
publication["medvet"] = 0
publication["bartoli"] = 0
publication["tarlao"] = 0
publication["de_lorenzo"] = 0
publication["talamini"] = 0
publication["panflo"] = 0
out.append(publication)
j = json.dumps(out)
f = open("publications.json","w")
f.write(j)
f.close()
#add_journal_or_conference.py
import json
import re
if __name__ == '__main__':
out = []
with open("publications.json") as f:
data = json.load(f)
for pub in data:
pub['conference'] = 0
pub['journal'] = 0
if re.match(r'.*/international-conference-publications/.*',
pub["old_url"]):
pub['conference'] = 1
elif re.match(r'.*/international-journal-publications/.*', pub["old_url"]):
pub['journal'] = 1
out.append(pub)
j = json.dumps(out)
f = open("publications.json","w")
f.write(j)
f.close()
#add_links.py
import json
import re
def add_links_to_pubs():
out = []
with open("publications.json") as publications:
data = json.load(publications)
for publication in data:
try:
if len(publication["other_links"]):
for link in publication["other_links"]:
29
publication["body"] =
publication["body"].replace(link[1], "<a
href="%s" target="_blank">%s</a>" %
(link[0], link[1]))
except KeyError:
pass
out.append(publication)
j = json.dumps(out)
f = open("publications.json","w")
f.write(j)
f.close()
def add_links_to_news():
out = []
with open("news.json") as news:
data = json.load(news)
for event in data:
try:
if len(event["links"]):
for link in event["links"]:
if link[1] is not '':
event["body"] =
event["body"].replace(link[1], "<a
href="%s" target="_blank">
%s</a>" % (link[0], link[1]))
except KeyError:
pass
out.append(event)
j = json.dumps(out)
f = open("news.json","w")
f.write(j)
f.close()
def add_images_to_news():
out = []
with open("news.json") as news:
data = json.load(news)
for event in data:
try:
if len(event["images"]):
for image in event["images"]:
event["body"] = "<img src="%s">" % image +
event["body"]
except KeyError:
pass
out.append(event)
j = json.dumps(out)
f = open("news.json","w")
f.write(j)
30
f.close()
if __name__ == '__main__':
add_links_to_pubs()
add_links_to_news()
add_images_to_news()
#add_timestamp_news.py
from bs4 import BeautifulSoup as Soup
import json
import re
import requests
from datetime import datetime
from url_spider import UrlSpider
if __name__ == '__main__':
url_spider = UrlSpider('http://machinelearning.inginf.units.it/system/feeds/sitemap')
url_spider.crawl()
url_spider.parse_urls()
url_spider.get_news_urls()
out = []
with open("news.json") as news:
data = json.load(news)
for url in url_spider.news_urls:
content = requests.get(url).content
soup = Soup(content, "lxml")
date = soup.fnd("span",
class_='announcementsPostTimestamp').fnd("span").text
date = re.sub(r', d+:d+ [A-Z]+', "", date)
datetime_object = datetime.strptime(date, '%b %d,
%Y').strftime('%x')
print(datetime_object)
for event in data:
if event["old_url"] == url:
event["date"] = datetime_object
out.append(event)
j = json.dumps(out)
f = open("news.json","w")
f.write(j)
f.close()
#download_upload_pdfs.py
31
from __future__ import print_function
import json
import re
from urllib.request import urlopen
from urllib.request import urlretrieve
import cgi
import os
import pickle
import os.path
from googleapiclient.discovery import build
from googleapiclient.http import MediaFileUpload
from google_auth_oauthlib.fow import InstalledAppFlow
from google.auth.transport.requests import Request
# If modifying these scopes, delete the fle token.pickle.
SCOPES = ['https://www.googleapis.com/auth/drive']
def download_pdfs():
creds = None
# The fle token.pickle stores the user's access and refresh tokens, and is
# created automatically when the authorization fow completes for the frst
# time.
if os.path.exists('token.pickle'):
with open('token.pickle', 'rb') as token:
creds = pickle.load(token)
# If there are no (valid) credentials available, let the user log in.
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
fow = InstalledAppFlow.from_client_secrets_fle(
'credentials.json', SCOPES)
creds = fow.run_local_server()
# Save the credentials for the next run
with open('token.pickle', 'wb') as token:
pickle.dump(creds, token)
service = build('drive', 'v3', credentials=creds)
out = []
with open("publications.json") as publications:
data = json.load(publications)
for publication in data:
if publication["pdf_dl_link_1"]:
if publication["pdf_dl_link_2"]:
article_url = publication["pdf_dl_link_2"]
slides_url = publication["pdf_dl_link_1"]
article_id = download_and_upload_to_drive(service,
article_url)
32
publication['article_dl_link'] =
'https://drive.google.com/uc?
export=download&id=%s' % article_id
publication['article_view_link'] =
'https://drive.google.com/fle/d/%s/view' %
article_id
slides_id = download_and_upload_to_drive(service,
slides_url)
publication['slides_dl_link'] =
'https://drive.google.com/uc?
export=download&id=%s' % slides_id
publication['slides_view_link'] =
'https://drive.google.com/fle/d/%s/view' %
slides_id
else:
article_url = publication["pdf_dl_link_1"]
article_id = download_and_upload_to_drive(service,
article_url)
publication['article_dl_link'] =
'https://drive.google.com/uc?
export=download&id=%s' % article_id
publication['article_view_link'] =
'https://drive.google.com/fle/d/%s/view' %
article_id
publication['slides_dl_link'] = ''
publication['slides_view_link'] = ''
else:
print('No article')
publication['article_dl_link'] = ''
publication['article_view_link'] = ''
publication['slides_dl_link'] = ''
publication['slides_view_link'] = ''
out.append(publication)
j = json.dumps(out)
f = open("publications.json","w")
f.write(j)
f.close()
def download_and_upload_to_drive(service, url):
remotefle = urlopen(url)
info = remotefle.info()['Content-Disposition']
value, params = cgi.parse_header(info)
flename = params["flename"]
urlretrieve(url, "pdfs//%s" % flename)
print("Downloaded File: %s" % flename)
folder_id = FOLDER_ID
fle_metadata = {'name': flename,
33
'parents': [folder_id]
}
media = MediaFileUpload('pdfs/%s' % flename,
mimetype='application/pdf')
fle = service.fles().create(body=fle_metadata,
media_body=media
).execute()
print('Uploaded fle ID: %s' % fle.get('id'))
return fle.get('id')
if __name__ == '__main__':
download_pdfs()
# merge_pdf_urls.py
import json
if __name__ == '__main__':
out = []
with open('publications.json') as f:
data = json.load(f)
for pub in data:
pub['pdf_link_1'] = ''
pub['pdf_dl_link_1'] = ''
pub['pdf_link_2'] = ''
pub['pdf_dl_link_2'] = ''
try:
pub['pdf_link_1'] = pub["pdf_links"][0]
except:
pass
try:
pub['pdf_dl_link_1'] = pub["pdf_dl_links"][0]
except:
pass
try:
pub['pdf_link_2'] = pub["pdf_links"][1]
except:
pass
try:
pub['pdf_dl_link_2'] = pub["pdf_dl_links"][1]
except:
pass
out.append(pub)
34
j = json.dumps(out)
f = open("publications.json","w")
f.write(j)
f.close()
#to_spreadsheet.py
import json
import pandas as pd
if __name__ == '__main__':
with open("publications.json") as f:
data = json.load(f)
pub_dataFrame = pd.DataFrame(data)
pub_dataFrame = pub_dataFrame.sort_values(by=['conference_year'],
ascending=False)
writer = pd.ExcelWriter('publications.xlsx', engine='xlsxwriter',
options={'strings_to_urls': False})
pub_dataFrame.to_excel(writer, sheet_name='Sheet1')
writer.save()
with open("news.json") as f:
data = json.load(f)
pub_dataFrame = pd.DataFrame(data)
pub_dataFrame['date']=pd.to_datetime(pub_dataFrame['date'])
pub_dataFrame = pub_dataFrame.sort_values(by=['date'], ascending=False)
writer = pd.ExcelWriter('news.xlsx', engine='xlsxwriter',
options={'strings_to_urls': False})
pub_dataFrame.to_excel(writer, sheet_name='Sheet1')
writer.save()
<!-- publications.html -->
<div class="container">
<h3 class="title">
<span class="square_icon conference_fag" data-display-
fag="{{conference_fag}}">C</span>
<span class="square_icon journal_fag" data-display-
fag="{{journal_fag}}">J</span>
{{Title}}
<span class="icon" data-show-pdf-icon="{{article_dl_link}}">
<a href="{{article_dl_link}}">
<i class="far fa-fle-pdf"></i>
</a>
35
</span>
</h3>
<h6 class="subtitle is-6 authors">
{{authors}}
</h6>
<p class="publication">
<span class="publication_name">{{publication_name}}</span>
<span class="publication_year">{{Year}}</span>
<span class="conference_location">{{conference_location}}</span>
</p>
<a onclick="toggleAbstractButton(this)">
<span class="icon">
<i class="fas fa-book-reader"></i>
</span>
<span>Abstract</span>
</a>
<div class="abstract_container">
<p class="pub_urls">
<span class="google_scholar"><a href="{{scholar}}"
target="_blank">Google
Scholar</a></span>
<span class="doi"><a href="{{doi}}"
target="_blank">DOI</a></span>
<span class="publisher"><a href="{{publisher}}"
target="_blank">Publisher</a></span>
</p>
<p class="abstract">
{{abstract}}
<span>
<a href="#" onclick="collapseAbstract(this)">
<span class="icon">
<i class="fas fa-angle-double-up"></i>
</span>
<span id="collapse_text">Collapse</span>
</a>
</span>
</p>
<div class="download_container">
<span>
<a href="{{article_dl_link}}" target="_blank">
<span class="icon">
<i class="fas fa-download"></i>
</span>
<span id="collapse_text">Full Article</span>
</a>
</span>
<span>
36
<a href="{{slides_dl_link}}" target="_blank">
<span class="icon">
<i class="fas fa-download"></i>
</span>
<span id="collapse_text">Slides</span>
</a>
</span>
</div>
</div>
</div>
/* publications.css */
.title{
font-size: 1rem !important;
}
.subtitle{
font-size: 0.9rem !important;
margin-bottom: 7px !important;
}
.publication{
margin-bottom: 5px;
}
.abstract_container{
display: none;
}
.abstract_container p {
padding-right: 5px;
padding-left: 5px;
}
.abstract_container > div {
margin-left: 5px;
}
.visible{
display: block;
}
[data-display-fag="0"]{
display: none;
}
[data-display-fag="1"]{
display: inline-block;
}
37
[data-show-pdf-icon=""]{
display: none !important;
}
.square_icon{
width: 1rem;
height: 1rem;
border-radius: 0.2rem;
color: white;
text-align: center;
}
.conference_fag{
background-color: hsl(348, 100%, 61%);
}
.journal_fag{
background-color: hsl(204, 86%, 53%);
}
#collapse_text{
margin-left: -5px;
}
// publications.js
function toggleAbstractButton(button){
let card_container = button.parentElement.parentElement;
toggleAbstract(card_container);
}
function toggleAbstract(card_container){
let abs_container = card_container.getElementsByClassName("abstract_container")
[0];
abs_container.classList.toggle("visible");
console.log(card_container);
console.log(abs_container);
}
function collapseAbstract(link){
let card_container =
link.parentElement.parentElement.parentElement.parentElement;
toggleAbstract(card_container);
}
<!-- news.html -->
38
<div class="container">
<div class="title_container">
<h3 class="title">
{{title}}
</h3>
</div>
<div class="subtitle_container">
<p class="date">
{{date}}
</p>
</div>
<a onclick="toggleBodyButton(this)">
<span class="icon">
<i class="fas fa-book-reader"></i>
</span>
<span>Read More</span>
</a>
<div class="body_container">
<p class="body">
{{body}}
<span>
<a href="#" onclick="collapseBody(this)">
<span class="icon">
<i class="fas fa-angle-double-up"></i>
</span>
<span id="collapse_text">Collapse</span>
</a>
</span>
</p>
</div>
</div>
/* news.css */
.title{
font-size: 1rem !important;
}
.subtitle{
font-size: 0.9rem !important;
margin-bottom: 7px !important;
}
img {
max-width: 100%;
}
.body_container{
39
display: none;
}
.body_container p {
padding-right: 5px;
padding-left: 5px;
}
.body_container > div {
margin-left: 5px;
}
.visible{
display: block;
}
#collapse_text{
margin-left: -5px;
}
// news.js
function toggleBodyButton(button){
let card_container = button.parentElement.parentElement;
toggleBody(card_container);
}
function collapseBody(link){
let card_container =
link.parentElement.parentElement.parentElement.parentElement;
toggleBody(card_container);
}
function toggleBody(card_container){
let abs_container = card_container.getElementsByClassName("body_container")[0];
abs_container.classList.toggle("visible");
}
40

More Related Content

Similar to Migrazione di un sito web mediante tecnica di estrazione dati automatizzata

Cert03 70-486 developing asp.net mvc 4 web applications
Cert03   70-486 developing asp.net mvc 4 web applicationsCert03   70-486 developing asp.net mvc 4 web applications
Cert03 70-486 developing asp.net mvc 4 web applicationsDotNetCampus
 
Maria Grazia Maffucci - programmazione relazione
Maria Grazia Maffucci - programmazione relazioneMaria Grazia Maffucci - programmazione relazione
Maria Grazia Maffucci - programmazione relazioneMaria Grazia Maffucci
 
Presentazione Corso - Parte 3
Presentazione Corso - Parte 3Presentazione Corso - Parte 3
Presentazione Corso - Parte 3Giorgio Carpoca
 
Esposizione RIA
Esposizione RIAEsposizione RIA
Esposizione RIAdiodorato
 
Introduzione a Internet
Introduzione a InternetIntroduzione a Internet
Introduzione a Internetdadahtml
 
HTML5 e Css3 - 1 | WebMaster & WebDesigner
HTML5 e Css3 - 1 | WebMaster & WebDesignerHTML5 e Css3 - 1 | WebMaster & WebDesigner
HTML5 e Css3 - 1 | WebMaster & WebDesignerMatteo Magni
 
Laboratorio Internet: 1. Introduzione
Laboratorio Internet: 1. IntroduzioneLaboratorio Internet: 1. Introduzione
Laboratorio Internet: 1. IntroduzioneRoberto Polillo
 
Architetture web - Linguaggi e standard - Web server, application server, dat...
Architetture web - Linguaggi e standard - Web server, application server, dat...Architetture web - Linguaggi e standard - Web server, application server, dat...
Architetture web - Linguaggi e standard - Web server, application server, dat...Fulvio Corno
 
7 - Ricercare nel web - 17/18
7 - Ricercare nel web - 17/187 - Ricercare nel web - 17/18
7 - Ricercare nel web - 17/18Giuseppe Vizzari
 
Le novita di visual studio 2012
Le novita di visual studio 2012Le novita di visual studio 2012
Le novita di visual studio 2012Crismer La Pignola
 
Generazione automatica diagrammi di rete con template pptx
Generazione automatica diagrammi di rete con template pptxGenerazione automatica diagrammi di rete con template pptx
Generazione automatica diagrammi di rete con template pptxGiacomoZorzin
 
SEO: Search Engine Optimization & Cloud computing
SEO: Search Engine Optimization & Cloud computingSEO: Search Engine Optimization & Cloud computing
SEO: Search Engine Optimization & Cloud computingbsdlover
 

Similar to Migrazione di un sito web mediante tecnica di estrazione dati automatizzata (20)

6 - Il browser - 16/17
6 - Il browser - 16/176 - Il browser - 16/17
6 - Il browser - 16/17
 
Cert03 70-486 developing asp.net mvc 4 web applications
Cert03   70-486 developing asp.net mvc 4 web applicationsCert03   70-486 developing asp.net mvc 4 web applications
Cert03 70-486 developing asp.net mvc 4 web applications
 
Maria Grazia Maffucci - programmazione relazione
Maria Grazia Maffucci - programmazione relazioneMaria Grazia Maffucci - programmazione relazione
Maria Grazia Maffucci - programmazione relazione
 
Presentazione Corso - Parte 3
Presentazione Corso - Parte 3Presentazione Corso - Parte 3
Presentazione Corso - Parte 3
 
Bootstrap
BootstrapBootstrap
Bootstrap
 
Esposizione RIA
Esposizione RIAEsposizione RIA
Esposizione RIA
 
Ricercare nel web
Ricercare nel webRicercare nel web
Ricercare nel web
 
WordCamp Catania 2019 PWA e TWA
WordCamp Catania 2019 PWA e TWAWordCamp Catania 2019 PWA e TWA
WordCamp Catania 2019 PWA e TWA
 
Introduzione a Internet
Introduzione a InternetIntroduzione a Internet
Introduzione a Internet
 
10 - Ricercare nel web II
10 - Ricercare nel web II10 - Ricercare nel web II
10 - Ricercare nel web II
 
HTML5 e Css3 - 1 | WebMaster & WebDesigner
HTML5 e Css3 - 1 | WebMaster & WebDesignerHTML5 e Css3 - 1 | WebMaster & WebDesigner
HTML5 e Css3 - 1 | WebMaster & WebDesigner
 
Il browser
Il browserIl browser
Il browser
 
Laboratorio Internet: 1. Introduzione
Laboratorio Internet: 1. IntroduzioneLaboratorio Internet: 1. Introduzione
Laboratorio Internet: 1. Introduzione
 
Architetture web - Linguaggi e standard - Web server, application server, dat...
Architetture web - Linguaggi e standard - Web server, application server, dat...Architetture web - Linguaggi e standard - Web server, application server, dat...
Architetture web - Linguaggi e standard - Web server, application server, dat...
 
7 - Ricercare nel web - 17/18
7 - Ricercare nel web - 17/187 - Ricercare nel web - 17/18
7 - Ricercare nel web - 17/18
 
Le novita di visual studio 2012
Le novita di visual studio 2012Le novita di visual studio 2012
Le novita di visual studio 2012
 
8 - Il browser
8 - Il browser8 - Il browser
8 - Il browser
 
Generazione automatica diagrammi di rete con template pptx
Generazione automatica diagrammi di rete con template pptxGenerazione automatica diagrammi di rete con template pptx
Generazione automatica diagrammi di rete con template pptx
 
06 - Il browser
06 - Il browser06 - Il browser
06 - Il browser
 
SEO: Search Engine Optimization & Cloud computing
SEO: Search Engine Optimization & Cloud computingSEO: Search Engine Optimization & Cloud computing
SEO: Search Engine Optimization & Cloud computing
 

Recently uploaded

Giornata Tecnica da Piave Servizi, 11 aprile 2024 | RENZI Daniele
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | RENZI DanieleGiornata Tecnica da Piave Servizi, 11 aprile 2024 | RENZI Daniele
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | RENZI DanieleServizi a rete
 
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | DI DOMENICO Simone
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | DI DOMENICO SimoneGiornata Tecnica da Piave Servizi, 11 aprile 2024 | DI DOMENICO Simone
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | DI DOMENICO SimoneServizi a rete
 
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | ROMANO' Davide
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | ROMANO' DavideGiornata Tecnica da Piave Servizi, 11 aprile 2024 | ROMANO' Davide
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | ROMANO' DavideServizi a rete
 
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | DISCIPIO Antonio
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | DISCIPIO AntonioGiornata Tecnica da Piave Servizi, 11 aprile 2024 | DISCIPIO Antonio
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | DISCIPIO AntonioServizi a rete
 
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | SERRA Giorgio
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | SERRA GiorgioGiornata Tecnica da Piave Servizi, 11 aprile 2024 | SERRA Giorgio
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | SERRA GiorgioServizi a rete
 
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | ALBIERO Andrea
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | ALBIERO AndreaGiornata Tecnica da Piave Servizi, 11 aprile 2024 | ALBIERO Andrea
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | ALBIERO AndreaServizi a rete
 
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | CADEI Giovanni
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | CADEI GiovanniGiornata Tecnica da Piave Servizi, 11 aprile 2024 | CADEI Giovanni
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | CADEI GiovanniServizi a rete
 

Recently uploaded (7)

Giornata Tecnica da Piave Servizi, 11 aprile 2024 | RENZI Daniele
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | RENZI DanieleGiornata Tecnica da Piave Servizi, 11 aprile 2024 | RENZI Daniele
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | RENZI Daniele
 
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | DI DOMENICO Simone
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | DI DOMENICO SimoneGiornata Tecnica da Piave Servizi, 11 aprile 2024 | DI DOMENICO Simone
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | DI DOMENICO Simone
 
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | ROMANO' Davide
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | ROMANO' DavideGiornata Tecnica da Piave Servizi, 11 aprile 2024 | ROMANO' Davide
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | ROMANO' Davide
 
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | DISCIPIO Antonio
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | DISCIPIO AntonioGiornata Tecnica da Piave Servizi, 11 aprile 2024 | DISCIPIO Antonio
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | DISCIPIO Antonio
 
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | SERRA Giorgio
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | SERRA GiorgioGiornata Tecnica da Piave Servizi, 11 aprile 2024 | SERRA Giorgio
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | SERRA Giorgio
 
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | ALBIERO Andrea
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | ALBIERO AndreaGiornata Tecnica da Piave Servizi, 11 aprile 2024 | ALBIERO Andrea
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | ALBIERO Andrea
 
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | CADEI Giovanni
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | CADEI GiovanniGiornata Tecnica da Piave Servizi, 11 aprile 2024 | CADEI Giovanni
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | CADEI Giovanni
 

Migrazione di un sito web mediante tecnica di estrazione dati automatizzata

  • 1. UNIVERSITÀ DEGLI STUDI DI TRIESTE Dipartimento di Ingegneria e Architettura Corso di laurea triennale in Ingegneria dell’Informazione MIGRAZIONE DI UN SITO WEB MEDIANTE TECNICA DI ESTRAZIONE DATI AUTOMATIZZATA Laureando: Relatore: GIULIO ZORZOLI PROF. ING. ALBERTO BARTOLI ANNO ACCADEMICO 2018/2019 1
  • 2. Indice Introduzione 3 Presentazione caso di studio 4 Analisi dei requisiti 5 Progettazione 6 Scelta del linguaggio di programmazione e delle librerie 6 Studio delle pagine HTML per estrarne i dati 6 File Robots.txt 6 File Sitemap 7 Pagine web da cui estrarre i dati di interesse 7 Roadmap 7 Realizzazione 8 Creazione ambiente virtuale 8 Estrazione Dati 8 Manipolazione Dati 12 Aggiunta di link 12 Differenziazione tra journal e conference 13 Aggiunta flag membri del laboratorio 14 Aggiunta delle date 14 Download/upload file allegati 15 Conversione da Json a spreadsheet 17 Creazione delle tabelle 17 Conclusioni 21 Problemi Riscontrati 21 Commento 22 Sitografia 23 Appendice 23 2
  • 3. Introduzione Il sito web del laboratorio di "Machine Learning" dell'Università degli Studi di Trieste è stato creato con l'applicativo web “Google Sites” versione "Classic" (anno 2008). Durante il periodo di tirocinio presso il laboratorio il candidato si è occupato della migrazione del sito utilizzando la nuova versione di "Google Sites" chiamata "New"[14] (anno 2016). La migrazione risulta necessaria a causa dei seguenti motivi: • La versione "Classic" è deprecata e non sarà più supportata dai servizi Google a partire dall'anno 2021 [17]. • Le pagine della versione "Classic" sono fornite mediante protocollo HTTP mentre quelle della versione "New" mediante protocollo HTTPS. • Il design delle pagine della versione "New" presenta elementi e regole CSS moderni conferendo al sito web la proprietà di "Responsive" ed un look meno obsoleto. Sono da rispettare i seguenti vincoli di progetto: 1. Per la realizzazione del nuovo sito web è necessario mantenere l'utilizzo della piattaforma Google Sites. 2. Le informazioni delle pagine "Pubblications" e "News" presentano le liste degli articoli pubblicati dai membri del laboratorio e delle notizie riguardanti il laboratorio stesso. La migrazione non deve causare la perdita delle informazioni già esistenti. Inoltre, è necessaria una soluzione per l'inserimento di nuove pubblicazioni o news. 3. L'unico spazio di web hosting utilizzabile è Google Drive. Applicando i passaggi descritti nella documentazione [18] è stato constatato che non è possibile utilizzare lo strumento di migrazione automatico fornito da Google. Dato l'elevato numero di informazioni presenti nel sito web il candidato ha progettato e sviluppato un Web Crawler, una soluzione software che automatizza l'estrazione dei dati analizzando il codice sorgente HTML di una pagina web in modo programmatico. Poichè non è possibile ospitare un database su Google Drive sorge il problema di quale strutture dati utilizzare per memorizzare i dati estratti delle pagine "Pubblicazioni" e "News". La soluzione proposta prevede l'integrazione del sito con Awesome Table[2], un applicativo web che genera delle views a partire dai dati inseriti in uno spreadsheet Google. Per raggiungere il risultato sono state affrontate le seguenti operazioni da parte del laureando: • Studio delle tecnologie software attuali e individuazione dei linguaggi e librerie più adatti alla funzionalità di web scraping. • Analisi della struttura e sintassi HTML delle pagine web generate dalla piattaforma Google Sites e individuazione delle regole Regex necessarie per la corretta estrazione delle informazioni. • Progettazione e scrittura del Web Crawler in Python (ver 3.6) utilizzando la libreria BeautifulSoup [1] che facilita la navigazione e la ricerca dei dati all'interno del DOM. • Esecuzione del programma, estrazione delle informazioni e inserimento di queste in una struttura dati dizionario esportata in un file JSON. Creazione degli spreadsheet Google a partire da questi dati. • Trasferimento automatizzato delle immagini e dei file allegati mediante interrogazione delle API [5] del servizio Google Drive. • Creazione delle pagine statiche del nuovo sito web utilizzando elementi nativi Google Sites New. Realizzazione delle porzioni dinamiche mediante scrittura di codice HTML, Javascript e CSS. 3
  • 4. Al termine del periodo di tirocinio del laureando il nuovo sito web risulta pronto per la produzione e si trova al seguente indirizzo web https://sites.google.com/view/tesi-zorzoli. Tutto il codice all'interno del presente documento è stato scritto dall'autore salvo diversamente specificato. Presentazione caso di studio Il sito web del laboratorio di “Machine Learning” dell’Università degli Studi di Trieste è stato realizzato con l’applicativo web “Google Sites Classic” (anno 2008) e utilizza Google Drive come servizio di Web Hosting. Esso è costituito da 6 pagine web statiche principali (“Home”, “News”, “People”, “Pubblications”, “Data and Tools”, “Student Opportunities”) che sono fornite mediante protocollo HTTP. Per aggiungere nuove informazioni i membri del laboratorio utilizzano gli elementi nativi Google Sites Classic modificando la pagina web preesistente o creandone direttamente una nuova. Le operazioni di routine consistono nella modifica del testo delle pagine statiche (p.e. aggiunta di personale nella sezione "People") e nella creazione ex novo di pagine web nel momento di pubblicazione di un nuovo articolo (sezione "Publications") o di una notizia inerente al laboratorio (sezione "News"). Nell’anno 2016 l’applicativo Google Sites Classic è stato aggiornato alla versione New. I nuovi costrutti HTML e direttive CSS conferiscono alle pagine web create un look più moderno. Le pagine web create con questa versione sono fornite mediante protocollo HTTPS (HTTP over TLS) sicchè la connessione TCP tra web client dell'utente e web server Google ha le proprietà di Segretezza, Autenticazione, Integrità. La versione Google Sites Classic risulta deprecata e le pagine web create con tale applicativo non saranno più supportate dai servizi Google a partire dall'anno 2021 [17]. Si vuole, inoltre, che le informazioni relative alle pagine "Publications" e "News" non siano più fornite staticamente ma dinamicamente. In questo modo è facilitato l'inserimento di nuovi dati ed è possibile l'utilizzo di tali dati da parte di siti web di terzi. A causa di questi motivi risulta necessario migrare il sito web del laboratorio ad una nuova versione. Sono da rispettare i seguenti vincoli tecnologici: 1. Sviluppo del nuovo sito web mediante utilizzo di Google Sites New. 2. Utilizzo di Google Drive come unico servizio di Web Hosting. 3. Integrazione del nuovo sito web con l'applicativo Awesome Table. 4. Utilizzo di Spreadsheet Google per alimentare le pagine web "Publications" e "News" dinamicamente. Il candidato nel periodo di tirocinio presso il laboratorio si è occupato dello studio, progettazione e realizzazione di una soluzione per migrare il sito web senza perdita di informazioni e rispettando i vincoli tecnologici sopra elencati. 4
  • 5. Analisi dei requisiti Inizialmente il laureando si è preoccupato di studiare l'ambiente Google, gli applicativi web da esso offerti e la struttura del sito web da migrare. Applicando i passaggi descritti nella documentazione [18] è stato constatato che non è possibile utilizzare lo strumento di migrazione automatico fornito da Google. Tale strumento ha funzionalità limitate; non può trasferire tutti i contenuti e costrutti utilizzati, la disposizione di testo e immagini non viene rispettata, gli aspetti grafici mantenuti non sono soddisfacenti. Nel caso del sito in esame l'opzione "Converti nella nuova versione di Sites" non è presente nel pannello di controllo. Risulta quindi necessaria una migrazione dei contenuti da parte del candidato. Il numero di articoli pubblicati ammonta a 95 e quello delle news a 43. Risulta quindi tedioso e problematico estrarre i dati presenti in 138 pagine web e inserirli manualmente in una struttura dati. La soluzione proposta prevede l'automazione di queste operazioni mediante la progettazione e programmazione da parte del candidato di un Web Crawler, un programma software che scansiona sequenzialmente il codice HTML sorgente di una pagina web e, utilizzando le espressioni regolari, estrae i dati di interesse inserendoli in una struttura dati. In accordo con il relatore è stato deciso di scrivere dei web crawler ad hoc per le pagine web del sito in esame. Non si è reso quindi il codice generalizzabile e indipendente dal sito web da analizzare. Alcuni elementi, quali i link delle pagine web e la struttura delle stringhe da analizzare mediante regole regex, sono cablati nel codice. Per rispettare il vincolo n° 1 egli ha studiato l'applicativo Google Sites New ed ha accertato di poter creare pagine web sia utilizzando puro codice HTML che elementi nativi della piattaforma. Il candidato ha individuato un problema di retrocompatibilità con i dispositivi Apple iOS versione 12.3.1 e precedenti che non permette il corretto scorrimento delle pagine web su dispositivi touch screen per pagine generate con porzioni di codice HTML. E' stato quindi scelto di realizzare il nuovo sito con soli elementi nativi Google Sites New. Poichè l'unico servizio di web hosting utilizzabile è Google Drive il laureando ha appurato di poter ospitare il nuovo sito, i file statici e gli allegati su tale servizio e di poterne trasferire i permessi di proprietà in un secondo momento ad un altro utente. In fase di produzione è stata riscontrata quindi una limitazione della piattaforma che non permette il trasferimento di ownership per file formato pdf o zip. Il team del laboratorio avrebbe dovuto effettuare prima il download dei 121 file allegati, poi l'upload di tali file sull'account Google Drive del laboratorio e infine sostituire l'hash identificativo di ogni file nei link presenti nelle pubblicazioni. Data la complessità dell'operazione è stato deciso di lasciare il candidato come proprietario di tutti i file allegati caricati su Google Drive di formato pdf. Per soddisfare il vincolo n° 3 è stata analizzata "Awesome Table"[2], una Web Application che genera codice HTML per rappresentare tabelle a partire dai dati inseriti in uno spreadsheet Google. Per modificare l'aspetto grafico delle tabelle generate il candidato ha utilizzato lo strumento web nativo fornito da Awesome Table e ha appurato di dover scrivere porzioni di codice HTML, CSS e Javascript. L'operazione di migrazione prevede quindi di utilizzare questo applicativo per rappresentare i dati delle pubblicazioni e delle news del laboratorio, salvati in due spreadsheet differenti. 5
  • 6. Progettazione Scelta del linguaggio di programmazione e delle librerie. Il candidato ha esperienza nella programmazione con il linguaggio Python. Egli ha scelto, quindi, di sviluppare il Web Crawler, gli script di manipolazione dati e gli script di download/upload automatizzato degli allegati con questo linguaggio. Le librerie utilizzate sono le seguenti: 1. Requests [6] v2.21.0 per creare connessione HTTP tra client e server. Questa libreria è determinante per poter visualizzare il contenuto di pagine web mediante terminale. 2. BeautifulSoup4 [1] v4.7.1 per poter estrarre dati da una pagina HTML o XML. Questa libreria è il cuore del Web Crawler. Essa memorizza la struttura ad albero del DOM di una pagina web e permette la navigazione attraverso i suoi nodi. Di ogni nodo è possibile visionare il tipo, il tag, il contenuto, il contenuto dei nodi figli o genitori ed effettuare ricerche di elementi chiave. 3. Pandas [3] v0.24.1. Questa libreria inserisce i dati presenti all'interno di un dizionario Python (una struttura dati non ordinata le cui informazioni sono associate ad una parola chiave) in uno spreadsheet formato .xlsx. E' stata utilizzata per convertire i dati estratti dal Web Crawler nel formato richiesto dagli Spreadsheet Google. 4. Google-auth [7] v1.6.3 per poter interagire con le API Google [5] utilizzate per l'upload su Google Drive dei file allegati alle pagine web. Questa libreria utilizza altre librerie native Python come Pickle per potere salvare le credenziali dell'utente sul filesystem e autenticare l'utente con le credenziali Google. Essa è stata sviluppata da Google stessa. 5. Re [8] per poter leggere e manipolare espressioni regolari. Questa libreria viene utilizzata insieme agli oggetti BeautifulSoup per estrarre informazioni. 6. Json [9] per poter esportare i dizionari Python nel formato JSON. L'installazione di queste librerie e delle dipendenze può inquinare l'ambiente lavorativo di un calcolatore. Inoltre si vuole rendere portabile su altri calcolatori il codice scritto dal candidato. Egli ha quindi deciso di creare un ambiente virtuale mediante il modulo Python Venv[13] e di esportare la lista delle dipendenze mediante il comando: Studio delle pagine HTML per estrarne i dati. File Robots.txt Scelte le librerie software da utilizzare è stata analizzata la possibilità di utilizzo di Web Crawler sulle pagine web create con Google Sites. E' stata interrogata la pagina robots.txt[15] che contiene le regole ed i permessi utilizzati dai programmi automatizzati di estrazione dati. E' stato così riscontrata la possibilità di utilizzo di un semplice programma "headless" lanciato da terminale che non deve presentare header User-Agent tipiche di un web browser comune. Il programma non deve utilizzare il browser di sistema per simulare le azioni di un utente ordinario in quanto le pagine web 6 $ pip freeze > requirements.txt
  • 7. da esaminare appartengono alla sezione Allowed e non Forbidden. Si è scelto quindi di utilizzare le librerie sopra elencate piuttosto che sviluppare una soluzione completamente automatizzata col software Selenium[10]. File Sitemap Successivamente è stata analizzata la pagina che rappresenta la struttura del sito web ovvero la Sitemap[16]. Essa è un file in formato XML. E' possibile estrarre da esso tutti i link delle pagine web delle pubblicazioni e delle news mediante scrittura di un web crawler apposito. Pagine web da cui estrarre i dati di interesse. E' stata quindi studiata la sintassi HTML delle pagine web delle pubblicazione e delle news. Sono state individuate le informazioni di interesse e le espressioni regolari necessarie per la loro corretta estrazione. In particolare di ogni pubblicazione interessano: gli autori, il titolo, l'abstract, nome e luogo e data della pubblicazione, link DOI, link Google Scholar, link della pubblicazione, link alle slide di presentazione. Si vuole inoltre che nello spreadsheet di ogni pubblicazione siano presenti dei valori booleani che identificano se un membro del laboratorio è autore della pubblicazione. Di ogni news interessano: titolo, testo della news, data. Roadmap Il candidato ha quindi creato una roadmap per la realizzazione della migrazione: 1. Scrittura di 3 Web Crawlers utilizzando la libreria BeautifulSoup per l'estrazione di: 1. Link di tutte le pubblicazioni e di tutte le news dal sito Sitemap. 2. Informazioni sopra citate dalle pagine web delle pubblicazioni. 3. Informazioni sopra citate dalle pagine web delle news. 2. Esportazione delle informazioni estratte dalla struttura dati nativa Python chiamata dizionario in file formato JSON. 3. Creazione di script per scaricare i 121 allegati formato .pdf sul calcolatore del candidato e caricarli successivamente sul suo account Google Drive utilizzando la libreria Google- auth[7]. 4. Creazione di script per manipolare i dati estratti. In particolare: 1. Aggiunta di un valore booleano che identifica la presenza o meno di ogni membro del laboratorio come autore della pubblicazione. 2. Aggiunta di un valore booleano che identifica se la pubblicazione è il risultato di una conferenza o di un journal. 3. Per le pubblicazioni aggiunta di link che puntano agli allegati presenti nell'account Google Drive del laureando. Per le news sostituzione dei link assoluti che puntano al vecchio sito web con link relativi. Lo stesso per le immagini. 4. Aggiunta di timestamp per ogni elemento delle news. 7
  • 8. 5. Creazione di due spreadsheet (1 per le pubblicazioni, 1 per le news) a partire da un file JSON utilizzando la libreria Pandas. 6. Creazione delle tabelle HTML con AwesomeTable. Scrittura codice HTML, Javascript, CSS utilizzando il framework Bulma. 7. Creazione delle pagine statiche del nuovo sito web utilizzando elementi nativi Google Sites New e integrazione con views di AwesomeTable. Realizzazione Creazione ambiente virtuale Tutti gli script sono stati eseguiti sul calcolatore del candidato. Il SO utilizzato è Linux Ubuntu 17.10. E' necessario l'interprete Python v3.6 e il modulo venv. Per creare un ambiente virtuale si utilizza il seguente comando: Successivamente si attiva l'ambiente virtuale e si installano tutte le librerie sopra citate Estrazione Dati Assicurandosi di avere una connessione Internet attiva, è possibile eseguire il programma main.py che si occupa della creazione ed esecuzione dei 3 Web Crawlers. Al termine dell'esecuzione sono disponibili i file "News.json" e "Pubblications.json" nella directory corrente. Il programma main.py inizialmente crea un web crawler per estrarre i link delle pagine contenenti le pubblicazioni e le news dalla pagina della sitemap. Nota: normalmente il link passato come parametro alla classe Urlspider viene inserito dalla linea di comando. Tuttavia essendo il web crawler scritto ad hoc per una e una sola pagina web si è preferito inserirlo nel codice sorgente direttamente. #main.py from url_spider import UrlSpider from publication_spider import PublicationSpider from news_spider import NewsSpider if __name__ == '__main__': url_spider = UrlSpider('http://machinelearning.inginf.units.it/system/feeds/sitemap') url_spider.crawl() url_spider.parse_urls() url_spider.get_publications_urls() url_spider.get_news_urls() 8 $ python -m venv venv $ source venv/bin/activate $ pip install -r requirements.txt $ python main.py
  • 9. Successivamente crea ed esegue un web crawler per ogni pagina di news e esporta i dati nel file News.json. out = [] for url in url_spider.news_urls: news_spider = NewsSpider(url) data = news_spider.crawl() out.append(data) NewsSpider.export_json(out) Lo stesso per ogni pagina di pubblicazione. out = [] for url in url_spider.publications_urls: pub_spider = PublicationSpider(url) data = pub_spider.crawl() out.append(data) PublicationSpider.export_json(out) I 3 web crawlers scritti hanno metodi simili. Si discute di seguito l'intera classe UrlSpider e i metodi più rilevanti di PublicationSpider e NewsSpider. Per vedere in dettaglio il codice si rimanda il lettore all'appendice (pag. 24). Inizialmente si importano le librerie da utilizzare e si inizializza l'istanza della classe utilizzando il link inserito come argomento al momento della creazione. #url_spider.py import requests import re from bs4 import BeautifulSoup as Soup class UrlSpider(object): def __init__(self, url=''): self.uri = url Si definisce il metodo che: 1. Preleva il contenuto dell'intera sitemap e la salva in un oggetto di tipo nativo BeautifulSoup. def crawl(self): req = requests.get(self.uri) self.soup = Soup(req.content, "lxml") 2. Estrae dalla pagina tutti i link presenti nelle sottosezioni location ("loc") del file XML. 9
  • 10. def parse_urls(self): self.urls = [x.contents[0] for x in self.soup.fnd_all("loc")] 3. Estrae dai link solo quelli relativi alle pubblicazioni o alle news utilizzando regole RegEx e li inserisce in delle variabili di classe. def get_publications_urls(self): self.publications_urls = [x for x in self.urls if re.match(r".+/publications/. +/", x) ] def get_news_urls(self): self.news_urls = [x for x in self.urls if re.match(r'.+/news/.+', x)] Le pagine con le pubblicazioni hanno molte informazioni di interesse. Nonostante siano tutte state create con Google Sites Classic esse presentano diversa struttura HTML l'una dall'altra. Sono stati quindi utilizzati costrutti "try,except" per gestire le eccezioni. A titolo di esempio si mostra la porzione di codice utilizzata per prelevare la data e il luogo della conferenza, se presenti: #publication_spider.py ... def parse(self, soup): data = {} data["old_url"] = self.url data["title"] = soup.fnd(id="sites-page-title").contents[0].string ... # Location and year section try: year_and_location = soup.fnd(id="sites-canvas- main").fnd("li").contents[1].string year = re.match(r'D+(d+).+', str(year_and_location).strip()).group(1) data["conference_year"] = unicodedata.normalize("NFKD", year) location = re.match(r'D+d+, (.+)', str(year_and_location).strip()).group(1) data["conference_location"] = unicodedata.normalize("NFKD", location) except (AttributeError, IndexError): try: year_and_location = soup.fnd(id="sites-canvas- main").fnd("li").contents[0].text year = re.match(r'.+D+(d+).+', str(year_and_location).strip()).group(1) data["conference_year"] = unicodedata.normalize("NFKD", year) location = re.match(r'.+D+d+, (.+)', str(year_and_location).strip()).group(1) data["conference_location"] = unicodedata.normalize("NFKD", location) except AttributeError: year_and_location = soup.fnd(id="sites-canvas-main").fnd("li").text 10
  • 11. year = re.match(r'.+D+(d+).?', str(year_and_location).strip()).group(1) data["conference_year"] = unicodedata.normalize("NFKD", year) try: location = re.match(r'.+D+d+,(.+)', str(year_and_location).strip()).group(1) data["conference_location"] = unicodedata.normalize("NFKD", location) except AttributeError: data["conference_location"] = None Per estrarre il link di DOI, Google Scholar e pubblicazione si usano le seguenti regole: ... try: links = soup.fnd(id="sites-canvas-main").fndAll("li")[2].fndAll("a") except IndexError: links = [] data["scholar"] = None data["doi"] = None data["publisher"] = None for link in links: if (re.match(r'.+scholar.google.+', link['href'])): data["scholar"] = link['href'] elif (re.match(r'.+doi.org.+', link['href'])): data["doi"] = link['href'] else: data["publisher"] = link['href'] Per estrarre gli allegati: ... try: div_with_links = soup.fndAll("div", class_="sites-attachments-name") pdf_links = [] for link in div_with_links: pdf_links.append(link.a['href']) except: pdf_links = [] data["pdf_links"] = pdf_links Infine per esportare il dizionario nel file formato JSON si definisce e invoca il metodo statico export_json: ... 11
  • 12. @staticmethod def export_json(data): j = json.dumps(data) f = open("publications.json","w") f.write(j) f.close() Il file news_spider.py è analogo a pubblication_spider.py. Risulta di interesse la regola per estrarre i link delle immagini: # news_spiders.py class NewsSpider(object): .... images = [] for image in soup.fnd(id="sites-canvas-main-content").fndAll("img"): try: images.append(image['src']) except: pass data["images"] = images Manipolazione Dati Le date delle news non sono nel formato desiderato per essere inseriti nello spreadsheet. Esse presentano una struttura non standard che le fa apparire come tipo stringa piuttosto che tipo Data; non possono quindi essere utilizzate come parametro di ordinamento. Si vuole inoltre aggiungere ulteriori informazioni ad ogni pubblicazione e news: 1. Aggiunta di elementi persi durante l'estrazione dati quali hyperlink e immagini. 2. Valore booleano per determinare se la pubblicazione è il risultato dell'esposizione in una conferenza o in un articolo accademico. 3. Valore booleano per determinare la partecipazione di ogni membro del laboratorio alla pubblicazione di un articolo. Sono stati quindi scritti degli script di manipolazione dati. Aggiunta di link Per avere il contenuto delle pagine web estratte con il web crawler si perdono elementi nativi HTML come per esempio gli hyperlink. Il primo script proposto risolve questo probelma sostituendo delle porzioni di stringa con i link HTML. Si riporta solo la parte di codice per inserire le immagini nelle news. #add_links.py ... def add_images_to_news(): 12
  • 13. out = [] with open("news.json") as news: data = json.load(news) for event in data: try: if len(event["images"]): for image in event["images"]: event["body"] = "<img src="%s">" % image + event["body"] except KeyError: pass out.append(event) j = json.dumps(out) f = open("news.json","w") f.write(j) f.close() Differenziazione tra journal e conference Ogni pubblicazione può essere il risultato dell'esposizione in una conferenza o in una rivista accademica. Si vogliono introdurre dei valori booleani che identifichino questa differenziazione. Lo script proposto risolve questo problema analizzando l'url della pagina da cui la pubblicazione è stata estratta. #add_journal_or_conference.py import json import re if __name__ == '__main__': out = [] with open("publications.json") as f: data = json.load(f) for pub in data: pub['conference'] = 0 pub['journal'] = 0 if re.match(r'.*/international-conference-publications/.*', pub["old_url"]): pub['conference'] = 1 elif re.match(r'.*/international-journal-publications/.*', pub["old_url"]): pub['journal'] = 1 out.append(pub) j = json.dumps(out) f = open("publications.json","w") f.write(j) f.close() 13
  • 14. Aggiunta flag membri del laboratorio Per ogni pubblicazione è stata estratta una lista contenente gli autori. Di ogni pubblicazione si vuole inserire un valore booleano che identifichi se il membro del laboratorio è tra gli autori. Lo script proposto risolve questo problema analizzando la stringa "authors" e verificando che il nome degli autori sia contenuto in essa. #add_users.py import json import re if __name__ == '__main__': out = [] with open("publications.json") as publications: data = json.load(publications) for publication in data: try: publication["medvet"] = 1 if re.match(r'.*Medvet.*', publication["authors"]) else 0 publication["bartoli"] = 1 if re.match(r'.*Bartoli.*', publication["authors"]) else 0 publication["tarlao"] = 1 if re.match(r'.*Tarlao.*', publication["authors"]) else 0 publication["de_lorenzo"] = 1 if re.match(r'.*De Lorenzo.*', publication["authors"]) else 0 publication["talamini"] = 1 if re.match(r'.*Talamini.*', publication["authors"]) else 0 publication["panflo"] = 1 if re.match(r'.*Panflo.*', publication["authors"]) else 0 except KeyError: ... j = json.dumps(out) f = open("publications.json","w") f.write(j) f.close() Aggiunta delle date Le date delle pagine news estratte dal web crawler non sono compatibili con il formato utilizzato dagli spreadsheet Google. Lo script proposto riesamina le date estraendole nuovamente e le converte utilizzando la libreria Python nativa datetime[12]. A titolo d'esempio la data "Sep 10, 2018, 4:36 PM” viene trasformata in “2018-09-10 16:36:00”. #add_timestamp_news.py ... 14
  • 15. from datetime import datetime if __name__ == '__main__': ... out = [] with open("news.json") as news: data = json.load(news) for url in url_spider.news_urls: content = requests.get(url).content soup = Soup(content, "lxml") date = soup.fnd("span", class_='announcementsPostTimestamp').fnd("span").text date = re.sub(r', d+:d+ [A-Z]+', "", date) datetime_object = datetime.strptime(date, '%b %d, %Y').strftime('%x') for event in data: if event["old_url"] == url: event["date"] = datetime_object out.append(event) j = json.dumps(out) f = open("news.json","w") f.write(j) f.close() Download/upload file allegati Ultima operazione di manipolazione dati consiste nel download dei file allegati sul calcolatore del candidato per poi fare l'upload sull'account Google Drive. Lo script proposto oltre a eseguire queste operazioni inserisce all'interno del file contenente le pubblicazioni i link che puntano ai file caricati. Parte di questo script è stato realizzato utilizzando codice d'esempio fornito nella documentazione delle API Google [5]. Si riportano solo i frammenti di codice rilevanti rimandando il lettore all'appendice (pag. 32) qualora volesse analizzare il codice in dettaglio. #download_upload_pdfs.py ... from urllib.request import urlopen from urllib.request import urlretrieve from googleapiclient.discovery import build from googleapiclient.http import MediaFileUpload from google_auth_oauthlib.fow import InstalledAppFlow from google.auth.transport.requests import Request # If modifying these scopes, delete the fle token.pickle. SCOPES = ['https://www.googleapis.com/auth/drive'] def download_pdfs(): 15
  • 16. creds = None # The fle token.pickle stores the user's access and refresh tokens, and is # created automatically when the authorization fow completes for the frst # time. if os.path.exists('token.pickle'): with open('token.pickle', 'rb') as token: creds = pickle.load(token) # If there are no (valid) credentials available, let the user log in. ... service = build('drive', 'v3', credentials=creds) ... ... ... ... article_url = publication["pdf_dl_link_2"] slides_url = publication["pdf_dl_link_1"] article_id = download_and_upload_to_drive(service, article_url) publication['article_dl_link'] = 'https://drive.google.com/uc? export=download&id=%s' % article_id slides_id = download_and_upload_to_drive(service, slides_url) publication['slides_dl_link'] = 'https://drive.google.com/uc?export=download&id=%s' % slides_id … … def download_and_upload_to_drive(service, url): remotefle = urlopen(url) info = remotefle.info()['Content-Disposition'] value, params = cgi.parse_header(info) flename = params["flename"] urlretrieve(url, "pdfs//%s" % flename) folder_id = FOLDER_ID fle_metadata = {'name': flename, 'parents': [folder_id] } media = MediaFileUpload('pdfs/%s' % flename, mimetype='application/pdf') fle = service.fles().create(body=fle_metadata, media_body=media ).execute() return fle.get('id') if __name__ == '__main__': 16
  • 17. download_pdfs() Conversione da Json a spreadsheet Il vincolo tecnologico n° 3 impone di utilizzare l'applicativo AwesomeTable per fornire dinamicamente i dati delle pubblicazioni e news. Tale applicativo necessita che i dati siano inseriti all'interno di uno spreadsheet Google. Sorge quindi il problema di come inserire i dati estratti presenti nel file formato JSON in uno spreadsheet. Lo script proposto risolve questo problema utilizzando la libreria Pandas. #to_spreadsheet.py import json import pandas as pd if __name__ == '__main__': with open("publications.json") as f: data = json.load(f) pub_dataFrame = pd.DataFrame(data) pub_dataFrame = pub_dataFrame.sort_values(by=['conference_year'], ascending=False) writer = pd.ExcelWriter('publications.xlsx', engine='xlsxwriter', options={'strings_to_urls': False}) pub_dataFrame.to_excel(writer, sheet_name='Sheet1') writer.save() with open("news.json") as f: data = json.load(f) pub_dataFrame = pd.DataFrame(data) pub_dataFrame['date']=pd.to_datetime(pub_dataFrame['date']) pub_dataFrame = pub_dataFrame.sort_values(by=['date'], ascending=False) writer = pd.ExcelWriter('news.xlsx', engine='xlsxwriter', options={'strings_to_urls': False}) pub_dataFrame.to_excel(writer, sheet_name='Sheet1') writer.save() Completata l'esecuzione dello script mediante comando compaiono sulla directory corrente i file publications.xlsx e news.xlsx. Questi file sono stati caricati sull'account Google Drive che contiene il nuovo sito web. A queste punto si procede alla creazione delle tabelle HTML utilizzando l'applicativo web AwesomeTable. Creazione delle tabelle Dalla homepage di Awesome Table [19] si seleziona l'opzione new view. Una view è una rappresentazione di una porzione di pagina web HTML. E' stato deciso di creare 3 views in totale. 17 $ python to_spreadsheet.py
  • 18. Le prime due hanno come sorgente lo spreadsheet delle pubblicazioni, la terza ha come sorgente lo spreadsheet delle news. Le 3 views sono così costituite: 1. Contiene tutte le pubblicazioni e alimenta la pagina publications del nuovo sito web. 2. Contiene le pubblicazioni più rilevanti. Questa view è alimentata dallo stesso spreadsheet del punto 1 ma da un foglio diverso che, utilizzando la funzione filter, rappresenta una sottoparte delle pubblicazioni. Essa alimenta la home page del nuovo sito web. 3. Contiene tutte le news del laboratorio. Assieme alla view del punto 2 alimenta la homepage del nuovo sito. Lo stile generico di rappresentazione delle views può essere modificato utilizzando lo strumento nativo disponibile dall'applicativo web. Tra le varie opzioni fornite da AwesomeTable è stato scelto di rappresentare le tabelle come Cards. Tale rappresentazione risulta più pulita e i testi sono più facili da leggere. Per poter dare al programmatore la possibilità di personalizzare le views, AwesomeTable consente di aggiungere codice HTML, CSS e Javascript. Inoltre è possibile includere alcune librerie o framework sia CSS che Javascript. Il candidato si è occupato di scrivere una struttura HTML adeguata per le 3 views. Per inserire i dati presenti all'interno di una colonna dello spreadsheet si usa una sintassi speciale {{ColumnName}}. Per quanto riguarda il design grafico delle views il candidato ha scelto di utilizzare il framework CSS Bulma. Integrando Bulma si integrano automaticamente le icone della libreria FontAwesome[11]. Il laureando ha quindi scritto codice CSS basandosi sulla documentazione di Bulma. Lo stile risulta pulito e moderno. Si allega codice HTML e CSS in appendice (pag. 35) qualora il lettore sia interessato. 18 Illustration 1: Rappresentazione delle pubblicazioni mediante Cards
  • 19. Di default si vuole che la sezione abstract non sia visibile. Si vuole che diventi visibile mediante un click sulla scritta "abstract". Per ottenere tale effetto grafico è stato scritto il seguente codice in javascript che modifica le classi html delle sezioni interessate: // publications.js function toggleAbstractButton(button){ let card_container = button.parentElement.parentElement; toggleAbstract(card_container); } function toggleAbstract(card_container){ let abs_container = card_container.getElementsByClassName("abstract_container") [0]; abs_container.classList.toggle("visible"); } function collapseAbstract(link){ let card_container = link.parentElement.parentElement.parentElement.parentElement; toggleAbstract(card_container); } Per visualizzare correttamente l'icona che rappresenta la J di journal o la C di conference si sono utilizzate le seguenti regole HTML e CSS: <!-- publications.html --> <div class="container"> <h3 class="title"> <span class="square_icon conference_fag" data-display- fag="{{conference_fag}}">C</span> <span class="square_icon journal_fag" data-display- fag="{{journal_fag}}">J</span> {{Title}} ... /* publications.css */ [data-display-fag="0"]{ display: none; } [data-display-fag="1"]{ display: inline-block; } 19
  • 20. ricordando che {{conference_fag}} e {{journal_fag}} rappresentano i valori booleani presenti nello spreadsheet publications. Si allegano screenshot del risultato dell'operazione di graphic design sulle views. Per le componenti statiche delle pagine web si sono utilizzati gli elementi nativi forniti da Google Sites New. Si allega screenshot di parte dell'home page. 20 Illustration 2: Homepage. Risultato dell'operazione di design grafico sulle views di pubblicazioni importanti e news. Illustration 3: Publications. Risultato dell'operazione di design grafico sulla view delle pubblicazioni.
  • 21. Conclusioni Problemi Riscontrati Sono stati riscontrati diversi problemi durante la realizzazione della migrazione del sito web. Si fornisce una lista esaustiva di ogni problema e di come esso è stato risolto: 1. Struttura HTML delle pagine web del sito da migrare che variano da pagina a pagina. La realizzazione di una soluzione di estrazione dati automatizzata richiede una precisa struttura HTML delle pagine da analizzare. Le espressioni regolari permettono ad un web crawler di estrarre una stringa di caratteri, per quanto complessa, ma la struttura HTML deve essere mantenuta uguale in ogni pagina. L'aggiunta di un singolo carattere non previsto di spaziatura, quale ad esempio uno spazio o una virgola, possono causare un errore e mandare in crash l'intero programma. Per raggiungere il risultato finale il candidato ha dovuto procedere per tentativi successivi evolvendo le espressioni regolari errore dopo errore fino a sviluppare una espressione capace di estrarre le stesse informazioni da pagine sorgenti con strutture HTML diverse. Le espressioni regolari hanno richiesto una linea di codice nel caso migliore (estrazioni link di Google Scholar e DOI) e 20 nel caso peggiore(estrazione dell'anno e locazione della conferenza). 2. Blocco dei web crawler da parte dei server Google. Seguendo le regole del file robots.txt il candidato ha appurato di poter realizzare l'estrazione dati automatizzata. I server Google per prevenire attachi informatici di tipo DDOS limitano il numero di request che possono essere effettuate da programmi headless. A causa del problema n° 1 al candidato è capitato di dover effettuare più volte richieste di tipo GET indirizzate verso il sito da migrare. Inizialmente Google blocca l'operazione di web scraping richiedendo l'inserimento di un codice Captcha. Il candidato quindi ha utilizzato le prime 21 Illustration 4: Homepage.
  • 22. iterazioni dei web crawler per salvare la struttura HTML delle pagine web in dei file locali in modo da poter effettuare le simulazioni di estrazione dati su tale copie. 3. Bug di interazione tra sito e dispositivi con sistema operativo iOS v12.3.1 o precedenti per pagine generate con snippet HTML ed elementi non nativi Google Sites. Si vuole che il nuovo sito web sia formato "Responsive" per ogni dispositivo utilizzato. Inizialmente il candidato ha sviluppato le pagine statiche del sito web in HTML e CSS. Questo permette maggiori opzioni di personalizzazione per quanto riguarda il design grafico. Durante la progettazione il candidato ha usufruito del servizio di emulazione dipositivi nativo di Google Chrome. La visione del sito con ogni dispositivo e sistema operativo emulato non presenteva nessun tipo di problema. In produzione il candidato ha provato a visionare il nuovo sito su un iPhone 7 (iOS v12.3.1) ed è stato riscontrato un bug che non permetteva il corretto scorrimento delle pagine web. Il bug è riproducibile su qualsiasi dispositivo (smartphone, tablet ecc..) con tale versione di sistema operativo o precedente. Esso risulta già segnalato [20] agli sviluppatori Google ma non è stato ancora risolto. Il candidato quindi ha dovuto reimplementare le pagine web utilizzando gli elementi nativi Google Sites New. In tal mondo il bug non si è manifestato. 4. Impossibilità di trasferimento di ownership per file pdf e zip tra account Google Drive. Il candidato inizialmente ha utilizzato come servizio di Web Hosting il proprio account Google Drive. L'idea era quella di trasferire l'ownership in fase di produzione del sito web, degli spreadsheet, dei file statici (immagini, css, javascript) e degli allegati ai membri del laboratorio. In produzione il candidato ha scoperto che Google non permette il trasferimento di ownership di file formato PDF e zip. Il direttore del Laboratorio avrebbe dovuto quindi personalmente scaricare gli allegati sul proprio calcolatore, eseguire l'upload sull'account Google Drive del laboratorio e modificare i link degli spreadsheet delle pubblicazioni utilizzando gli script realizzati dal laureando. Il team del laboratorio ha deciso di lasciare gli allegati pdf nell'account Google Drive del candidato. Commento Nonostante i problemi sopra citati al termine del periodo di tirocinio presso il laboratorio la migrazione del sito web risulta completata. Gli obiettivi primari, ossia scrittura del nuovo sito web con Google Sites New e conservazione delle informazioni relative alle pubblicazioni e news, risultano raggiunti e i vincoli tecnologici sono rispettati. Il candidato ha sviluppato un totale di 515 righe di codice Python diviso in 11 programmi, 98 di codice HTML, 91 di codice CSS, 33 di codice Javascript per un totale di 737 linee di codice effettivo utilizzato (questo conteggio non considera il codice HTML e CSS scritto previa scoperta del bug grafico iOS). Per quanto riguarda l'operazione extra di programmazione grafica il candidato non si ritiene soddisfatto del lavoro compiuto. La progettazione del design grafico non è una scienza esatta e quindi quantificare e valutare il lavoro svolto in tale ambito risulta opinabile. Il laureando ritiene tuttavia che il nuovo sito web sia troppo minimale ed essenziale. Egli inizialmente aveva intenzione di integrare effetti grafici complessi mediante librerie CSS e Javascript ma non aveva previsto il riscontro del bug grafico dei dispositivi iOS in fase di produzione che limita all'ultilizzo di soli elementi Google Sites New. In generale il candidato si ritiene soddisfatto del lavoro svolto. Egli ha avuto l'opportunità di sviluppare codice in oltre 4 linguaggi di programmazione diversi, di lavorare con tecnologie Google 22
  • 23. con le quali non aveva familiarità e di approfondire le proprie conoscenze nell'ambito del Data Mining utilizzando la tecnica di Web Scraping automatizzata. Il sito web è pronto per la produzione e può essere visionato al seguente URL: https://sites.google.com/view/tesi-zorzoli. Sitografia [1] BeautifulSoup https://www.crummy.com/software/BeautifulSoup/bs4/doc/ [2] Awesome Table https://support.awesome-table.com/hc/en-us [3] Pandas https://pandas.pydata.org/pandas-docs/stable/ [4] Bulma https://bulma.io/documentation/ [5] Google API https://developers.google.com/drive/api/v3/quickstart/python [6] Requests https://2.python-requests.org/en/master/ [7] Google Auth https://google-auth- oauthlib.readthedocs.io/en/latest/reference/google_auth_oauthlib.flow.html [8] Re https://docs.python.org/3.6/library/re.html [9] Json https://docs.python.org/3/library/json.html [10] Selenium https://selenium-python.readthedocs.io/ [11] FontAwesome https://fontawesome.com/how-to-use/on-the-web/referencing-icons/basic-use [12] Datetime https://docs.python.org/3.6/library/datetime.html [13] Venv https://docs.python.org/3/library/venv.html [14] Google Sites New https://support.google.com/sites/?hl=en#topic=7184580 [15] File Robots.txt http://machinelearning.inginf.units.it/robots.txt [16] File Sitemap http://machinelearning.inginf.units.it/system/feeds/sitemap [17] Google Suite Updates https://gsuiteupdates.googleblog.com/2019/01/google-sites- improvements-new-roadmap.html [18] Google Sites Support https://support.google.com/sites/answer/7035197 [19] Awesome Table Homepage https://awesome-table.com/ [20] Bug Google Sites iOS https://support.google.com/sites/forum/AAAATAKBXDQZjNKM9-- D3A/?hl=en&msgid=DJWBua95AAAJ&gpf=d/msg/sites/ZjNKM9--D3A/DJWBua95AAAJ Appendice Si inserisce in questa sezione tutto il codice sorgente scritto. #main.py from url_spider import UrlSpider from publication_spider import PublicationSpider from news_spider import NewsSpider if __name__ == '__main__': url_spider = UrlSpider('http://machinelearning.inginf.units.it/system/feeds/sitemap') url_spider.crawl() url_spider.parse_urls() url_spider.get_publications_urls() url_spider.get_news_urls() 23
  • 24. out = [] for url in url_spider.news_urls: news_spider = NewsSpider(url) data = news_spider.crawl() out.append(data) NewsSpider.export_json(out) out = [] for url in url_spider.publications_urls: pub_spider = PublicationSpider(url) data = pub_spider.crawl() out.append(data) PublicationSpider.export_json(out) #url_spider.py import requests import re from bs4 import BeautifulSoup as Soup class UrlSpider(object): def __init__(self, url=''): self.uri = url def crawl(self): req = requests.get(self.uri) self.soup = Soup(req.content, "lxml") def parse_urls(self): self.urls = [x.contents[0] for x in self.soup.fnd_all("loc")] def get_publications_urls(self): self.publications_urls = [x for x in self.urls if re.match(r".+/publications/. +/", x) ] def get_news_urls(self): self.news_urls = [x for x in self.urls if re.match(r'.+/news/.+', x)] def get_urls(self): return self.urls #publication_spider.py import requests import re import json import unicodedata from bs4 import BeautifulSoup as Soup 24
  • 25. class PublicationSpider(object): def __init__(self, url=''): self.url = url def crawl(self): req = requests.get(self.url) soup = Soup(req.content, "lxml") return self.parse(soup) def parse(self, soup): data = {} data["old_url"] = self.url data["title"] = soup.fnd(id="sites-page-title").contents[0].string print(self.url) try: conference_name = soup.fnd(id="sites-canvas- main").fnd("li").contents[0].string if conference_name is None: conference_name = soup.fnd(id="sites-canvas- main").fnd("li").contents[0].b.string data["conference_name"] = unicodedata.normalize("NFKD", conference_name) except AttributeError: # This page is diferent from others therefore it needs special handling. return {} # Location and year section try: year_and_location = soup.fnd(id="sites-canvas- main").fnd("li").contents[1].string year = re.match(r'D+(d+).+', str(year_and_location).strip()).group(1) data["conference_year"] = unicodedata.normalize("NFKD", year) location = re.match(r'D+d+, (.+)', str(year_and_location).strip()).group(1) data["conference_location"] = unicodedata.normalize("NFKD", location) except (AttributeError, IndexError): try: year_and_location = soup.fnd(id="sites-canvas- main").fnd("li").contents[0].text year = re.match(r'.+D+(d+).+', str(year_and_location).strip()).group(1) data["conference_year"] = unicodedata.normalize("NFKD", year) location = re.match(r'.+D+d+, (.+)', str(year_and_location).strip()).group(1) data["conference_location"] = unicodedata.normalize("NFKD", location) 25
  • 26. except AttributeError: year_and_location = soup.fnd(id="sites-canvas- main").fnd("li").text year = re.match(r'.+D+(d+).?', str(year_and_location).strip()).group(1) data["conference_year"] = unicodedata.normalize("NFKD", year) try: location = re.match(r'.+D+d+,(.+)', str(year_and_location).strip()).group(1) data["conference_location"] = unicodedata.normalize("NFKD", location) except AttributeError: data["conference_location"] = None data["authors"] = unicodedata.normalize("NFKD", soup.fnd(id="sites- canvas-main").fndAll("li")[1].text) try: links = soup.fnd(id="sites-canvas-main").fndAll("li")[2].fndAll("a") except IndexError: links = [] data["scholar"] = None data["doi"] = None data["publisher"] = None for link in links: if (re.match(r'.+scholar.google.+', link['href'])): data["scholar"] = link['href'] elif (re.match(r'.+doi.org.+', link['href'])): data["doi"] = link['href'] else: data["publisher"] = link['href'] body = soup.fnd(id="sites-canvas-main").div.text.replace("n", "") data["body"] = unicodedata.normalize("NFKD", body) try: div_with_links = soup.fndAll("div", class_="sites-attachments-name") pdf_links = [] for link in div_with_links: pdf_links.append(link.a['href']) except: pdf_links = [] data["pdf_links"] = pdf_links try: div_with_dl_links = soup.fndAll("div", id=re.compile("^attachment- download-wuid.*")) pdf_dl_links = [] for link in div_with_dl_links: 26
  • 27. pdf_dl_links.append('http://machinelearning.inginf.units.it' + link.a['href']) except: pdf_dl_links = [] data["pdf_dl_links"] = pdf_dl_links links = [] for link in soup.fnd(id="sites-canvas-main-content").fndAll("a"): try: links.append((link['href'], link.text)) except KeyError: pass data["other_links"] = links return data @staticmethod def export_json(data): j = json.dumps(data) f = open("publications.json","w") f.write(j) f.close() #news_spider.py import json import re import requests import unicodedata from bs4 import BeautifulSoup as Soup class NewsSpider(object): def __init__(self, url=''): self.url = url def crawl(self): req = requests.get(self.url) soup = Soup(req.content, "lxml") return self.parse(soup) def parse(self, soup): data = {} data["old_url"] = self.url data["title"] = soup.fnd(id="sites-page-title").text print(self.url) 27
  • 28. data["body"] = unicodedata.normalize("NFKD", soup.fnd(id="sites-canvas- main-content").text.replace("n", "")) links = [] for link in soup.fnd(id="sites-canvas-main-content").fndAll("a"): try: links.append((link['href'], link.text)) except KeyError: pass data["links"] = links images = [] for image in soup.fnd(id="sites-canvas-main-content").fndAll("img"): try: images.append(image['src']) except: pass data["images"] = images return data @staticmethod def export_json(data): j = json.dumps(data) f = open("news.json","w") f.write(j) f.close() #add_users.py import json import re if __name__ == '__main__': out = [] with open("publications.json") as publications: data = json.load(publications) for publication in data: try: publication["medvet"] = 1 if re.match(r'.*Medvet.*', publication["authors"]) else 0 publication["bartoli"] = 1 if re.match(r'.*Bartoli.*', publication["authors"]) else 0 publication["tarlao"] = 1 if re.match(r'.*Tarlao.*', publication["authors"]) else 0 publication["de_lorenzo"] = 1 if re.match(r'.*De Lorenzo.*', publication["authors"]) else 0 publication["talamini"] = 1 if re.match(r'.*Talamini.*', publication["authors"]) else 0 publication["panflo"] = 1 if re.match(r'.*Panflo.*', publication["authors"]) else 0 except KeyError: 28
  • 29. publication["medvet"] = 0 publication["bartoli"] = 0 publication["tarlao"] = 0 publication["de_lorenzo"] = 0 publication["talamini"] = 0 publication["panflo"] = 0 out.append(publication) j = json.dumps(out) f = open("publications.json","w") f.write(j) f.close() #add_journal_or_conference.py import json import re if __name__ == '__main__': out = [] with open("publications.json") as f: data = json.load(f) for pub in data: pub['conference'] = 0 pub['journal'] = 0 if re.match(r'.*/international-conference-publications/.*', pub["old_url"]): pub['conference'] = 1 elif re.match(r'.*/international-journal-publications/.*', pub["old_url"]): pub['journal'] = 1 out.append(pub) j = json.dumps(out) f = open("publications.json","w") f.write(j) f.close() #add_links.py import json import re def add_links_to_pubs(): out = [] with open("publications.json") as publications: data = json.load(publications) for publication in data: try: if len(publication["other_links"]): for link in publication["other_links"]: 29
  • 30. publication["body"] = publication["body"].replace(link[1], "<a href="%s" target="_blank">%s</a>" % (link[0], link[1])) except KeyError: pass out.append(publication) j = json.dumps(out) f = open("publications.json","w") f.write(j) f.close() def add_links_to_news(): out = [] with open("news.json") as news: data = json.load(news) for event in data: try: if len(event["links"]): for link in event["links"]: if link[1] is not '': event["body"] = event["body"].replace(link[1], "<a href="%s" target="_blank"> %s</a>" % (link[0], link[1])) except KeyError: pass out.append(event) j = json.dumps(out) f = open("news.json","w") f.write(j) f.close() def add_images_to_news(): out = [] with open("news.json") as news: data = json.load(news) for event in data: try: if len(event["images"]): for image in event["images"]: event["body"] = "<img src="%s">" % image + event["body"] except KeyError: pass out.append(event) j = json.dumps(out) f = open("news.json","w") f.write(j) 30
  • 31. f.close() if __name__ == '__main__': add_links_to_pubs() add_links_to_news() add_images_to_news() #add_timestamp_news.py from bs4 import BeautifulSoup as Soup import json import re import requests from datetime import datetime from url_spider import UrlSpider if __name__ == '__main__': url_spider = UrlSpider('http://machinelearning.inginf.units.it/system/feeds/sitemap') url_spider.crawl() url_spider.parse_urls() url_spider.get_news_urls() out = [] with open("news.json") as news: data = json.load(news) for url in url_spider.news_urls: content = requests.get(url).content soup = Soup(content, "lxml") date = soup.fnd("span", class_='announcementsPostTimestamp').fnd("span").text date = re.sub(r', d+:d+ [A-Z]+', "", date) datetime_object = datetime.strptime(date, '%b %d, %Y').strftime('%x') print(datetime_object) for event in data: if event["old_url"] == url: event["date"] = datetime_object out.append(event) j = json.dumps(out) f = open("news.json","w") f.write(j) f.close() #download_upload_pdfs.py 31
  • 32. from __future__ import print_function import json import re from urllib.request import urlopen from urllib.request import urlretrieve import cgi import os import pickle import os.path from googleapiclient.discovery import build from googleapiclient.http import MediaFileUpload from google_auth_oauthlib.fow import InstalledAppFlow from google.auth.transport.requests import Request # If modifying these scopes, delete the fle token.pickle. SCOPES = ['https://www.googleapis.com/auth/drive'] def download_pdfs(): creds = None # The fle token.pickle stores the user's access and refresh tokens, and is # created automatically when the authorization fow completes for the frst # time. if os.path.exists('token.pickle'): with open('token.pickle', 'rb') as token: creds = pickle.load(token) # If there are no (valid) credentials available, let the user log in. if not creds or not creds.valid: if creds and creds.expired and creds.refresh_token: creds.refresh(Request()) else: fow = InstalledAppFlow.from_client_secrets_fle( 'credentials.json', SCOPES) creds = fow.run_local_server() # Save the credentials for the next run with open('token.pickle', 'wb') as token: pickle.dump(creds, token) service = build('drive', 'v3', credentials=creds) out = [] with open("publications.json") as publications: data = json.load(publications) for publication in data: if publication["pdf_dl_link_1"]: if publication["pdf_dl_link_2"]: article_url = publication["pdf_dl_link_2"] slides_url = publication["pdf_dl_link_1"] article_id = download_and_upload_to_drive(service, article_url) 32
  • 33. publication['article_dl_link'] = 'https://drive.google.com/uc? export=download&id=%s' % article_id publication['article_view_link'] = 'https://drive.google.com/fle/d/%s/view' % article_id slides_id = download_and_upload_to_drive(service, slides_url) publication['slides_dl_link'] = 'https://drive.google.com/uc? export=download&id=%s' % slides_id publication['slides_view_link'] = 'https://drive.google.com/fle/d/%s/view' % slides_id else: article_url = publication["pdf_dl_link_1"] article_id = download_and_upload_to_drive(service, article_url) publication['article_dl_link'] = 'https://drive.google.com/uc? export=download&id=%s' % article_id publication['article_view_link'] = 'https://drive.google.com/fle/d/%s/view' % article_id publication['slides_dl_link'] = '' publication['slides_view_link'] = '' else: print('No article') publication['article_dl_link'] = '' publication['article_view_link'] = '' publication['slides_dl_link'] = '' publication['slides_view_link'] = '' out.append(publication) j = json.dumps(out) f = open("publications.json","w") f.write(j) f.close() def download_and_upload_to_drive(service, url): remotefle = urlopen(url) info = remotefle.info()['Content-Disposition'] value, params = cgi.parse_header(info) flename = params["flename"] urlretrieve(url, "pdfs//%s" % flename) print("Downloaded File: %s" % flename) folder_id = FOLDER_ID fle_metadata = {'name': flename, 33
  • 34. 'parents': [folder_id] } media = MediaFileUpload('pdfs/%s' % flename, mimetype='application/pdf') fle = service.fles().create(body=fle_metadata, media_body=media ).execute() print('Uploaded fle ID: %s' % fle.get('id')) return fle.get('id') if __name__ == '__main__': download_pdfs() # merge_pdf_urls.py import json if __name__ == '__main__': out = [] with open('publications.json') as f: data = json.load(f) for pub in data: pub['pdf_link_1'] = '' pub['pdf_dl_link_1'] = '' pub['pdf_link_2'] = '' pub['pdf_dl_link_2'] = '' try: pub['pdf_link_1'] = pub["pdf_links"][0] except: pass try: pub['pdf_dl_link_1'] = pub["pdf_dl_links"][0] except: pass try: pub['pdf_link_2'] = pub["pdf_links"][1] except: pass try: pub['pdf_dl_link_2'] = pub["pdf_dl_links"][1] except: pass out.append(pub) 34
  • 35. j = json.dumps(out) f = open("publications.json","w") f.write(j) f.close() #to_spreadsheet.py import json import pandas as pd if __name__ == '__main__': with open("publications.json") as f: data = json.load(f) pub_dataFrame = pd.DataFrame(data) pub_dataFrame = pub_dataFrame.sort_values(by=['conference_year'], ascending=False) writer = pd.ExcelWriter('publications.xlsx', engine='xlsxwriter', options={'strings_to_urls': False}) pub_dataFrame.to_excel(writer, sheet_name='Sheet1') writer.save() with open("news.json") as f: data = json.load(f) pub_dataFrame = pd.DataFrame(data) pub_dataFrame['date']=pd.to_datetime(pub_dataFrame['date']) pub_dataFrame = pub_dataFrame.sort_values(by=['date'], ascending=False) writer = pd.ExcelWriter('news.xlsx', engine='xlsxwriter', options={'strings_to_urls': False}) pub_dataFrame.to_excel(writer, sheet_name='Sheet1') writer.save() <!-- publications.html --> <div class="container"> <h3 class="title"> <span class="square_icon conference_fag" data-display- fag="{{conference_fag}}">C</span> <span class="square_icon journal_fag" data-display- fag="{{journal_fag}}">J</span> {{Title}} <span class="icon" data-show-pdf-icon="{{article_dl_link}}"> <a href="{{article_dl_link}}"> <i class="far fa-fle-pdf"></i> </a> 35
  • 36. </span> </h3> <h6 class="subtitle is-6 authors"> {{authors}} </h6> <p class="publication"> <span class="publication_name">{{publication_name}}</span> <span class="publication_year">{{Year}}</span> <span class="conference_location">{{conference_location}}</span> </p> <a onclick="toggleAbstractButton(this)"> <span class="icon"> <i class="fas fa-book-reader"></i> </span> <span>Abstract</span> </a> <div class="abstract_container"> <p class="pub_urls"> <span class="google_scholar"><a href="{{scholar}}" target="_blank">Google Scholar</a></span> <span class="doi"><a href="{{doi}}" target="_blank">DOI</a></span> <span class="publisher"><a href="{{publisher}}" target="_blank">Publisher</a></span> </p> <p class="abstract"> {{abstract}} <span> <a href="#" onclick="collapseAbstract(this)"> <span class="icon"> <i class="fas fa-angle-double-up"></i> </span> <span id="collapse_text">Collapse</span> </a> </span> </p> <div class="download_container"> <span> <a href="{{article_dl_link}}" target="_blank"> <span class="icon"> <i class="fas fa-download"></i> </span> <span id="collapse_text">Full Article</span> </a> </span> <span> 36
  • 37. <a href="{{slides_dl_link}}" target="_blank"> <span class="icon"> <i class="fas fa-download"></i> </span> <span id="collapse_text">Slides</span> </a> </span> </div> </div> </div> /* publications.css */ .title{ font-size: 1rem !important; } .subtitle{ font-size: 0.9rem !important; margin-bottom: 7px !important; } .publication{ margin-bottom: 5px; } .abstract_container{ display: none; } .abstract_container p { padding-right: 5px; padding-left: 5px; } .abstract_container > div { margin-left: 5px; } .visible{ display: block; } [data-display-fag="0"]{ display: none; } [data-display-fag="1"]{ display: inline-block; } 37
  • 38. [data-show-pdf-icon=""]{ display: none !important; } .square_icon{ width: 1rem; height: 1rem; border-radius: 0.2rem; color: white; text-align: center; } .conference_fag{ background-color: hsl(348, 100%, 61%); } .journal_fag{ background-color: hsl(204, 86%, 53%); } #collapse_text{ margin-left: -5px; } // publications.js function toggleAbstractButton(button){ let card_container = button.parentElement.parentElement; toggleAbstract(card_container); } function toggleAbstract(card_container){ let abs_container = card_container.getElementsByClassName("abstract_container") [0]; abs_container.classList.toggle("visible"); console.log(card_container); console.log(abs_container); } function collapseAbstract(link){ let card_container = link.parentElement.parentElement.parentElement.parentElement; toggleAbstract(card_container); } <!-- news.html --> 38
  • 39. <div class="container"> <div class="title_container"> <h3 class="title"> {{title}} </h3> </div> <div class="subtitle_container"> <p class="date"> {{date}} </p> </div> <a onclick="toggleBodyButton(this)"> <span class="icon"> <i class="fas fa-book-reader"></i> </span> <span>Read More</span> </a> <div class="body_container"> <p class="body"> {{body}} <span> <a href="#" onclick="collapseBody(this)"> <span class="icon"> <i class="fas fa-angle-double-up"></i> </span> <span id="collapse_text">Collapse</span> </a> </span> </p> </div> </div> /* news.css */ .title{ font-size: 1rem !important; } .subtitle{ font-size: 0.9rem !important; margin-bottom: 7px !important; } img { max-width: 100%; } .body_container{ 39
  • 40. display: none; } .body_container p { padding-right: 5px; padding-left: 5px; } .body_container > div { margin-left: 5px; } .visible{ display: block; } #collapse_text{ margin-left: -5px; } // news.js function toggleBodyButton(button){ let card_container = button.parentElement.parentElement; toggleBody(card_container); } function collapseBody(link){ let card_container = link.parentElement.parentElement.parentElement.parentElement; toggleBody(card_container); } function toggleBody(card_container){ let abs_container = card_container.getElementsByClassName("body_container")[0]; abs_container.classList.toggle("visible"); } 40