Mehr API's braucht die Stadt oder ein kleiner Hack um zu sehen wann die Bücher zurück in die Bibliothek müssen

Alles ist besser mit öffentlich zugänglichen API's. Sogar Städte. Konnte keiner mit rechnen. Gerade aktuell ist mir keine Stadt in Deutschland bekannt die erstmal ohne viel Bürokratie öffentliche API's zu verfügung stellt. Und zwar am besten alles was unter der eigenen Verwaltung steht. Zum Beispiel die Daten der öffentlichen Verkehrsmittel oder halt auch die Stadtbibliothek. Und so war in auf einem Spaziergang durch Nürnberg mit einem guten Freund. Wir unterhielten uns über die große Hürde verwendbare Daten von der Stadt zu bekommen. Ich zum Beispiel habe oft das Problem, dass ich nicht mehr weiß wann meine Bücher zurück zur Bibliothek müssen. Ok gut. Die Stadtbibliothek bietet eine Website an in der man sich einloggen kann und ein paar Informationen bekommt. Neben einer Suche gibt es auch eine Übersicht der ausgeliehenden Bücher und dem Rückgabe Datum. Die ganze Seite ist ein Schmerz im Arsch. Es gibt sehr skurille Login-Sessions mit sehr komisch generierten Urls. Und was will man? Eine schöne Bibliotheks-App? Noch praktischer: Ein ical-File welches ich in den Kalendar meiner Wahl einbinden kann. Also kam ich zu dem schluß einen Wrapper zu bauen. Am besten wäre es wenn am Ende eine API rauskommt auf der andere Apps aufbauen können. War nicht so einfach wie ich gedacht habe. Als erstes hatte ich einfach requests und BeautifulSoup probiert. Damit konnte ich selbst nach Tagen den Login nicht hinbekommen. Den zweiten Anlauf bestritt ich mit RoboBrowser. Der setzt auch auf requests und BeautifulSoup, soll aber gut mit Sessions und Cookies zurecht kommen. Gleiches Spiel. Suche habe ich hinbekommen, den Login aber nicht. An diesem Zeitpunkt war ich kurz davor das Projekt in die Tonne zu treten. Dann googelte ich umher und fand selenium. Ein Testingframework um Webapps zu testen. Eigentlich dafür da um verschiedene installierte Browser zu öffnen um automatisch zu testen ob die Seite auch funktiert. Nun gibt es einen WebKit Browser der ohne grafische Oberfläche auskommt. PhantomJS. Ist eigentlich dafür da um die automatisierten Tests schneller durchlaufen zu lassen. Na klar, man muss nicht erstmal Chrome oder Firefox öffnen lassen. Dieser tut es nun um Hintergrund der API. Er browst die Seiten ab und übergibt den Source an BeautifulSoup. Das aufgearbeiteten Daten gehen dann per Bottle, Gunicorn und NGINX raus. Das ganze ist natürlich ein fieser Hack... aber es funktioniert. Zum vereinfachten Rollout benutze ich Docker und Ansible. Mir geht es garnicht darum das alle auf einer API rumklicken. Da es sich um einen Hack handelt soll es so einfach wie möglich sein seine eigene API aufzusetzen. Aber schauen wir uns erstmal an welche Sachen funktionieren. Dafür benutze ich mein geliebtes requests...

In [2]:
import requests
import json
from pprint import pprint

Es ist wichtig das wir JSON an die API senden. Dazu ist mir aufgefallen das es für requests nötig ist den richtigen Header zu setzen.

In [3]:
headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
In [4]:
payload = {'name': 'american splendor'}

r = requests.post('http://localhost:5000/api/search', data=json.dumps(payload), headers=headers)

pprint(r.json())
{u'results': [{u'available': False,
               u'name': u'American Splendor [DVD] / Paul Giamatti, Hope Davis. Dir. by Robert Pulcini ... ',
               u'type': u'DVD-Video/-Audio',
               u'year': u'2005-01-01'}]}

Es wird eine Liste mit Items zurückgegeben. In diesem Fall habe ich nach American Splendor gesucht. Es gab nur einen Treffer. Bis jetzt sind folgende Informationen abrufbar: Verfügbarkeit, Name, der Typ (in diesem Fall eine DVD) und das Erscheinungsjahr. Zukünftig will ich noch mehr Sachen parsen und vor allem auch bei der Anfrage mehr Optionen anbieten. Zum Beispiel das man gleich sagen kann "Gib mir nur DVD's". Aber ich wollte erstmal die für mich relevanten Informationen auslesen.

Ein weitere Basis-Funktion war das auslesen der noch ausgeliehenden Teile. Dies macht man durch die Angabe von Kartennummer und Passwort.

In [5]:
payload = {'cardnumber': 'B123456', 'password': '123456'}

