Schlagwort-Archive: Twitter

Eine chronologisch sortierte Historie zu #aufschrei

Im Januar 2013 wurde auf Twitter unter dem Hashtag #aufschrei eine Aktion gestartet, die eine (nicht nur) deutschlandweite Debatte über Sexismus auslöste. Bisher fehlte eine Historie mit allen Tweets zu diesem Hashtag. Aufgrund der eingeschränkten Möglichkeit von Twitter über die API darauf zuzugreifen, wird dieses nie der Fall sein. Aber zumindest für die ersten zwei Wochen konnte ich ein Archiv erstellen und in einer angenehmen Form darstellen:

http://aufschrei.konvergenzfehler.de/

Scraping

Im Herbst 2012 entwickelte ich ein Script, um die Tweets für das #refugeecamp Berlin (später #rfcamp) einzusammeln. Für mich war es etwas Fingerübung in Python und mit der Twitter-API. Eigentlich hatte ich vor, daraus eine Webseite zu erstellen, die die Aktionen (Spendenaktionen, Temperaturwerte, Bilder, Gespräche mit Politikern und vor allem die Übergriffe der Polizei) darstellt und wichtige Zeitpunkte hervorhebt. Dank Zeitmangel und fehlenden Fähigkeiten wurde daraus nichts.

Ende Januar stieß mich map an und meinte “Du hast da doch mal dieses Script zum Scraping geschrieben – kannst Du die mal auf #aufschrei ansetzen? Uns fehlt der allererste Tweet.” Gefragt, getan. Die Tools lagen noch alle einsatzbereit auf einem Server und eine knappe Stunde später hatte ich etwa 50000 Tweets runtergeladen. Großer Jubel, als wir feststellten, dass der allererste Tweet dabei war.

Das Script sucht mit der Twitter Search API nach dem angegebenen Hashtag. Dabei geht es vom aktuellen Zeitpunkt iterativ immer weiter zurück, bis keine Tweets mehr gefunden werden. Die Search API findet Tweets der vergangenen neun Tage – mehr bietet Twitter nicht an. Außerdem gibt es Beschränkungen auf 200 Tweets pro Request, aber es kann das Datum des letzten gefundene Tweets als Startadresse für eine neue Suche angegeben werden.

Zwei Kleinigkeiten, über die ich schon beim Scraping für das #refugeecamp gestolpert war: Twitter erlaubt sich, Benutzer_innen aus der Search API zu verbannen, die viel mit einem Hashtag twittern. Die Benutzerin ist nicht geblockt, aber wird nicht mehr mittels einer Suche zurück gegeben. Die andere Kleinigkeit ist die Einstellung auf “gib mir alles” und nicht nur die “most trending Tweets”. Letzteres ist der Default, weshalb die Tweets nicht chronologisch ankommen, sondern nach irgendeiner Heuristik von Twitter, die meint, das seien die interessantesten Ergebnisse für diese Suche.

Fast schon unnötig es explizit anzugeben, aber es werden nur Tweets von Accounts erfasst, die zu dem Zeitpunkt auf public geschaltet waren. Wer einen protected Account hat, wird über die Search API nicht gefunden.

JSON-Daten – und nun?

Das Scraping-Script spuckte mir nach Tagen aufgeteilt JSON-Dateien aus. Da lag nun alles drin, aber ansehnlich war es nicht. Da ich eh schon mal ein Projekt mit Django machen wollte und sogar noch ein paar Anfänge zur #refugeecamp-Geschichte rumliegen hatte, begab ich mich an die Datenbankmodellierung. Da Django das Datenbank-Backend ziemlich egal ist, wählte ich PostgreSQL. Doch trotz eines fertigen Models mussten die Daten in die Datenbank kommen. Das klappt bei Django ganz gut mit Fixtures, was JSON-Dumps der Datenbank sind.

Also baute ich ein Script, was mir die JSON-Search-API-Dateien in JSON-Django-Fixtures umwandelte. Damit waren die 93667 Tweets in der Datenbank gespeichert. Recht fix schrieb ich ein paar Django-Views und einfache Templates, die mir eine chronologische Sortierung ausgaben.

Aber es war hässlich.

Das mag daran liegen, dass ich aus der Zeit von HTML2 komme und es seit 1995 gewohnt bin, Webseiten in einem Texteditor zu schreiben. CSS? Gab es nicht. JavaScript? Wurde im Browser abgeschaltet, weil böse. Pures HTML – mehr nicht! Und Webdesign? Ich fahre kein Snowboard.

