• Non ci sono risultati.

2. La diffusione di Facebook in Italia

2.4 Scraper: funzionamento in dettaglio

2.4.2 Dettaglio script per estrazione post

La funzione getFacebookPageFeedData(page_id, access_token, num_statuses) ha come parametri l’ID della pagina, il token di accesso dell’utilizzatore dello scraper e il numero degli stati della pagina esaminata. Ha il compito di chiedere alcuni campi specifici del post, come testo del post (message), il link al post (link), data di creazione (created_time), tipo di post (type), ID univoco, condivisioni, reazioni. Per ora non vengono richiesti i commenti e il campo comments ha il limite impostato a zero. La funzione richiede questi campi tramite costruzione di un URL, sulla quale si baserà la richiesta fatta con la funzione request_until_succeed(url), in cui concatena ID pagina e i campi richiesti.

base = "https://graph.facebook.com/v2.6" node = "/%s/posts" % page_id

fields = "/?fields=message,link,created_time,type,name,id," + \ "comments.limit(0).summary(true),shares,reactions" + \

".limit(0).summary(true)"

parameters = "&limit=%s&access_token=%s" % (num_statuses, access_token)

#creazione dell’url

url = base + node + fields + parameters

Il testo della risposta HTTP restituito da request_until_succeed(url) viene interpretato come una struttura JSON memorizzata come oggetto nella variabile data:

data = json.loads(request_until_succeed(url)) return data

La funzione getReactionsForStatus(status_id, access_token) ha come parametri l’ID dello status di cui contare le diverse reazioni e l’access token dell’utente che utilizza lo script. Ha lo scopo di contare e analizzare le reazioni che i post hanno ricevuto:

base = "https://graph.facebook.com/v2.6" #id del post da esaminare

node = "/%s" % status_id

#i campi da chiedere nell’url sono stati definiti nella funzione

#precedente. Qui vi si aggiungono le reazioni: reactions = "/?fields=" \ "reactions.type(LIKE).limit(0).summary(total_count).as(like)" \ ",reactions.type(LOVE).limit(0).summary(total_count).as(love)" \ ",reactions.type(WOW).limit(0).summary(total_count).as(wow)" \ ",reactions.type(HAHA).limit(0).summary(total_count).as(haha)" \ ",reactions.type(SAD).limit(0).summary(total_count).as(sad)" \ ",reactions.type(ANGRY).limit(0).summary(total_count).as(angry)" parameters = "&access_token=%s" % access_token

url = base + node + reactions + parameters

La costruzione url composto da base (indirizzo grafo), node (id del post da esaminare),

reactions reazioni da contare, parameters composto dall’ID dell’utilizzatore. La risposta

della funzione viene restituita come oggetto JSON:

data = json.loads(request_until_succeed(url)) return data

Ogni status viene trattato sotto forma di dizionario: una struttura dati in cui le informazioni sono organizzate in tuple, ovvero coppie chiave:valore dove ogni chiave è unica e a essa corrisponde un solo valore. La funzione

esaminare e access token dell’utilizzatore come prima cosa controlla che la chiave esista nel dizionario dello status e in caso ne normalizza i valori in UTF-8.

status_id = status['id']

status_message = '' if 'message' not in status.keys() else \ unicode_normalize(status['message'])

link_name = '' if 'name' not in status.keys() else \ unicode_normalize(status['name'])a

status_type = status['type']

status_link = '' if 'link' not in status.keys() else \ unicode_normalize(status['link'])

Il controllo sull’esistenza della chiave nel dizionario e in caso la sua normalizzazione in UTF-8 viene effettuato anche sui campi annidati: ad esempio se sono presenti reazioni, ne estrae il sottocampo conteggio (total_count) dal percorso reactions>summary, altrimenti se la chiave è assente, il conteggio riporta zero.

num_reactions = 0 if 'reactions' not in status else \ status['reactions']['summary']['total_count'] num_comments = 0 if 'comments' not in status else \

status['comments']['summary']['total_count']

num_shares = 0 if 'shares' not in status else status['shares'] ['count']

Il numero di like al post è estratto tramite le reazioni al post se il post è successivo alla data di introduzione in Facebook di tale funzionalità, altrimenti è dato dalla variabile

