image alt >\

Alle Entwickler aus Bonn, die sich bei StackOwerflow registriert haben. Was du als Entwickler in Bonn oder als Recruiter für Jobs in Bonn schon immer wissen wolltest, lade es dir als businessfähige Datei herunter, die du entweder in Microsoft Excel oder als Apple Numbers oder in OpenOffice als Tabellendokument öffnen kannst.

Hier die Daten

Das war die Kurzversion für Ungeduldige. Nachfolgend in Langform.

Die Daten von StackOverflow stehen unter der Lizenz Attribution-Share Alike 3.0.

Meine Daten sind eine abgewandelte Form der Ursprungsdaten von StackOverflow. Daher veröffentliche ich sie unter der oben genannten Lizenz.

<?xml version="1.0" encoding="utf-8"?>
<users>
  <row Id="-1" Reputation="1" CreationDate="2008-07-31T00:00:00.000" DisplayName="Community" LastAccessDate="2008-
  <row Id="1" Reputation="44300" CreationDate="2008-07-31T14:22:31.287" DisplayName="Jeff Atwood" LastAccessDate="
  <row Id="2" Reputation="3491" CreationDate="2008-07-31T14:22:31.287" DisplayName="Geoff Dalgas" LastAccessDate="
  <row Id="3" Reputation="13418" CreationDate="2008-07-31T14:22:31.287" DisplayName="Jarrod Dixon" LastAccessDate=
  <row Id="4" Reputation="28768" CreationDate="2008-07-31T14:22:31.317" DisplayName="Joel Spolsky" LastAccessDate=
  <row Id="5" Reputation="39172" CreationDate="2008-07-31T14:22:31.317" DisplayName="Jon Galloway" LastAccessDate=
  <row Id="8" Reputation="942" CreationDate="2008-07-31T21:33:24.057" DisplayName="Eggs McLaren" LastAccessDate="2
  <row Id="9" Reputation="14337" CreationDate="2008-07-31T21:35:26.517" DisplayName="Kevin Dente" LastAccessDate="
  <row Id="10" Reputation="101" CreationDate="2008-07-31T21:57:06.240" DisplayName="Sneakers O'Toole" LastAccessDa
  <row Id="11" Reputation="1890" CreationDate="2008-08-01T00:59:11.147" DisplayName="Anonymous User" LastAccessDat
  <row Id="13" Reputation="177138" CreationDate="2008-08-01T04:18:04.943" DisplayName="Chris Jester-Young" LastAcc
  <row Id="16" Reputation="527" CreationDate="2008-08-01T12:01:53.023" DisplayName="Rodrigo Sieiro" LastAccessDate
  <row Id="17" Reputation="44443" CreationDate="2008-08-01T12:02:21.617" DisplayName="Nick Berardi" LastAccessDate
  <row Id="19" Reputation="1272" CreationDate="2008-08-01T12:05:14.233" DisplayName="Mads Kristiansen" LastAccessD
  <row Id="20" Reputation="8520" CreationDate="2008-08-01T12:09:11.010" DisplayName="Tom" LastAccessDate="2016-12-
  <row Id="22" Reputation="12816" CreationDate="2008-08-01T12:11:11.897" DisplayName="Matt MacLean" LastAccessDate
  <row Id="23" Reputation="4296" CreationDate="2008-08-01T12:11:43.703" DisplayName="Jax" LastAccessDate="2018-08-
  <row Id="24" Reputation="3001" CreationDate="2008-08-01T12:12:53.453" DisplayName="sanmiguel" LastAccessDate="20
  <row Id="25" Reputation="16981" CreationDate="2008-08-01T12:15:23.243" DisplayName="CodingWithoutComments" LastA
  <row Id="26" Reputation="11411" CreationDate="2008-08-01T12:18:14.520" DisplayName="Shawn" LastAccessDate="2018-
  <row Id="27" Reputation="1281" CreationDate="2008-08-01T12:21:40.020" DisplayName="denny" LastAccessDate="2018-0
  <row Id="29" Reputation="72926" CreationDate="2008-08-01T12:24:53.820" DisplayName="Michael Haren" LastAccessDat
  <row Id="30" Reputation="8133" CreationDate="2008-08-01T12:25:44.817" DisplayName="Grant" LastAccessDate="2018-0