Irgendwer verwies mich auf Bootstrap. Nachdem ich die Doku drei Mal durchlies, konnte ich es anwenden. So wurde die Ausgabe in Django immer schöner und ganz langsam gefiel mir mein Werk.

Weniger ist mehr

So ganz im klaren darüber, was die Webseite eigentlich können sollte, war ich mir nicht. Aber es gab ein paar Ideen. So war eine der Ideen, eine interaktive Oberfläche zu bieten, in der mit Crowd-Working die Tweets mit Tags versehen werden (beispielsweise Spam, KackscheisseVorfall, etc.). Das hätte Interaktion benötigt und Schreibzugriff auf die Datenbank. Oder ich wollte coole Auswertungen machen, so mit Graphen und Zeitleiste. Alles automatisiert aus den Daten in der Datenbank. Immer wieder überlegte ich, wie ich das machen sollte. Zwischendurch lag deswegen das Projekt monatelang in der Schublade (oder eher auf dem Server) und ich packte es nicht mehr an.

Aber bevor ich gar nichts mache und die Daten vergammeln, fällte ich die Entscheidung, nur eine Timeline anzuzeigen. Einfach und simpel.

Lokal ist besser als Remote

Mit meiner ersten Darstellung einer Timeline fiel mir auf, dass Links in Tweets vom Twitter-eigenen URL-Shortener t.co zum “Zwecke der Qualitätssteigerung” gekürzt werden. Dadurch lässt sich nicht erkennen, wohin verlinkt wird, geschweige eine Ahnung vom Inhalt zu erhalten.

Also, noch einen Scraper bauen, der alle Links durchgeht, die Original-URL wiederherstellt und am besten sogar noch die HTML-Titelzeile abholt und in der Datenbank speichert. Das machte ich mit der Python Bibliothek BeautifulSoup. Zwischendurch rannte ich in einen nicht lösbaren Speicherkiller-Bug von BeautifulSoup, weshalb das Scraping immer wieder neu angestoßen werden musste. Nach ein paar Stunden hatte ich 13992 Links zusammen und bewahrte sie wertvoll in der Datenbank auf.

Dieses Scraping lief erst im Mai und Juni 2013. Etliche Links werden als Fehler angezeigt, da die Webseite nicht mehr vorhanden war – ob nun tote Webseiten, gelöscht oder vom öffentlich-rechtlichen Rundfunk depubliziert worden.

Außerdem checkte ich die Links, ob es Bilder sind. Nicht jeden Webseiten-Bilder-Dienst habe ich unterstützt und bei manchen ist es auch verdammt schwer an die Original-URL eines Bilds zu kommen. Aber ein paar große sind dabei. Diese Bilder speicherte ich auch weg. 783 Bilder mit 80MB liegen lokal vor.

Und noch etwas wollte weggespeichert werden: Die Avatare der 25888 Twitter-Benutzer_innen, die an der Aktion mitgewirkt haben. Der ein oder andere Account war inzwischen gelöscht oder gesperrt, aber viele Avatare sind zusammen gekommen. Diese stellen einen alten Stand dar – aktuelle Avatare lade ich nicht runter.

Alles wegwerfen und neu anfangen

Ein paar Django Views waren zusammen gekommen, die Timeline konnte angezeigt werden, etwas Statistik war auch möglich. Aber alles sah zusammengestückelt aus. Nicht wie aus einem Guss. Da hilft nur eins: mit dem gesammelten Wissen noch mal neu machen.

Aber das geht dann recht fix. An einem Wochenende ist in etwa zehn Stunden die jetzige Seite mit allen Views entstanden. Direkt auf Bootstrap 3 aufgesetzt, mit diversen Templates, die einfach nur included werden, und nicht all zu viel Schnickschnack. Als kleine und binnen weniger Minuten umgesetzte Fingerübung kamen noch die reine Bilder– und die Linklisten-Timeline hinzu. Der “zufällige Tweet” war auch keine große Sache, musste nur etwas durchdacht werden.

Fehlte noch eine Startseite in einem etwas anderen Layout. Mit Bootstrap kein großes Problem.

Deploy

Bisher lief die Seite nur im Django Development Modus. Das ist kein Setup, was viele Requests abfrühstücken kann. Mit etwas Suche entschied ich mich für eine Kombination aus Nginx und uWSGI. Dieses Setup aufzusetzen ist nicht ganz einfach, aber es läuft sehr stabil.