r = requests.post('http://localhost:5000/api/rented', data=json.dumps(payload), headers=headers)

pprint(r.json())
{u'results': [{u'from_date': u'2015-05-20',
               u'name': u'Duden-Abiturhilfen Kunstgeschichte : 11. bis 13. Klasse. - 2.. 19. und 20. JahrhundertKunst00237375',
               u'notes': u'',
               u'till_date': u'2015-06-17'},
              {u'from_date': u'2015-05-20',
               u'name': u'Duden-Abiturhilfen Kunstgeschichte : (11./13. Schuljahr). - 1.. Von den Anf\xe4ngen bis zum 18. JahrhundertKunst00237374',
               u'notes': u'',
               u'till_date': u'2015-06-17'},
              {u'from_date': u'2015-05-20',
               u'name': u'Kammerlohr - Kunst im \xdcberblick : Stile - K\xfcnstler - Werke / Walter Etschmann ; Robert Hahne ; Volker Tlusty7 Ets00302818',
               u'notes': u'',
               u'till_date': u'2015-06-17'},
              {u'from_date': u'2015-05-20',
               u'name': u'Kunst : 7. Klasse bis Abitur / [Autoren Klaus Borkmann ...]7 Dud01083769',
               u'notes': u'',
               u'till_date': u'2015-06-17'},
              {u'from_date': u'2015-05-20',
               u'name': u'Duden, Abiwissen Kunstgeschichte : von der Antike bis zum 21. Jahrhundert ; [Abitur] / [Autorin: Eva Bambach-Horst]Kunst01208988',
               u'notes': u'',
               u'till_date': u'2015-06-17'}]}

Bis jetzt parse ich hier auch nur die Basics. Neben Ausleih und Fälligskeitsdatum gibt es auch den Punkt "notes". Da würden zum Beispiel fällige Gebühren stehen.

Als erste Beispielanwendung wollte ich den ical-Support einbauen. Ich habe mich dazu entschieden Kartennummer und Passwort, durch einem dem Server bekannten KEY, in ein Token zu kodieren. Die komplette URL kann man per URL anfordern in dem man ihm Kartennummer und Passwort sendet.

In [7]:
payload = {'cardnumber': 'B123456', 'password': '123456'}

r = requests.post('http://localhost:5000/api/ical-url', data=json.dumps(payload), headers=headers)

pprint(r.json())
{u'url': u'http://localhost:5000/ical/rented.ics?token=W3siY2FyZG51bWJlciI6IkIxOTM1NTEiLCJwYXNzd29yZCI6IjE3LjA3LjE5ODcifSx7fV0.3OgATqehpVhdnyU4dlW8LPMiR6g'}

na mal schauen was mir die url zurück liefert.

In [9]:
%%bash
http GET http://localhost:5000/ical/rented.ics?token=W3siY2FyZG51bWJlciI6IkIxOTM1NTEiLCJwYXNzd29yZCI6IjE3LjA3LjE5ODcifSx7fV0.3OgATqehpVhdnyU4dlW8LPMiR6g
BEGIN:VCALENDAR
PRODID:ics.py - http://git.io/lLljaA
VERSION:2.0
BEGIN:VEVENT
DTSTAMP:20150612T090919Z
DTSTART:20150617T000000Z
SUMMARY:Bibliothekrueckgabe: 5 Teile
DESCRIPTION:- Duden-Abiturhilfen Kunstgeschichte : 11. bis 13. Klasse. - 2.. 19. und 20. JahrhundertKunst00237375\n- Duden-Abiturhilfen Kunstgeschichte : (11./13. Schuljahr). - 1.. Von den Anfängen bis zum 18. JahrhundertKunst00237374\n- Kammerlohr - Kunst im Überblick : Stile - Künstler - Werke / Walter Etschmann \; Robert Hahne \; Volker Tlusty7 Ets00302818\n- Kunst : 7. Klasse bis Abitur / [Autoren Klaus Borkmann ...]7 Dud01083769\n- Duden\, Abiwissen Kunstgeschichte : von der Antike bis zum 21. Jahrhundert \; [Abitur] / [Autorin: Eva Bambach-Horst]Kunst01208988
UID:d500f008-1ecb-4f55-85e5-38fd13ff31d1@d500.org
END:VEVENT
END:VCALENDAR

Und diese URL funktioniert im Thunderbird wie auch im Google Calendar. Ein paar Basics habe ich also eingebaut. Das Projekt ist auch noch lange nicht am Ende. Mir ging es erstmal darum sowas zu hacken um zu sehen was es für Möglichkeiten gibt wenn man mal API Zugriff auf die Daten hätte. Das es sich hierbau um einen Dirty-Hack handelt ist mir klar. Wir brauchen die API in nativ mit Soße und scharf.