So sehen die Daten entpackt von StackOverflow aus. Entpackt sind das 2,95 GB Daten! 9.600.000 User! Das sind Dimensionen! Das sind schon fast Datenmassen. Die Datenmenge ist so groß, dass ich die Datei auf meinem Arbeitswerkzeug nicht einfach mal so auf einmal in den Speicher lesen kann, sondern sie sequentiell durcharbeiten muss. Es macht auch keinen Sinn, den gesamten Datenhaufen als CSV für Excel oder ähnliche zu schreiben, sondern man muss eine Auswahl treffen, damit die Datei nicht zu groß wird. Denn jedes Office-Programm verschluckt sich an sehr großen Happen. Also kommen wir nicht um eine Datenbank herum, in die wir die Massendaten zunächst schreiben um dann per SQL einige Datensätze auswählen zu können und uns unsere Daten selbst zusammenstellen zu können. Ich lasse bei mir zur Zeit immer Postgres (Postgres weitverbreiter Spitzname for PostgreSQL) laufen, seitdem ich ein Python ERP-System mit Postgres programmiere. Der nachfolgende Code basiert darauf, dass es eine Datenbank dev und den Benutzer dev gibt.

from sqlalchemy import create_engine
from sqlalchemy import Table, Column, Integer, String, MetaData

engine = create_engine('postgresql://dev:@localhost:5432/dev', echo=True)

metadata = MetaData()

# Aufgabe: Erstelle die Tabellenspalten der User-Tabelle anhand des Arrays
cols = ['SODisplayName', 'SOWebsiteUrl', 'SOReputation', 'SOLocation',
                         'SOAboutMe', 'SOCreationDate', 'SOLastAccessDate', 'SOProfileImageUrl']

users = Table('User', metadata,
    Column('id', Integer, primary_key=True),
    Column('SOId', Integer),
    Column('SODisplayName', String),
    Column('SOWebsiteUrl', String),
    Column('SOReputation', String),
    Column('SOLocation', String),
    Column('SOAboutMe', String),
    Column('SOCreationDate', String),
    Column('SOLastAccessDate', String),
    Column('SOProfileImageUrl', String),
              )

metadata.create_all(engine)

SQLAlchemy - noch so ein Zungenbrecher wie PostgreSQL - wird hier verwendet, ein Persistenzframework für Python. Der Code lässt aber gar nicht erahnen, dass SQL hier das Thema ist. SQL ist ja schön und gut und die Namen zeigen, dass dies eine relationale Datenbank ist und die Datenbankschicht darauf basiert, aber um eine Tabelle zu definieren brauche ich kein SQL und auch nicht um simple Daten hinein zu schreiben. SQL ist heutzutage, da es ORM gibt, den Spezialfällen vorbehalten und ich darf meine Datenbank objektorientiert programmieren.

Nun gehe ich mit einem anderen Python-Programm die XML-Datei durch und ohne die gesamte Datei im Speicher zu halten, lese ich Zeile für Zeile und speichere für jede Zeile ein User-Objekt:

import xml.etree.ElementTree as etree
from db import users
from sqlalchemy import create_engine
engine = create_engine('postgresql://dev:@localhost:5432/dev')
columns = ['SOId', 'SODisplayName', 'SOWebsiteUrl', 'SOReputation', 'SOLocation',
                     'SOAboutMe', 'SOCreationDate', 'SOLastAccessDate', 'SOProfileImageUrl']
conn = engine.connect()
value_map = {}
ins_rows = []
for event, elem in etree.iterparse('users.xml', events=['end']):
    for col in columns:
        orig_col = col[2:]
        val = elem.attrib[orig_col] if orig_col in elem.attrib else None
        value_map[col] = val
    ins_rows.append(value_map)
    if len(ins_rows) == 1000:
        conn.execute(users.insert(), ins_rows)
        ins_rows = []

Allerdings gibt es hier noch eine wichtige Geschwindigkeitsoptimierung, die die ganze Prozedur nicht 30 sondern nur 5 Minuten dauern lässt: Ich speichere die 9.600.000 User nicht einzeln, sondern in Päckchen zu je 1000 Usern. Denn bei jedem Speichervorgang wird auch eine Transaktion beendet und das ist auf jeder SQL-Datenbank teuer.

Sind die Daten schließlich in der Datenbank, lässt sich mit diesem SQL-Befehl eine sinnvolle Auswahl der gigantischen Datenmenge treffen, nämlich alle User aus Bonn:

SELECT * FROM public."User" 
	where "SOLocation" like '%Bonn%' 
		and "SOWebsiteUrl" is not null
		and "SOWebsiteUrl" <> ''

Diese Daten wiederum lassen sich mit jedem besseren SQL-Werkzeug als CSV-Datei speichern, die man in jedem Tabellenprogramm lesen kann, womit wir wieder am Anfang wären, die Datei mit den Stackowerflow-Entwicklern aus Bonn.