Und was ich bei meinem aktuellen Job gelernt habe: Caches einsetzen. Viele Caches. Neben dem Datenbank-Cache gibt es noch einen Memcached und Nginx cached auch noch mal die uWSGI-Requests. Die Bilder werden aktuell nicht gecached, da sie im Laufe der Zeit vom Betriebssystem in die RAM-Caches geladen werden statt jedesmal auf die Festplatte zuzugreifen. Lägen sie Remote, würde auch hier ein Cache wieder Sinn machen.

Meine Freude war echt groß, als ich die Seite http://aufschrei.konvergenzfehler.de/ über dieses Setup aufrufen konnte. Und dass sie sogar auf einem Smartphone dank Bootstrap ohne Code-Änderung sehr gut aussieht!

Weiterentwicklung

Für mich ist das Projekt an dieser Stelle erstmal beendet, damit ich mich wieder anderen Dingen zuwenden kann. Aber wer gerne mit den Daten arbeiten oder die vorher genannten Ideen umsetzen möchte, kann das Projekt bei GitHub clonen. Das Fixture für die Datenbank liegt mit drin. Die Mediendateien (130MB) habe ich separat gepackt. Die Installation sollte “Standard-Django 1.5” sein.

Ich freue mich, wenn ich irgendwann in Zukunft noch tolle Projekte daraus entstehen sehe. Vielleicht eine schön aufgewertete Timeline. Oder sogar ein etwas größeres Projekt, was automatisch Hashtags mitschreibt und anzeigt, sodass in Zukunft nicht so viel Arbeit notwendig ist.

Das Empörungsmedium

Ich erkläre meinen „Twitter-Urlaub“ nach 25 Tagen für beendet. Nach 30 Tagen wäre der Account seitens Twitter endgültig gelöscht worden. Immer wieder wurde ich gefragt, ob ich zurückkehren würde.

In diesen 25 Tagen erfuhr ich von diversen anderen Menschen, die ihren Account geschlossen hatten. Nach wenigen Stunden oder Tagen kehrten sie zurück. Für meine Entscheidung ließ ich mir etwas mehr Zeit, um mich zu orientieren. An dieser Stelle bedanke ich mich bei all denjenigen Menschen, mit denen ich in den vergangenen Wochen lange und ausgiebige Gespräche führte! ❤

Entscheidung

Die Entscheidung fiel nicht leicht – nach reiflicher Überlegung gibt es drei Punkte, die mich zur Reaktivierung bewogen:

1. Verabredungen, Treffen, kurze Gespräche

Twitter hat sich als ein gutes Medium zum nahezu spontanen Verabreden etabliert. Statt Anrufe, SMS oder Facebook-Events, kann ich darüber recht gut in der eigenen Peer-Group einladen. Oder es ist ersichtlich, wo sich die Peer-Group gerade befindet und ich kann nachfragen, ob ich noch vorbei schauen kann.

2. Möglichkeit zur Kommunikation

Es passiert mir immer häufiger, dass ich keine XMPP (vulgo: Jabber) oder E-Mail Adressen, geschweige Telefonnummern von Bekannten besitze. Meistens treffe ich andere Menschen, erfahre oder erfrage den Twitter-Account und folge diesem. Durch gegenseitiges Folgen gibt es die Möglichkeit, DMs zu senden.

3. Manchmal habe ich doch etwas zu sagen

Mit meinem Account habe ich eine gewisse Reichweite errungen. Ab und an habe ich doch etwas zu sagen und möchte es mitteilen. Da ich bereits in der Vergangenheit etwas sagte, wurde darauf verlinkt. Diese Links zu den eindeutigen IDs der Tweets waren in der Zeit der Deaktiverung nicht erreichbar. Ich ärgere mich selbst immer, wenn Links ins Leere führen und kann Depublizierung nicht gut heißen.

Kommunikationsarten

Im Nachgang zur Deaktivierung des Accounts, versuchte ich, die Gründe zu kanalisieren. In der Ingenieurswissenschaft wird traditionell zwischen zwei Klassen von Kommunikation unterschieden, was sich auch auf Menschen und deren Kommunikationsmittel übertragen lässt.

Zur synchronen Kommunikation gehören direkte Gespräche, wie sie von Angesicht zu Angesicht durchgeführt werden oder am Telefon. Hier lassen sich ebenso IRC oder Instant Messenger einsortieren. Das prägende an dieser Art von Kommunikation ist die direkte Antwort auf das vorher Gesagte. Informationen, die mehrere Augenblicke gealtert sind, haben an Bedeutung verloren.

