Speaker_Graph_scraper.py at master · lucahammer_Speaker_Graph - Google Chrome 2015-05-01 16.12.38

Heute habe ich eine kleine Visualisierung der Speaker_innen der #rp15 erstellt. Gedauert hat das Ganze etwa 3 Stunden. Damit ihr es in 15 Minuten schafft, beschreibe ich in diesem Beitrag wie ich vorgegangen bin. Ziel war es ein Netzwerkvisualisierung zu erstellen, die die Verbindungen zwischen den Speaker_innen sichtbar macht. Beachtet bitte, dass ich kein Entwickler bin, ich mir das meiste selbst beigebracht habe und oft Fehler mache.

Zwischenformat .gdf

Ich arbeite sehr gerne mit Gephi , um Netzwerke zu visualisieren. Gephi versteht viele Formate, am einfachsten zum selbst erstellen finde ich derzeit .gdf. Die Spezifikation ist einfach. Für dieses Projekt wird es ungefähr wie folgt aussehen:
nodedef>name VARCHAR,label VARCHAR
speakerID1,Speaker Name 1
speakerID2,Speaker Name 2
speakerID3,Speaker Name 3
edgedef>node1 VARCHAR,node2 VARCHAR
speakerID1,speakerID2
speakerID1,speakerID3
speakerID2,speakerID3

Zuerst werden die Knoten aufgezählt. Einer pro Zeile. Dann die Verbindungen. Minimum ist bei den Knoten eine eindeutige ID und bei den Verbindungen zwei IDs, zwischen denen die Verbindung ist. Zusätzlich können beliebig viele Eigenschaften für Knoten und Verbindungen gespeichert werden. Diese werden in der nodedef bze. edgedef definiert.

re:data API erkunden

Die re:publica stellt ihre Daten über eine sehr praktische API zur Verfügung. Diese ist Open Source und wird auch schon von ein paar anderen Veranstaltungen genutzt. Dadurch sind Tools, die man dafür schreibt mit minimalen Aufwand wiederverwendbar. Hier geht es zur Dokumentation .

Mich interessieren die Endpunkte Speake, weil es ja die Knoten sind, und Sessions, weil ich daraus die Verbindungen ziehen kann.
Also http://data.re-publica.de/api/rp13/sessions?count=5 und http://data.re-publica.de/api/rp13/speakers?count=5 . Das “?count=5″ beschränkt die Rückgabe auf 5 Ergebnisse. Das nutze ich beim experimentieren, um nicht jedes Mal mit ein paar hundert Ergebnissen arbeiten zu müssen.

Da es sich bei der Rückgabe um Json handelt, bietet es sich an sich das in hübsch anzeigen zu lassen. Auch wenn die re:data API das schon sehr schön macht.

Ich mag den Jason Parser Online , aber es gibt noch viele andere. Ganz nach belieben. Über die Einstellungen rechts oben kann ich mir nun schnell Typen und Indices anzeigen lassen. Somit sehe ich besser, wie ich auf die einzelnen Elemente zugreifen kann.
Json Parser Online - Google Chrome 2015-05-01 15.40.05

Daten sammeln und vorbereiten mit morph.io

Man muss morph.io nicht nutzen. Vor allem bei einem so einfachen Projekt. Aber ich mag es, weil ich es von jedem Gerät nutzen kann und mich um nichts kümmern muss. (Auf meinem Desktop läuft die Windows 10 Technical Preview, da geht schon mal was kaputt.). morph.io ist quasi der Nachfolger von scraperwiki , welches inzwischen keine kostenfreien Accounts mehr anbietet. Ich würde dafür zahlen, aber sie starten bei $2000 im Monat. Das ist zuviel. morph.io ist derzeit kostenlos. Ihr braucht allerdings einen Github-Account, um es nutzen zu können. Aber der ist auch kostenlos.

Als erstes muss ein neuer Scraper angelegt werden. Als Sprache habe ich mich für Python entschieden, weil ich mich damit am wohlsten fühle. morph.io legt dann ein neues Repository auf Github und ihr könnt den Code dort über diverse Tools oder direkt im Browser bearbeiten.

Mein Code hat gerade einmal 32 Zeilen und kann gerne übernommen werden.

import os
import requests
import json

Im ersten Teil importiere ich die benötigten Module. “os” ist nötig, um Variablen, die ich auf morph.io eingebe nutzen zu können. “requests” ist für die Kommunikation mit der API zuständig. “json” wandelt mir die Antwort de API in ein Python-Objekt um.

