Hackety hack hack

Atypisches Nutzerverhalten mit Rat und Tat.

Die Cola oder das Cola? Mediawiki APIs hacken

Die Web-API von Mediawiki (und damit von den meisten Wikimedia-Projekten) ist zwar nicht gerade versteckt, aber meiner Meinung nach viel zu unbekannt. In loser Folge werde ich ein paar Hacks mit APIs für verschiedene Projekte vorstellen, auch um dazu anzuregen, selbst mit der Infrastruktur zu spielen.

Die Web-API von Mediawiki

Mediawiki hat eine RESTful Web-API. Leider ist API im Mediawiki-Umfeld ein mehrfach belegtes Wort, so dass es auch eine API gibt, um eigene Erweiterungen in PHP zu schreiben; diese API meine ich in dem Fall nicht, sondern die Schnittstelle, mit der sich Daten und Inhalte per HTTP aus Mediawiki holen lassen.

Zunächst gibt es da einen Endpunkt. Bei den Wikimedia-Projekten liegt dieser unter /w/api.php, bei dem unten benutzten Wiktionary also unter https://de.wiktionary.org/w/api.php. Praktischerweise ist der Endpunkt für die API-Calls auch gleich eine URL zur Dokumentation. Einfach den Endpunkt im Browser eingeben es erscheint eine Seite, auf der die möglichen Calls verzeichnet sind und auch mit Beispielen erklärt werden.

Ein vollständiger API-Call könnte etwa so aussehen:

https://de.wiktionary.org/w/api.php?format=json&action=query&prop=revisions&rvprop=content&titles=Cola

Schauen wir uns den API-Call mal genauer an und zerlegen ihn in seine Einzelteile.

Das Format für die Serialisierung

format=json

Hiermit sagen wir dem Wikimedia-Web-Service, dass wir den Rückgabewert gern als JSON formatiert bekommen wollen. Alternativ können wir zur Anzeige im Webbrowser das auch schön formatiert mit dem Aufruf format=jsonfm bekommen – das ist manchmal für das Debugging übersichtlicher und hilfreicher. Die API unterstützt noch andere Ausgabeformate wie WDDX, XML, YAML und eine native PHP-Serialisierung, aber möglicherweise werde diese in Zukunft nicht mehr bedient. JSON wird stark präferiert und sollte wenn immer möglich benutzt werden.

Die Action

action=query

Die “Action”, also sozusagen das Verb, ist der eigentliche Befehl, der aufgerufen wird. Es gibt etliche solche Actions, die in der Mediawiki-API dokumentiert sind, Extensions können noch weitere Actions hinzufügen, die Actions zu Wikidata etwa beginnen mit “wb” für Wikibase. Hier benutzen wir queryzur Abfrage von Daten.

Die Action query ist selbst wieder umfangreich dokumentiert, wir schauen uns hier ein paar beispielhafte Parameter an.

Action-spezifische Parameter

titles=Cola

Wir fragen hier die API nach den Daten einer Wiki-Seite mit dem Titel “Cola”. In der Abfrage werden Titel URL-kodiert, wenn der Titel also ein Leerzeichen enthält, wird dieses zu %20. Wenn mehrere Seiten gleichzeitig abgefragt werden können (und sollten) sie kombiniert werden, um nicht durch mehrere Abfragen hintereinander die Netzwerklast unnötig zu erhöhen: titles=PageA|PageB|PageC

prop=revisions

Mit diesem Parameter können wir eine Revision der Seite angeben. Wenn wir keine weiteren Informationen zur Revision angeben bekommen wir die aktuelle Version der Seite.

rvprop=content

Mit diesem Parameter sagen wir der API, dass wir den Inhalt der Seite haben wollen. Hätten wir stattdessen zum Beispiel rvprop=content|user angegen, bekämen wir den Inhalt un den Benutzer, der die letzte Änderung vorgenommen hat.

Es gibt über die API und mögliche Parameter und Actions noch viel mehr zu sagen, aber als Beispiel reicht das erstmal. Die Dokumentation zur API ist noch viel umfangreicher. Ein guter Einstiegspunkt ist die Dokumentation auf Mediawiki, die ich hier teilweise übersetzt habe.

Eine gute Einführung in die API gibt es auch als Video:

Artikel von Substantiven mit migrationsschatten.py abfragen

Als erstes Beispiel für eine Spielerei mit der API geht es um Zweifelsfragen der deutschen Sprache. Eine sehr gute Freundin von mir, beneidenswert bilingual aufgewachsen mit Deutsch und Englisch als Muttersprache, erzählte mir vor einiger Zeit davon, dass sie dann doch manchmal überrascht sei, welches grammatikalische Geschlecht und damit welchen Artikel ein Wort im Deutschen hat: Der Knoblauch? Ernsthaft?”. Wie gesagt, es handelt sich bei ihr um eine Muttersprachlerin, aber diesen Einwand wischte sie schnell beiseite mit “Ach, das ist eben mein Migrationsschatten”.

Das Problem ist aber häufig genug, um sich hinzusetzen und ein kleines Programm zu schreiben, das Wiktionary nach dem korrekten Artikel befragt. Heisst es “der Laptop” oder “das Laptop”? Und was war mit der Werbung einer österreichischen Energydrink-Marke, die von “das Cola” sprach? Ist das korrekt?