Bei der asynchronen Kommunikation hingegen kann zwischen dem Verbreiten der Information und einer Reaktion sehr viel Zeit verstreichen. Als Klassiker gilt der Brief oder moderner die E-Mail. Die Information ist in der Regel so aufbereitet, dass viele Informationshappen übermittelt werden. Gedanken können komplett ausformuliert werden und somit sehr tief ins Detail gehen. Zu dieser Art von Kommunikation zähle ich auch einen Blogpost.

Nun möchte ich eine dritte Klasse von Kommunikation hinzufügen, die in speziellen Ingenieursdisziplinen bereits so bezeichnet wird (Bereich: Bussysteme): die semisynchrone Kommunikation. Nachrichten werden verbreitet und bieten die Möglichkeit zur Reaktion und gleichzeitig zur Referenzierung mittels Identifier (URI). Der Klassiker hierbei ist Twitter. Noch lange Zeit später kann wie bei der asynchronen Kommunikation auf eine verbreitete Information verwiesen und darauf reagiert werden. Im Rahmen der Reaktion skaliert Twitter so weit, dass es wie synchrone Kommunikation – eine Art Chat – gebraucht werden kann.

Twitter, das Empörungsmedium

Bei meinen Gesprächen in den letzten Wochen habe ich einige Thesen entwickelt, wodurch Twitter als Medium zur Empörung überaus gut geeignet ist.

Ausformulierung von Gedankengängen

Es ist nahezu unmöglich – oder oft nur durch starke Kürzung – Gedankengänge auszuformulieren. 140 Zeichen sind dafür schlicht nicht genug. Erst durch die Nutzung eines weiteren (asynchronen) Mediums, wie das eigene Blog, Ton- oder Videoformate oder auch pastebin, kann die nötige Zeichenlänge gefunden werden, mit dem teils komplexe Gedankengänge in voller Länge niedergelegt werden können. Sehr häufig wird mittels eines Links in einem Tweet darauf hingewiesen und die Diskussion auf Twitter geführt. Weitere Diskussionsstränge ergeben sich durch die Verbreitung des Links. Es müssen nicht nur die eigenen Beiträge sein – üblich ist auch die Referenzierung auf etwas Gesagtes einer anderen Person.

„Im Internet wird mehr über Menschen kommuniziert, als mit ihnen.“ – @zweifeln

Ab und an ist zu beobachten, dass das ursprüngliche Medium gar nicht mehr konsultiert wird. In der Diskussion werden zusammenhanglose Satzfragmente auseinandergepflückt, die in der Gesamtdarstellung durchaus ein anderes Bild ergeben. Für die Recherche fehlt offenbar die Aufmerksamkeit oder auch Zeit. Schließlich ist das Medium semisynchron und in wenigen Minuten oder Stunden wird die nächste „Sau durchs Dorf gejagt“.

Gesunkene Schwelle für Beleidigungen

Eine Beleidigung ist schnell geschrieben und abgeschickt. Etwas distanziert vom eigenen Ich über einen Twitter-Account sinkt die Schwelle dazu. Mit etwas mehr Gelassenheit mag die Beleidigung übertrieben gewesen sein, doch da Twitter an dieser Stelle zu einem synchronen Kommunikationsmedium wurde, ist Schnelligkeit gefragt und somit wird einfach rausposaunt, was durch den Kopf geht.

Vor ein paar Wochen (nachdem diverse Menschen ihren Twitter-Account schlossen) schrieb Sascha Lobo dazu in seiner Kolumne unter dem Titel „Netzhass ist gratis“. Ich gehe davon aus, dass an dieser Stelle diverse Leser die Nase rümpfen, da sie „den Lobo nicht leiden können“ und wie ich überhaupt darauf kommen könne, einen seiner Texte als Referenz heranzuziehen – dabei haben sie vermutlich gar nicht mal die Kolumne gelesen. (Die Kritik, zwischen „Analogwelt“ und „Digitalwelt“ zu differenzieren ist eine andere, bereits an diversen Stellen geführte Diskussion mit jeder Menge Empörung.)

Im Podcast „Leitmotiv 002 – Eine Meinungsänderung – mit @herrurbach“ von Caspar Clemens Mierau im Gespräch mit Stephan Urbach sprach dieser an, dass er Personen, die ihn über das Netz beleidigen, teilweise anruft und auf die Beleidigung anspricht. Die Beleidiger sind oft sehr erstaunt darüber und reden sich gerne damit raus, dass es nicht so gemeint war. Der Wechsel des Mediums und von semisynchroner in synchrone Kommunikation in Verbindung mit der weggefallenen Abstraktion der Person im Netz zu der realen Person, führen zum Nachdenken und einer Distanzierung vom Geschriebenen.