baseUrl = 'http://data.re-publica.de/api/'
if 'MORPH_EVENTID' in os.environ:
eventId = os.environ['MORPH_EVENTID']
else:
print 'Please add the ID of the event you want to analyze with the name "MORPH_EVENTID" to the morph.io settings of this scraper'
print 'See http://data.re-publica.de/doc/ to find supported events'

Auch hier bin ich etwas ausufernd. “baseUrl” definiere ich, um das Skript schneller auf andere Events anwenden zu können oder falls sich einmal die URL ändert. Anschließend hole ich mir noch die ID des gewünschten Events von morph.io. In meinem Fall habe ich dort “rp15″ hinterlegt. Funktioniert aber genauso mit “rp14″ oder “rp13″. Falls keine Variable in den Einstellungen angegeben ist, bekommt die Nutzer_in einen Hinweis.

print 'Copy the following output, paste it into a textfile and save it with ".gdf" as file extension to open it with Gephi'
print 'nodedef>name VARCHAR,label VARCHAR'
response = requests.get(baseUrl+eventId+'/speakers').content
speakers = json.loads(response)
for speaker in speakers['data']:
print speaker['id']+','+speaker['name'].encode('ascii', 'replace')

Normalerweise speichere ich Sachen, die ich mir morph.io sammle in eine Datenbank. Das ist dieses Mal nicht nötig. Stattdessen wird das Ergebnis direkt in der Konsole ausgegeben. Zuerst aber der Hinweis, was man damit machen soll. Einfach in eine Textdatei kopieren und diese als .gdf abspeichern.

Zuerst bitte ich die API mir alle Speaker zu liefern. Den Inhalt der Antwort speichere ich in “response”. Zu dem Zeitpunkt ist es einfach ein sehr langer Zeichenstring. In der nächsten Zeile wird er in ein Python-Objekt mit dem Namen “speakers” umgewandelt. Für jedes Element der Liste, die sich in “data” im Objekt “Speaker” befindet wird dann die ID und der Name der Speaker_in ausgegeben. Weil in die Konsole ausgegeben wird, muss der Name in ascii umgewandelt werden. Etwas schlampig habe ich dabei alle Sonderzeichen einfach durch ein ‘?’ ersetzen lassen. Tut man das nicht wird man einen UnicodeError bekommen, sobald es ein nicht-Ascii-Zeichen in einem Namen gibt.

#make edges
print 'edgedef>node1 VARCHAR,node2 VARCHAR,label VARCHAR'
response = requests.get(baseUrl+eventId+'/sessions').content
sessions = json.loads(response)
for session in sessions['data']:
if len(session['speakers']) > 1:
for i, speaker in enumerate(session['speakers']):
for y in range (i, len(session['speakers'])-1):
print session['speakers'][i]['id']+','+session['speakers'][y+1]['id']+',"'+session['title'].encode('ascii', 'replace')+'"'

Sehr ähnlich läuft es bei den Verbindungen ab. Ich lasse mir alle Sessions geben und gehe sie dann Stück für Stück durch. Falls es mehr als eine Speaker_in gibt, gibt es auch eine Verbindung. Dann wird es etwas komplizierter, weil zwischen jede_r Speaker_in eine Verbindung erstellt werden muss. Immer nach dem Muster “IDstartKnoten,IDendKnoten”. Ich beginne mit der ersten Speaker_in und erstelle eine Verbindung zur zweiten, dritten und so weiter. Dann mache ich mit der zweiten weiter. Allerdings habe ich die Verbindung zwischen erster und zweiten schon und kann somit direkt mit dritter, vierter, und so weiter weitermachen. Zusätzlich gebe ich noch den Titel der Session als Bezeichnung der Verbindung an.

Fertig.

Jetzt kann man den Code speichern, auf morph.io das Event in den Einstellungen angeben, mit dem man arbeiten möchte und auf “run scraper” klicken.

Hier die fertige .gdf Datei zum Download. Und hier meine Scraper auf morph.io .

Visualisierung in Gephi

Import report 2015-05-01 13.11.46
Nachdem man die Ausgabe auf morph.io kopiert und als .gdf gespeichert hat, kann man diese in Gephi öffnen. Wichtig ist, dass man angibt, dass es sich um einen ungerichteten (undirected) Graph handelt, weil wir jede Verbindung ja nur aus einer Richtung angegeben haben, auch wenn die Richtung keine Rolle spielt.