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

  1. Avete suggerimenti per la realizzazione del grafo? Sbaglio qualcosa nella sintassi del file .dot?
  2. 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.

5 commenti/trackback a “La genesi della Twittersfera”

  1. flod scrive:

    Al momento sto provando un’altra soluzione (un po’ macchinosa):

    • importo il file .dot in PixelGlow GraphViz e faccio il rendering come “Energy Minimized”
    • lo esporto come file .dot: in questo modo vengono aggiunte le coordinate a tutti i nodi)
    • da linea di comando converto il file .dot modificato con neato in SVG (il file pesa meno di 2MB)
    • apro il file SVG con InkScape (velocissimo) e lo esporto come PNG impostando le dimensioni dell’immagine
  2. riffraff scrive:

    ma neato non è parte di graphviz?
    Ad ogni modo, OmniGraffle, anche nella versione shareware, permette il rendering dei file di graphviz, anche se non è molto ovvio.

  3. flod scrive:

    Sì, neato e twopi fanno parte di GraphViz: il problema è che PixelGlow si appoggia su una versione molto vecchia di GraphViz.

  4. MFP scrive:

    Probabilmente per dimensioni più grandi non basta la memoria. Usa linux (anche dentro ad una vm installando Q.app su OSX; usa una versione minimale senza X).

  5. flod scrive:

    Sembra che la strada indicata nel primo commento funzioni: peccato che per generare una png di 16000 pixel di larghezza con InkScape ci impieghi una marea di tempo (ad occhio e croce 8-9 ore, ma devo controllare stasera quando rientro dal lavoro).

Trackback e pingback

  1. Nessun trackback o pingback disponibile per questo articolo

Non è possibile inserire nuovi commenti. I commenti vengono disattivati automaticamente dopo 60 giorni.