Empörung ist cool

Es kommt mir vor, dass Empörung zur Steigerung des Coolnessfaktors führt. Wer sich viel empört, ist cool, ist ein Vorbild und ein Kumpel, mit dem gerne abgehangen wird. Das Tolle an der semisynchronen Kommunikation: alle können sich empören. Es ist nicht mehr so wie auf dem Schulhof, wo die coole Truppe über jemanden herzieht, diese Person aber ein paar Sekunden länger zur Formulierung einer passenden Antwort benötigt, in der Zeit aber schon eine gelangt bekommen hat oder die coole Truppe lachend von dannen zieht. Nein, bei der semisynchronen Kommunikation ist die Zeit zum Nachdenken für einen coolen Empörungstweet gegeben! Ich muss nicht den erstbesten Gedanken rauspusten, sondern kann mir ruhig acht Sekunden mehr Zeit lassen, um sogar die beste Formulierung dafür zu finden, um damit viele Favs und Retweets abzusahnen! Empörung führt also direkt zu einer Bestätigung in der Peer-Group, was das vor sich hinsiechende Ego mal so richtig aufmuntert. Sind die Favs für eure Empörung wirklich das, worauf ihr stolz seid? (Der letzte Absatz wurde mit einem Bissen mehr Empörung geschrieben – ich hatte ja Zeit zum Nachdenken.)

Nichtcool

Smartphones und überall

Twitter vor fünf Jahren scrollte nicht. Das iPhone war gerade erfunden, Clients dafür gab es erst gar nicht und dann nur sehr begrenzt, das digitale Mobilfunkdatennetz entwuchs gerade erst den Kinderschuhen. Die Timeline wurde am heimischen Monitor gelesen – oder auf der Arbeit. Ab und an wurde ein Tweet verfasst, der vielleicht das Tagesgeschehen zusammenfasste.

Jetzt gibt es die Möglichkeit immer und ständig twittern zu können. Ich kenne Menschen, die dauerhaft auf ihr Smartphone starren, die Timeline ständig updaten, um sich berieseln zu lassen, oder jeden kleinsten quersitzenden Furz raushauen. Ergibt sich ein Moment zum Mitreden, wird halt mal was dazu gesagt. Ein paar Momente später wäre es schon wieder vergessen – sowohl aus dem Kopf als auch im Medium.

Die Alternativen

Seien wir ehrlich, es gibt keine Alternative. Insbesondere kein Medium, was die Reichweite von Twitter hat. Für synchrone Kommunikation klappt es teilweise mit Instant Messengern, die ich während meines „Twitterurlaubs“ wieder stärker verwendete.

Einige Personen behaupten, der Trend verlagert sich zu APP.NET, einem Twitter-ähnlichen Dienst. Dadurch, dass die Entwicklung komplett frei läuft und kein Unternehmen dahinter steckt, das mit den Inhalten der Nutzer Geld verdienen möchte, wird ein Kostenbeitrag zur Nutzung verlangt. So etwas schränkt ein und begrenzt die Reichweite. Tatsächlich kommt mir APP.NET derzeit wie ein Netz aus weißen, mittelalten, gebildeten und wohlhabenden Männern aus Industrienationen vor (Privilegiencheck durchgeführt). Die Inhalte der Nachrichten sind eher technischer, weniger sozialer. Aber wenn hier und da mal eine Diskussion entflammt, ist sie in der Regel reflektierter und weniger empört.

Überlebenstricks gegen die Empörung

Eine wasserdichte Strategie gegen eine Flut von Empörungen in der Timeline habe ich nicht. Was aber gut hilft, sind Listen. Und Clients, die Listen als alternative Timeline darstellen können. Mehrere hundert Followings schaffe ich einfach nicht zu lesen – selbst nicht ohne Vollzeitjob. Mit einem guten Misch an Personen in dieser Liste gelingt es mir, auf dem aktuellen Stand an gemeinsamen Themen zu bleiben.

Ab und an gibt es „Retweet-Monster“. Da werden diverse Informationen einfach weiter verteilt, auch wenn mich das keinen Deut interessiert. Seit geraumer Zeit gibt es die Möglichkeit, die Retweets von einzelnen Personen zu unterbinden, sodass nur eigens geschriebene Tweets angezeigt werden. Das hilft hier und da, etwas Timelinehygiene zu betreiben.