Das Progrämmchen ist ein bisschen älter, aber ich habe es seitdem ein bisschen poliert und umgeschrieben. Die Freundin mit der Frage nach den Artikeln benutzt es nach eigener Aussage regelmäßig. Im Original ist es als gist auf Github verfügbar, aber gehen wir das Programm doch mal der Reihe nach durch:

 
 
 
 
 
from __future__ import print_function
import json
import re
import sys
import urllib2

Es beginnt mit ein paar Imports. Das Programm ist für Python 2.7 geschrieben, eine Portierung nach Python3 lasse ich mal als Aufgabe für die Leserin oder den Leser (urllib2 und json bräuchten dafür Liebe, die über die automatische Portierung mit 2to3 hinausgeht).

 
 
 
if len(sys.argv) < 2:
    sys.exit('Abfrage mit: %s Substantiv' % sys.argv[0])
substantiv = sys.argv[1]

Wenn kein Substantiv angegeben ist, ist die ganze Sache auch schon vorbei, ansonsten nehmen wir an, dass der erste Kommandozeilenparameter das gesuchte Substantiv ist.

 
 
 
 
 
 
 
 
 
 
 
 
try:
    r = urllib2.urlopen(
        'http://de.wiktionary.org/w/api.php?' +
        'format=json' +
        '&action=query' +
        '&prop=revisions' +
        '&rvprop=content' +
        '&titles=' +
        substantiv
        ).read()
except urllib2.URLError, e:
    sys.exit('Problem beim Zugriff auf Wiktionary.')

Wir basteln uns die API-Anfrage zusammen. Oben ist erklärt, wieso die Abfrage so aussieht, wie sie aussieht. Wenn die Abfrage über das Internet nicht klappen sollte, brechen wir hier ab.

 
 
 
 
 
 
 
content = json.loads(r)
page = content['query']['pages'][content['query']['pages'].keys()[0]]
if 'revisions' not in page.keys():
        sys.exit(
            'Substantiv nicht im deutschsprachigen Wiktionary verzeichnet.'
        )
wikitext = (page['revisions'][0]['*'])

Der Seiteninhalt wird als JSON eingelesen und in eine Python-Datenstruktur hineingeschlürft. Wenn es keine Seite mit dem Titel gibt, der gewünscht ist, brechen wir ab. Python kennt schließlich keine Autovivication bei Collections, also passen wir auf wie ein Luchs, dass wir nicht auf eine Struktur zugreifen, für die es keinen Key gibt.

 
 
 
 
 
 
 
 
match = re.search(
    '===\s?,\s?' +
    '' +
    '(,\s?(,\s?)?)*' +
    '\s?===',
    wikitext)
if match is None:
    sys.exit('Kein Substantiv!')

Es folgt eine epische regular expression, mit der der Wikitext geparst wird. Leider wird im Wiktionary nicht ganz durchgängig dasselbe Pattern für die Angabe des grammatischen Geschlechts genutzt. Aber irgendwie sowas wie “m” oder “mf” oder “m,f,n” steht hinter einer Template-Variable, die ein Substantiv bezeichnet. Wenn nicht mal dieses Pattern gefunden wird, ist der Suchstring wohl kein Substantiv und wir verabschieden uns aus dem Programm.

 
 
genera = filter(lambda x: re.match('^[mfn]$', x),
                filter(lambda x: x is not None, match.groups()))

Wir filtern aus den capturing brackets alles heraus, was nicht “m”, “f”, oder “n” ist. Ich finde da eine Lösung mit zwei Filtern (und anonymen Lambda-Funktionen) am elegantesten, aber das ist natürlich Geschmackssache.

 
 
 
 
m = ["der " + substantiv for genus in genera if genus == "m"]
f = ["die " + substantiv for genus in genera if genus == "f"]
n = ["das " + substantiv for genus in genera if genus == "n"]
artikel = m + f + n

Mit ein paar list comprehensions bauen wir uns drei Listen mit den gefilterten Ergebnissen, die wir dann zu einer flachen Ergebnisliste vereinigen (artikel). Natürlich kann ein Substantiv mehr als ein grammtisches Geschlecht und damit mehr als einen gültigen Artikel haben.

Warum basteln wir uns eine Liste? Nun, darum:

 
map(print, artikel)

Lieber ein gedingenes map auf eine Liste als eine for-Schleife. Da wir print als Funktion benutzen können (wir haben extra from __future__ import print_function benutzt), können wir diese Funktion auf alle möglichen Ergebnis-Strings anwenden.

Was gibt dieses Programm jetzt aus? Nun, schauen wir mal:

 
 
python migrationsschatten.py Knoblauch
der Knoblauch

oder auch:

 
 
 
python migrationsschatten.py Ketchup
der Ketchup
das Ketchup

oder gar:

 
 
 
 
python migrationsschatten.py Joghurt
der Joghurt
die Joghurt
das Joghurt

Und was ist mit der Frage aus der Überschrift?

 
 
 
python migrationsschatten.py Cola
die Cola
das Cola

So seltsam das für bundesdeutsche Sprecherinnen und Sprecher klingen mag, das Cola ist okay, sagt Wiktionary. Es handelt sich um einen Austriazismus.

Leiwand!

Wie bereits gesagt, das vollständige Programm findet sich hier.