La genesi della Twittersfera
23 Maggio 2007
Come richiesto espressamente da Maurizio, in questo post vi spiegherò con dovizia di particolari come ho ottenuto l’immagine presente in questo post 😛
L’idea
L’idea di base è semplice: realizzare un’immagine in grado di rappresentare le relazioni che collegano i miei amici (friends) in Twitter e gli “amici degli amici”, in pratica due livelli di twitter-relazioni a partire dal sottoscritto.
Doverosa premessa: non sono un programmatore, queste sono le prime righe che scrivo in Python e non ho mai frequentato i grafi in vita mia 😉
Python
Ecco un elenco dei software indispensabili (nel mio caso il tutto è stato realizzato su Mac Os X):
- Python: l’ultima versione è la 2.5.1, per Mac Os X è disponibile un comodo installer
- Setuptools
- Python Twitter comprensivo di semplici istruzioni di installazione
Generazione del file .dot
Per la generazione del file .dot ho scritto qualche riga di codice in Python (è la volta buona che mi costringo ad impararlo…).
#!/usr/bin/python
import twitter, simplejson, time
class ApiEstesa (twitter.Api):
''' Estende Api per recuperare i friends di un user diverso dal proprio '''
def __init__(self):
twitter.Api.__init__(self)
def GetAnotherOneFriends(self, username, password, identificativo):
url = 'http://twitter.com/statuses/friends/'+str(identificativo)+'.json'
json = self._FetchUrl(url, username=username, password=password)
data = simplejson.loads(json)
return [twitter.User.NewFromJsonDict(x) for x in data]
def aggiungi_soggetti (utenti, relazioni, id):
'''Aggiunge gli id dei friends dell'utente con identificativo ID al dizionario id_soggetti'''
try:
friends = api.GetAnotherOneFriends(user, password, id)
except:
print 'Errore nel recupero dei dati da Twitter: attendo 10 secondi e riprovo'
time.sleep(10)
friends = api.GetAnotherOneFriends(user, password, id)
for friend in friends:
utenti.setdefault(friend.id, friend.screen_name)
relazioni.append(' '+str(id)+' -- '+str(friend.id)+'n')
api = ApiEstesa()
user = 'vostro_user'
password= 'vostra_password'
identificativo = 'vostro_id'
screen_name = 'vostro_nome_visualizzato'
# Recupero i friends del soggetto principale
friends = api.GetFriends(user, password)
# Uso un dizionario per evitare la duplicazione degli utenti; aggiungo subito il soggetto principale
id_utenti = {}
id_utenti.setdefault(identificativo, screen_name)
relazioni = []
for friend in friends:
# Aggiungo i friends del soggetto principale al dizionario id_utenti
id_utenti.setdefault(friend.id, friend.screen_name)
# Aggiungo le relazioni del soggetto principale
relazioni.append(' '+identificativo+' -- '+str(friend.id)+'n')
# Aggiungo i friends e le relazioni del friend che sto controllando
aggiungi_soggetti(id_utenti, relazioni, friend.id)
print 'Aggiungo soggetto e relazioni di '+friend.screen_name
lista_utenti = id_utenti.items() # Trasformo il dizionario in una lista
inizio_file = '''graph relazioni_twitter {
graph [bgcolor=black, overlap="scale", ratio="auto"];
node [fontcolor="white", fontsize="10", shape="circle", color="antiquewhite"];
edge [color="gold1"];n'''
f = open('grafico_prova.dot','w')
# Scrivo su file la parte iniziale del grafo
f.writelines(inizio_file)
# Scrivo la label del nodo root (soggetto principale)
f.writelines(' %s [label="%s", root="1", fontcolor="red",shape="circle"];n' % (identificativo, screen_name))
# Scrivo su file tutti i soggetti raccolti in lista_utenti
print '%s nodi, %s relazioni' % (str(len(lista_utenti)),str(len(relazioni)))
for utente in lista_utenti:
f.write(' %s [label="%s"];n' % utente)
f.writelines('n')
for relazione in relazioni:
f.write(relazione)
f.writelines('}n')
f.close()
Primo problema: per qualche strano motivo il modulo per Twitter non prevede una funzione per leggere i friends di un utente diverso dal proprio, funzionalità prevista dalle API di Twitter. A questo punto è stato necessario estendere la classe originaria con una nuova funzione: getAnotherOneFriends.
Descrizione del codice (qui lo potete scaricare come file di testo):
- creo delle variabili con il mio identificativo, il mio user, la mia password e il mio screen_name (nome visualizzato)
- ottengo una lista dei miei friends (friends)
- scorro la lista: ogni friend viene aggiunto al dizionario id_utenti e, tramite la funzione aggiungi_soggetti, aggiungo i suoi friends al dizionario e le relazioni alla lista omonima
- alla fine non resta che scrivere su file l’elenco dei nodi e le relazioni (questo è file .dot generato)
L’algoritmo è volutamente semplice: così come è strutturato, ad esempio, non sarebbe in grado di rilevare delle relazioni di terzo grado senza generare una marea di ripetizioni inutili. Il time.sleep(10) serve ad evitare che un down di Twitter (già verificatosi 3 volte stasera) blocchi completamente la generazione 😉
Appena ho un attimo di tempo conto di ristrutturare il codice in modo da permettere di analizzare le relazioni per n-livelli (anche se in quel caso il problema sarà il grafo…)
Generazione del grafo
Questa è la parte più complicata e, al momento, parzialmente irrisolta. Per Mac Os X esiste un ottimo software che però è vecchio e non più supportato (il che significa, tra le altre cose, che funziona in emulazione con Rosetta sul mio iMac Core 2 Duo). Sullo schermo riuscivo chiaramente a leggere i nomi sui nodi, nell’esportazione in png il file è stato ridimensionato drasticamente rendendo i nomi invisibili. Per generare l’immagine ho comunque utilizzato PixelGlow con l’impostazione Energy Minimized.
Al momento sto provando a ricreare un grafico usando neato e twopi da linea di comando ma incontro qualche problema: segmentation fault ed errori simili, che elimino solo impostando delle dimensioni inferiori per il grafo, peccato solo che rendano i nodi incomprensibili.
In queste ore sto anche provando a generare il grafo sul PowerBook G4 con PixelGlo; stamattina sul P4 3.0Ghz in ufficio (Windows XP) sono arrivato ad occupazioni di memoria di 3GB senza ottenere grandi risultati.
Domande in sospeso
- Avete suggerimenti per la realizzazione del grafo? Sbaglio qualcosa nella sintassi del file .dot?
- Python: qualcuno è in grado di spiegarmi perché, pur utilizzando un dizionario e setdefault, mi ritrovo un nodo (quindi una chiave) ripetuto per ben 3 volte? Il nodo incriminato è quello del soggetto principale.
Gli amici degli amici (TwitterSfera)
21 Maggio 2007
Da sabato scorso sto facendo qualche prova con Python, le API di Twitter e GraphViz: per il momento sono riuscito ad ottenere un’immagine, incomprensibile ma affascinante 😛
L’idea di fondo è semplice: rappresentare i miei amici (friends) e gli amici dei miei amici (amici di 2° grado). Sembrano pochi dati, in realtà siamo già ad un file di testo di 8160 righe.
Ecco il risultato:
L’utente in fondo a destra che genera una raggiera di relazioni è TheMacPack 😉