Muten (Stummschalten) von Hashtags und Personen sorgt dafür, dass bestimmte (überdrüssige) Themen nicht mehr angezeigt werden. Leider werden häufig Hashtags „vergessen“ (damit das auch noch die Muter mitbekommen) oder der Platz reichte nicht mehr dafür aus. In harten Diskussionen hilft es schon mal, eine oder beide Diskussionsteilnehmer zu muten. Leider ist Muten eine Funktion des Clients und nicht von Twitter selbst.

Für die erweiterte Timelinehygiene helfen noch Unfollow und Block. Das ist das Pendant zu „ich will nichts mehr von Dir hören“ und „ich möchte es nicht, dass Du meine Inhalte auf einfachem Wege liest“. Natürlich ist es weiterhin möglich, dass diese Personen Empörung über mich oder ein Thema verbreiten. Nur, eine Diskussion kann bei einer starken Voreinnahme eh nicht mehr statt finden.

„The only winning move is not to play“ War Games (1983)

Ab und an lohnt sich ein Protected Account. Nachrichten werden nur eigens bestätigten Personen sichtbar gemacht, wodurch ich selbst über die Reichweite meiner Inhalte bestimme. Der Nachteil ist hier ganz klar, dass ich nicht mehr auf Personen reagieren kann, die ich nicht freigeschaltet habe (und die eh zuerst eine Anfrage an mich stellen müssen, mir folgen zu dürfen). Ein Protected Account ist ein halbherziger Versuch, sich einen Rückzugsort zu schaffen, ohne den Anschluss an die Peer-Group (in diesem Fall häufig vertrauenswürdige Follower) zu verlieren.

Dazu in eigener Sache: ich habe auch einen Protected Account, bei dem ich die Zahl der Follower recht klein auf ein paar vertrauenswürdige Personen halte oder womit ich anderen Protected Accounts aus meinem engeren Umfeld folge. Über manche Follower-Anfragen muss ich länger nachdenken – und manche lehne ich ganz ab.

Zusammengefasst halte ich es mit den Worten von @heliantje im Gedicht „Die Shitstormer“:

„Wir hetzen, wir ätzen!“

PS: Eigentlich hätte ich gerne @Pylon als Twitter-Account gehabt, aber der war zur Zeit meiner Registrierung im Mai 2008 nicht mehr frei (und wird seitdem von einem nicht genutzten Protected Account blockiert). Deshalb gibt es an meinem Account den „Nachnamen“ C: @PylonC

[28C3] Presale Sniper-Scripts

Der Vorverkauf zum 28C3 ist vorbei. Am Dienstagvormittag gab es die dritte Chance, Tickets für alle vier Tage zu erwerben. Vor Ort wird es nur noch Tages- und Nachttickets in sehr begrenzter Zahl geben.

Internetkurort
Statt zum Chaos Communication Congress mal schön Urlaub machen

Der Vorverkauf lief nicht besonders glücklich. Der kleine Server (ich kenne die Netzinfrastruktur nicht, aber vom Gefühl her, kommt das am besten hin) mit seinem nginx wurde während der Ticket-Verkaufszeiten mächtig attackiert und lieferte gerne den Fehler “502 Bad Gateway” aus statt die Presale-Webseite anzuzeigen. Insbesondere auf Twitter konnte unter dem Hashtag #28C3 das Ärgernis mitverfolgt werden. Die Ankündigung zum Vorverkauf im CCC-Events-Blog ist mit knapp 300 Kommentaren gut gefüllt.

Bereits zum ersten Vorverkaufstermin kursierten Scripte im Internet, die die Verpeilung abnehmen sollten und automagisch ein Ticket bestellen. Die Vorgehensweise ist grundsätzlich die gleiche: Der Besucher erstellt zu einem beliebigen Zeitpunkt während der gesamten Vorverkaufsphase einen Account im Presale-System, wählt das gewünschte Ticket aus und startet das Script. Dieses ruft in regelmäßigen Abständen die Webseite ab und überprüft, ob Tickets verfügbar sind. Daraufhin wird die Bestellung getätigt.

Ein paar Scripte habe ich gespeichert, aber nicht immer stand die Quelle oder Lizenz dabei. Zum Zweck des Selbststudiums erlaube ich mir die Freiheit, diese hier zu veröffentlichen.

Der automatisierte Webbrowser

#!/usr/bin/env python
# encoding: utf-8
"""
untitled.py

Created by Thorsten Philipp on 2011-11-06.
Copyright (c) 2011 . All rights reserved.
"""


import sys
import os
import platform
from selenium import webdriver
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.support.ui import WebDriverWait
import time


USERNAME = 'your_username'
PASSWORD = 'your_password'

