L'acronimo sta per: DIANA Is Another News Aggregator
Salvatore Pappalardo
Il sistema DIANA prova a semplificare l'esperienza di lettura delle notizie postate su Facebook dalle principali testate giornalistiche italiane.
L'applicazione monitora periodicamente i feed delle pagine dei suddetti giornali per estrarre articoli rilevanti, aggregandoli nel caso in cui trattino argomenti simili.
La loro importanza viene calcolata in base al seguito "social" generato (in pratica, in base al numero di likes, condivisioni e commenti che l'articolo ottiene).
L'architettura Data Warehouse è stata scelta principalmente per restituire velocemente dati ad applicazioni esterne (e quindi agli utenti).
Lo strato di data extraction (avviato ciclicamente) ha il compito di estrapolare i dati dalle fonti tramite i wrapper, unire le informazioni trovate ed inviare i dati finali al data warehouse.
Per risparmiare iterazioni superflue, gli articoli esaminati vengono conservati per due giorni all'interno di BoltDB (un database chiave-valore), per ogni link viene conservata l'azione da compiere alla prossima iterazione; le azioni sono due: ignora e aggiorna.
Il Data Warehouse immagazzina i dati sotto forma di grafo (grazie a Neo4J) e li espone ad eventuali client (con oppurtune limitazioni) tramite una REST API.
esempio di endpoint: <fbNodeName>/feed?fields=link,reactions.limit(0).summary(true),shares,created_time
esempio di risposta:
Per permettere lo scambio di dati tra l'API di facebook e l'extraction layer è stato implementato un package in go appositamente per quest'applicazione (chiamato graphapi).
Le funzioni principali sono definite nelle seguenti interfacce
esempi d'utilizzo:
esempio di endpoint: /nex/v1/?lang=it&min_confidence=0.5&url=<PH>&include=image%2Csummary%2Ccategories&token=<PH>
esempio di risposta:
Come per l'api di facebook, anche per dandelion è stato scritto un wrapper che non si appoggia a librerie esterne. Di seguito un esempio di come la libreria viene utilizzata
Per estrapolare i dati dalle pagine html è stato utilizzato il pacchetto goQuery, che permette di manipolare le fonti attraverso selettori css, utilizzando una sintassi simile a quella di jQuery.
goQuery è stato utilizzato all'interno del package crawler per estrarre i dati dai meta tag delle pagine web.
di seguito un esempio di utilizzo delle funzioni del package crawler
socialInfo(articleURL,reactions,shares,comments,createdTime)⊇
post(articleURL,reactions,shares,comments,createdTime),
article(articleURL,title,description,imageURL,section,publisher)⊇
repubblica(articleURL,title,description,imageURL,section,publisher),
post(articleURL,reactions,shares,comments,createdTime)
article(articleURL,title,description,imageURL,section,publisher)⊇
corriere(articleURL,title,description,imageURL,section,publisher),
post(articleURL,reactions,shares,comments,createdTime)
article(articleURL,title,description,imageURL,section,publisher)⊇
laStampa(articleURL,title,description,imageURL,section,publisher),
post(articleURL,reactions,shares,comments,createdTime)
article(articleURL,title,description,imageURL,section,publisher)⊇
ilFattoQuotidiano(articleURL,title,description,imageURL,section,publisher),
post(articleURL,reactions,shares,comments,createdTime)
articleEntity(articleURL,name,image,summary,accuracy)⊇
entity(name,image,summary,accuracy,articleURL),
post(articleURL,reactions,shares,comments,createdTime)
post(articleURL,reactions,shares,comments,createdTime)⊆
socialInfo(articleURL,reactions,shares,comments,createdTime)
repubblica(articleURL,title,description,imageURL,section,publisher)⊆
article(articleURL,title,description,imageURL,section,publisher),
socialInfo(articleURL,reactions,shares,comments,createdTime),
publisher='repubblica.it'
corriere(articleURL,title,description,imageURL,section,publisher)⊆
article(articleURL,title,description,imageURL,section,publisher),
socialInfo(articleURL,reactions,shares,comments,createdTime),
publisher='corriere.it'
laStampa(articleURL,title,description,imageURL,section,publisher)⊆
article(articleURL,title,description,imageURL,section,publisher),
socialInfo(articleURL,reactions,shares,comments,createdTime),
publisher='lastampa.it'
ilFattoQuotidiano(articleURL,title,description,imageURL,section,publisher)⊆
article(articleURL,title,description,imageURL,section,publisher),
socialInfo(articleURL,reactions,shares,comments,createdTime),
publisher='ilfattoquotidiano.it'
entity(name,image,summary,accuracy,articleURL)⊆
articleEntity(articleURL,name,image,summary,accuracy),
socialInfo(articleURL,reactions,shares,comments,createdTime),
Articoli riguardanti gli esteri pubblicati il 21/07/2017
SELECT a.articleURL, a.title, a.description, a.imageURL, si.reactions, si.shares, si.comments
FROM article AS a,socialInfo AS si
WHERE a.section = "esteri"
AND a.articleURL=si.articleURL
AND si.createdTime>=1500595200
AND si.createdTime<=1500681599
articleEsteri(articleURL,title,description,imageURL,reactions,shares,comments):-
article(articleURL,title,description,imageURL,section,publisher),
socialInfo(articleURL,reactions,shares,comments,createdTime),
section="esteri",
createdTime>=1500595200,
createdTime<=1500681599
Entità collegate agli articoli pubblicati il 21/07/2017 in cronaca
SELECT ae.name
FROM article AS a,socialInfo AS si,article AS ae
WHERE a.section = "cronaca"
AND si.createdTime>=1500595200
AND si.createdTime<=1500681599
AND a.articleURL = si.articleURL
AND ae.articleURL = si.articleURL
entityCronaca(name):-
articleEntity(articleURL,name,image,summary,accuracy),
socialInfo(articleURL,reactions,shares,comments,createdTime),
article(articleURL,title,description,imageURL,section,publisher),
section="cronaca",
createdTime>=1500595200,
createdTime<=1500681599
Articoli collegati all'entità Napoli pubblicati il 21/07/2017
SELECT a.articleURL, a.title, a.imageURL, si.reactions, si.shares, si.comments
FROM article AS a, socialInfo AS si, articleEntity AS ae
WHERE ae.name = "napoli"
AND a.createdTime>=1500595200
AND a.createdTime<=1500681599
AND a.articleURL = si.articleURL
AND a.articleURL = ae.articleURL
artNapoli(articleURL,title,imageURL,reactions,shares,comments):-
articleEntity(articleURL,name,image,summary,accuracy),
socialInfo(articleURL,reactions,shares,comments,createdTime),
article(articleURL,title,description,imageURL,section,publisher),
name="napoli",
createdTime>=1500595200,
createdTime<=1500681599
artNapoli(articleURL,title,imageURL,reactions,shares,comments):-
articleEntity(articleURL,name,image,summary,accuracy),
socialInfo(articleURL,reactions,shares,comments,createdTime),
article(articleURL,title,description,imageURL,section,publisher),
name="napoli",
createdTime>=1500595200,
createdTime<=1500681599
artNapoli(articleURL,title,imageURL,reactions,shares,comments):-
entity(name,image,summary,accuracy,articleURL),
post(articleURL,reactions,shares,comments,createdTime)
post(articleURL,reactions,shares,comments,createdTime)
repubblica(articleURL,title,description,imageURL,section,publisher),
post(articleURL,reactions,shares,comments,createdTime)
name="napoli",
createdTime>=1500595200,
createdTime<=1500681599
Il procedimento è simile per gli altri giornali...
artNapoli(articleURL,title,imageURL,reactions,shares,comments):-
entity(name,image,summary,accuracy,articleURL),
post(articleURL,reactions,shares,comments,createdTime)
repubblica(articleURL,title,description,imageURL,section,publisher),
name="napoli",
createdTime>=1500595200,
createdTime<=1500681599
artNapoli(articleURL,title,imageURL,reactions,shares,comments):-
entity(name,image,summary,accuracy,articleURL),
post(articleURL,reactions,shares,comments,createdTime)
corriere(articleURL,title,description,imageURL,section,publisher),
name="napoli",
createdTime>=1500595200,
createdTime<=1500681599
artNapoli(articleURL,title,imageURL,reactions,shares,comments):-
entity(name,image,summary,accuracy,articleURL),
post(articleURL,reactions,shares,comments,createdTime)
laStampa(articleURL,title,description,imageURL,section,publisher),
name="napoli",
createdTime>=1500595200,
createdTime<=1500681599
artNapoli(articleURL,title,imageURL,reactions,shares,comments):-
entity(name,image,summary,accuracy,articleURL),
post(articleURL,reactions,shares,comments,createdTime)
ilFattoQuotidiano(articleURL,title,description,imageURL,section,publisher),
name="napoli",
createdTime>=1500595200,
createdTime<=1500681599
artNapoli(articleURL,title,imageURL,reactions,shares,comments):-
articleEntity(articleURL,name,image,summary,accuracy),
socialInfo(articleURL,reactions,shares,comments,createdTime),
article(articleURL,title,description,imageURL,section,publisher),
name="napoli",
createdTime>=1500595200,
createdTime<=1500681599
articleEntity(A,N,I,S,AC) | article(A,T,D,IU,S,P) | socialInfo(A,R,SH,C,CT) |
---|---|---|
entity(A,N,I,S,AC) | repubblica(A,T,D,IU,S,P) | post(A,R,SH,C,CT) |
corriere(A,T,D,IU,S,P) | ||
laStampa(A,T,D,IU,S,P) | ||
ilFattoQuotidiano(A,T,D,IU,S,P) |
applicando il prodotto cartesiano...
artNapoli'(articleURL,title,imageURL,reactions,shares,comments):-
entity(name,image,summary,accuracy,articleURL),
repubblica(articleURL,title,description,imageURL,section,publisher),
post(articleURL,reactions,shares,comments,createdTime)
name="napoli",
createdTime>=1500595200,
createdTime<=1500681599
artNapoli'(articleURL,title,imageURL,reactions,shares,comments):-
entity(name,image,summary,accuracy,articleURL),
corriere(articleURL,title,description,imageURL,section,publisher),
post(articleURL,reactions,shares,comments,createdTime)
name="napoli",
createdTime>=1500595200,
createdTime<=1500681599
artNapoli'(articleURL,title,imageURL,reactions,shares,comments):-
entity(name,image,summary,accuracy,articleURL),
laStampa(articleURL,title,description,imageURL,section,publisher),
post(articleURL,reactions,shares,comments,createdTime)
name="napoli",
createdTime>=1500595200,
createdTime<=1500681599
artNapoli'(articleURL,title,imageURL,reactions,shares,comments):-
entity(name,image,summary,accuracy,articleURL),
ilFattoQuotidiano(articleURL,title,description,imageURL,section,publisher),
post(articleURL,reactions,shares,comments,createdTime)
name="napoli",
createdTime>=1500595200,
createdTime<=1500681599
testando il containment (solo per l'ultima, le altre sono pressoché uguali)
artNapoli'(articleURL,title,imageURL,reactions,shares,comments):-
articleEntity(articleURL,name,image,summary,accuracy),
socialInfo(articleURL,reactions,shares,comments,createdTime),
article(articleURL,title,description,imageURL,section,publisher),
socialInfo(articleURL,reactions,shares,comments,createdTime),
socialInfo(articleURL,reactions,shares,comments,createdTime),
publisher='ilfattoquotidiano.it',
name="napoli",
createdTime>=1500595200,
createdTime<=1500681599
risulta che la query artNapoli' è contenuta in artNapoli.
artNapoli'(articleURL,title,imageURL,reactions,shares,comments):-
articleEntity(articleURL,name,image,summary,accuracy),
socialInfo(articleURL,reactions,shares,comments,createdTime),
article(articleURL,title,description,imageURL,section,publisher),
publisher='ilfattoquotidiano.it',
name="napoli",
createdTime>=1500595200,
createdTime<=1500681599
Quando il processo di data extraction (nel progetto chiamato retriever) ha terminato la sua iterazione, manda i dati raccolti al data warehouse (neowr).
Prima di salvare gli articoli e le entità all'interno di neo4j, viene eseguito un ulteriore controllo per cercare di accorpare gli articoli che parlano di argomenti simili in un unico cluster.
In pratica, vengono cercati gli articoli (inseriti durante la giornata corrente) che abbiano in comune, con quello che sta per essere immagazzinato, la sezione ed almeno un'entità correlata.
Se esistono articoli con tali caratteristiche, vengono prelevati insieme ad un massimo di 5 entità correlate.
Le 5 entità vengono scelte in base al grado di accuratezza che dandelion ha precedentemente calcolato per quell'articolo (nel caso in cui il valori siano uguali, prevale quella esaminata più volte da quando il server è in funzione).
Un procedimento simile viene applicato all'articolo prossimo all'inserimento.
L'ultimo passaggio compiuto, è quello di calcolare un indice di similarità tra le due liste risultanti
L'indice di similarità scelto è Jaccard
Se l'indice supera una certa soglia, vuol dire che ci sono abbastanza entità in comune da effettuare un accorpamento tra le due notizie
Oltre all'accorpamento delle notizie, prima di inserire articoli ed entità all'interno del data warehouse viene compiuto un ulteriore passo.
Sia le informazioni riguardanti gli articoli e le informazioni riguardanti le entità vengono date in pasto ad un analyzer (scritto in go), in modo di generare una serie di parole chiave che servano a rendere la ricerca più efficace.
Nel caso degli articoli, per ogni parola viene generato anche il suo score tf (salvato sull'arco che collega l'articolo e la parola chiave).
Quando l'utente immette una query, anche questa viene data in input all'analyzer e le parole risultanti vengono processate in OR