num_reactions precedentemente trattata. Nello specifico lo script prima acquisisce le reazioni nella variabile reactions tramite la funzione getReactionsForStatus per i post successivi al 24/02/2016. Se in tale variabile (ovvero nel dizionario) non esiste il campo like, allora il numero di like è 0, altrimenti è il valore del sotto campo total_count del

campo like; se il post è stato pubblicato prima del 24/02/2016 il numero di like coincide con il numero totale delle reazioni precedentemente estratto ( questo perché prima del febbraio 2016 l’unica reazione possibile ad un post o commento era soltanto il like):

reactions = getReactionsForStatus(status_id, access_token) if \ status_published > '2016-02-24 00:00:00' else {}

num_likes = 0 if 'like' not in reactions else \

reactions['like']['summary']['total_count']

num_likes = num_reactions if status_published < '2016-02-24 00:00:00' \

else num_likes

La funzione get_num_total_reactions(reaction_type, reactions) con parametri tipo di reazione e reazioni ha lo scopo di controllare la tipologia di ogni reazione e il loro numero suddiviso per tipo. Inoltre ha il compito di restituire le tuple dell’intero dizionario:

return (status_id, status_message, link_name, status_type, status_link, status_published, num_reactions, num_comments, num_shares, num_likes, num_loves, num_wows, num_hahas, num_sads, num_angrys)

Il lavoro di questo script si basa sulla funzione getFacebookPageFeedData vista prima: l’ultima funzione scrapeFacebookPageFeedStatus si occupa di chiamare

getFacebookPageFeedData con limite di cento post, di analizzarli un commento alla

volta con la funzione processFacebookPageFeedStatus e di creare un file di testo CSV in cui scrivere le informazioni trovate. I post sono forniti da Facebook organizzati in pagine, cioè in un elenco lungo tanto quanto il limite impostato, in questo caso cento. Se i post eccedono il limite, per ogni pagina ottenuta la presenza di un campo next indica l’esistenza di una pagina successiva (al massimo di cento post anche essa). La funzione

scrapeFacebookPageFeedStatus ottiene una pagina di cento post alla volta, e finché è

presente il campo next continua a chiedere la pagina successiva per analizzare tutti i post. Vediamo il codice nel dettaglio:

def scrapeFacebookPageFeedStatus(page_id, access_token): #crea un file CSV con le colonne specificate:

with open('%s_facebook_statuses.csv' % page_id, 'wb') as file:

w = csv.writer(file)

w.writerow(["status_id", "status_message", "link_name", "status_type", "status_link",

"status_published", "num_reactions", "num_comments", "num_shares", "num_likes", "num_loves", "num_wows", "num_hahas", "num_sads", "num_angrys"])

has_next_page = True

num_processed = 0 # contatore stati analizzati scrape_starttime = datetime.datetime.now() #inizio attività

#stampa nel terminale o prompt dei comandi un feedback di #quanto svolge, per tenere l’utente informato. In questa riga

#visualizza la pagina target e l’ora di inizio analisi: print "Scraping %s Facebook Page: %s\n" % (page_id, scrape_starttime)

#La variabile statuses è data dal risultato di

#getFacebookPageFeedData con limite impostato a 100 status:

statuses = getFacebookPageFeedData(page_id, access_token,100)

while has_next_page: #continua finché ci sono pagine for status in statuses['data']:

#controlla che sia uno stato con i metadati attesi: if 'reactions' in status:

w.writerow(processFacebookPageFeedStatus(status, access_token))

#aggiorna il contatore e lo stampa nel prompt num_processed += 1

if num_processed % 100 == 0:

print "%s Statuses Processed: %s" % \ (num_processed, datetime.datetime.now()) #se non ci sono altre pagine da esaminare: if 'paging' in statuses.keys():

statuses = json.loads(request_until_succeed( statuses['paging']['next']))

else:

has_next_page = False

#stampa un messaggio che avvisa della fine dell’esecuzione #dello script, del numero di post analizzati e del tempo #impiegato

print "\nDone!\n%s Statuses Processed in %s" % \ (num_processed, datetime.datetime.now() -

scrape_starttime)

Documenti correlati