def main():

    driver = webdriver.Firefox()
    driver.get("https://presale.events.ccc.de/order")
   
    NO_TICKET = True
    while NO_TICKET:
        if driver.current_url == 'https://presale.events.ccc.de/accounts/sign_in':
            username = driver.find_element_by_xpath("//input[@id='account_username']")
            password = driver.find_element_by_xpath("//input[@id='account_password']")
            username.send_keys(USERNAME)
            password.send_keys(PASSWORD)
            password.submit()
       
        # Now wait for the order page
        WebDriverWait(driver, 10).until(lambda x : x.find_element_by_xpath("//input[@value='Confirm Order']"))
        confirmOrder = driver.find_element_by_xpath("//input[@value='Confirm Order']")
        confirmOrder.submit()
        time.sleep(5)
        errorField = driver.find_element_by_xpath("//div[@id='flash_error']")
        if errorField.text == 'There are currently not enough tickets available.':
            print "%s. Trying again" % errorField.text
            driver.get('https://presale.events.ccc.de/order')
        else:
            print "Something else happend! Maybe a successful order? I'm not touching anything any more. Please take over Commander"
            while True:
                if platform.system() == 'Darwin':
                    os.system("say Help")
                else:
                    print "\a"
                time.sleep(10)


if __name__ == '__main__':
    main()

Das erste mir bekannte Sniper-Script nutzt die Python Bindings des Selenium/Webdriver Projekts. Es öffnet einen Browser (in diesem Fall Firefox), ruft die Presale-Webseite auf, logt sich mit dem gegebenen Usernamen und Passwort ein und wartet in einer Schleife darauf, dass Tickets verfügbar sind. Dem Autor dieses ersten Scripts war nicht bekannt, wie die Seite aussehen würde, wenn Tickets vorhanden sind. Das Script gibt nur einen Hinweis aus (unter Mac OS X wird auch eine Sprachausgabe gestartet) und lässt den Benutzer weiter machen.

Screen-Scraping

import requests
from BeautifulSoup import BeautifulSoup
from time import sleep

s = requests.session()

username = ''
password = ''

print "CCC-Presale 28C3 Script by @sctan [https://github.com/szechuen/CCC-Presale]"

# Sign In

print "Accessing Sign In..."

while True:
    sign_in = s.get("https://presale.events.ccc.de/accounts/sign_in")
    if sign_in.ok: break
    print "Request Error - Retrying..."

sign_in_soup = BeautifulSoup(sign_in.content)

account_post = {}
for item in sign_in_soup.findAll("input", type="hidden"): account_post[item['name']] = item['value']
for item in sign_in_soup.findAll("input", type="submit"): account_post[item['name']] = item['value']
account_post['account[username]'] = username
account_post['account[password]'] = password

account_link = "https://presale.events.ccc.de" + sign_in_soup.find("form")['action']

# Account

print "Accessing Account..."

while True:
    account = s.post(account_link, data=account_post)
    if account.ok: break
    print "Request Error - Retrying..."

account_soup = BeautifulSoup(account.content)

ordered = False
page = account
soup = account_soup

while not ordered:

    post = {}
    for item in soup.findAll("input", type="hidden"): post[item['name']] = item['value']
    for item in soup.findAll("input", type="submit"): post[item['name']] = item['value']

    link = "https://presale.events.ccc.de" + soup.find("form")['action']

    while True:
        page = s.post(link, data=post)
        if page.ok: break
        print "Request Error - Retrying..."
   
    soup = BeautifulSoup(page.content)

    last = open("last.html", "w")
    last.write(soup.prettify())
    last.close()

    if soup.find(text=lambda(x): x.find("There are currently not enough tickets available") != -1):
        print "Not Open - Sleeping before retrying..."
        sleep(1)
    else:
        print "Ordered :D"
        ordered = True

Das zweite Script, auch in Python, benutzt den Screen-Scraper Beautiful Soup. Die Selbstbeschreibung lautet “an HTML parser optimized for screen-scraping”. Durch die erste Runde des Vorverkaufs war bereits bekannt, dass nur noch der ‘Confirm’-Button getätigt werden muss, wenn Tickets vorhanden sind. Das macht sich dieses Script zunutze und läuft komplett autark ohne Webbrowser ab. Es geht einfach nur die HTML-Sourcen durch, füllt die Eingabefelder zum Login aus und schickt HTTP-Kommandos ab.

Bash mit curl

#! /bin/bash


#---------------------login and password----------------------------------------

user=$1 && shift
pass=$1


echo "getting for user = $user, pass = $pass"


#-------------------------------------------------------------------------------


cookie="./coockie$user"
agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.8 (KHTML, like Gecko) Chrome/17.0.928.0 Safari/535.8"



function callit() {

    cmd=$1
    echo $cmd
    res=$($1 -w "%{http_code}")
    echo $res


}

function prepare() {

    login=''


    page=$(curl --retry 100 --retry-delay 1 --connect-timeout 75  -s -A $agent -c $cookie "https://presale.events.ccc.de/accounts/sign_in")
   

    token=$(echo $page  | grep -o '<input name="authenticity_token" type="hidden" value=".*"' | sed s/'.*value="\(.*\)"'/'\1'/g)


    login="utf8=&#x2713;&authenticity_token=$token&account[username]=$user&account[password]=$pass&commit=Sign+in"

   
   
    page=$(curl --retry 100 --retry-delay 1 --connect-timeout 5 -A "$agent" -s -c $cookie  -X POST -d "$login" "https://presale.events.ccc.de/accounts/sign_in")

    page=$(curl --retry 100 --retry-delay 1 --connect-timeout 75 -s -A "$agent" -b $cookie  -c $cookie "https://presale.events.ccc.de/")

    echo $page | grep -o "<td>confirmed</td>" && echo "GOT ITTTTTTTTTTTTTT !!!" && exit 0


    token=$(echo $page| grep -o '<input name="authenticity_token" type="hidden" value="[^"]*"' | sed s/'.*value="\([^"]*\)"'/'\1'/g)


    order="utf8=&#x2713;&authenticity_token=$token&commit=Confirm+Order"

   
}



function getIt() {


    confirm=$(curl --retry 100 --retry-delay 1 -L  -A "$agent" -s -b $cookie  -c $cookie -X POST -d "$order" "https://presale.events.ccc.de/order/confirm/")

    res=$(curl --retry 100 --retry-delay 1 -s -A "$agent" -b $cookie  -c $cookie "https://presale.events.ccc.de/order")

    echo $res
}



prepare








while [ true ]
do

it=$(getIt)


echo $it | grep '<html><body>You are being <a href="https://presale.events.ccc.de/accounts/sign_in">redirected</a>.</body></html>' > /dev/null && echo "error in site " && prepare && continue

ret=$(echo $it| grep -o "<div id='flash[^>]*' class=visible>[^<]*</div>" | sed s/"<div id='flash_\(.*\)'.*visible>\(.*\)<\/div>"/"\1 : \2"/g)

status=$(echo $it | grep status)

echo $ret | grep "^error" && continue

echo $it | grep -o "<td>confirmed</td>" && echo "GOT ITTTTTTTTTTTTTT !!!" && break



done

Auch dieses Script konnte erst nach der ersten Runde des Vorverkaufs entstanden sein, da es überprüft, ob der Status nach der Order auf ‘confirmed’ gesetzt wurde. Der Ansatz, die Webseite mit curl abzurufen und mit grep die wichtigen Daten auszulesen, ist passabel, aber nicht wirklich effizient. Ich habe das Script nicht selbst getestet, aber ich kann mir vorstellen, dass es sehr gut an der CPU knabberte.

KISS-Prinzip

#!/bin/bash


while [ 1 ]; do
wget -q -T 10 -O out --load-cookies=cookie -U "`cat ua`" --post-file=postdata --no-check-certificate https://presale.events.ccc.de/order/confirm
if [ -s out ]; then
grep -q "Confirm Order" out || exit
fi
rm -f out
sleep 1
echo Retrying...
done

In einer Schleife ruft wget mit dem vorher im Browser erstellten Cookie die Routine zum Bestätigen der Order auf – drückt also quasi dauerhaft auf den Button. Das Script gibt erst dann auf, wenn auf der Seite nicht mehr der Text ‘Confirm Order’ erscheint. Eine sehr elegante Lösung, bei der nicht bekannt sein muss, wie die Bestätigungsmeldung aussieht.

Anti-Sniper

In der dritten Runde des Vorverkaufs wurde ein simples Captcha eingeführt. Mit eigenen Augen habe ich es nicht gesehen, aber es war wohl nur die Eingabe einer ausgeschriebenen Zahl notwendig (“six million, one thousand, eight hundred and 5”). Das reichte aus, um Menschen eine Chance zu lassen, denn in der Kürze bis zu dem Zeitpunkt, zu dem die Scripte umgeschrieben sind, dürften alle Tickets bereits vergeben sein.

Hier nicht gelistet, aber den Dämlichkeits-Preis erhält der Autor des Scripts, der es mit vollen Login-Daten auf eine Paste Server Webseite kopierte…