Wenn Web-Technologien verschmelzen: NLP im Browser dank WebAssembly

Schema für die Tehnologien

Heute möchte ich Euch eine echte NLP-Suche im Browser vorstellen:

DEMO

Aber eines nach dem anderen:

Vor ein paar Jahren wäre die Idee, Python-NLP direkt im Browser laufen zu lassen, noch ein typischer „nice try“-Moment gewesen. Heute ist das ein ernstzunehmender Ansatz nicht als Gimmick, sondern als Strategie: Du baust Web-UIs mit HTML/CSS/JavaScript, nutzt aber für die Rechenarbeit genau die Tools, die in ihrem Ökosystem seit Jahren am stärksten sind. Der Katalysator dafür heißt WebAssembly.

WebAssembly (oft nur „Wasm“) ist im Grunde eine zweite Ausführungsform neben JavaScript, die Browser nativ unterstützen. Während JavaScript als Sprache sehr flexibel ist, ist es für gewisse Aufgaben schlicht nicht ideal besonders dort, wo viel gerechnet, geparst oder analysiert wird. Wasm schließt diese Lücke: Es ist ein kompaktes Binärformat, das im Browser sehr schnell ausgeführt werden kann. Der Clou dabei ist weniger „noch schneller als JS“, sondern: Viele Sprachen können nach Wasm kompilieren. Das heißt: Jahrzehnte an Bibliotheken und Know-how aus anderen Communities werden plötzlich „browserfähig“, ohne dass jemand alles neu in JavaScript erfinden muss.

Damit sind wir mitten im Thema Natural Language Processing. Denn „Suche“ ist in der Praxis schnell frustrierend, wenn sie nur Teilstrings vergleicht. Gerade im Deutschen willst du, dass ein Nutzer „laufen“ eingibt und trotzdem „lief“ oder „gelaufen“ Treffer sind. Und du willst idealerweise auch Wortfamilien wie „Wahnsinn“ und „wahnsinnig“ zusammenbringen, weil das für Menschen offensichtlich zusammengehört für eine naive Suche aber nicht. Sobald man das ernst nimmt, landet man fast automatisch bei zwei Bausteinen: Lemmatisierung (Grundform) und Stemming (Wortstamm). Und an dieser Stelle ist die Realität ziemlich eindeutig: Viele der etablierten und praxiserprobten NLP-Bausteine, gerade auch für Deutsch, sind im Python-Ökosystem über Jahre gereift.

WebAssembly als Brücke statt als Ersatz

Wasm ist nicht „das neue JavaScript“. Im Gegenteil: JavaScript bleibt im Browser die Schaltzentrale für alles, was mit UI, DOM und Web-APIs zu tun hat. Wasm ergänzt das Bild, indem es einen performanten Ausführungsraum liefert, in dem andere Runtimes oder Libraries laufen können und über eine Brücke mit JS zusammenarbeiten. Genau dieses Zusammenspiel ist der Kern der „Technologie-Verschmelzung“: Du nutzt das Web als Plattform, aber du bist nicht mehr auf das JS-Ökosystem als einziges Werkzeugset beschränkt.

Ein wichtiger Aspekt, der dabei oft untergeht: Wasm läuft in der gleichen Sicherheitswelt wie der Browser. Es ist sandboxed, es muss sich an die Regeln des Web halten, und es kann nicht einfach beliebig auf Betriebssystem-Ressourcen zugreifen. Das ist gut aber es erklärt auch, warum man bestimmte Dinge (z. B. Dateisystem, Threads, bestimmte Systemmodule) bewusst gestalten muss, wenn man von „klassischen“ Desktop-Runtimes kommt.

Pyodide: Python im Browser – ohne Server, ohne Backend

An der Stelle wird Pyodide interessant. Pyodide bringt CPython als WebAssembly in den Browser. Praktisch heißt das: Du lädst eine Pyodide-Runtime (z. B. von einem CDN oder self-hosted), initialisierst sie in JavaScript und kannst dann Python-Code im Browser ausführen. Für NLP ist das extrem attraktiv, weil du plötzlich Zugriff auf Python-Bibliotheken bekommst, ohne dass du serverseitig rechnen musst oder alles neu implementieren musst.

In unserem konkreten Beispiel nutzen wir genau das als „Proof of Value“: Wir bauen eine Textsuche, die nicht bei exakten Teilstrings endet, sondern sprachlich robuster ist. Dazu kombinieren wir zwei Python-Bausteine: einen Lemmatizer (bei uns simplemma) und einen Stemmer (Snowball über snowballstemmer). Das Zusammenspiel ist der Trick: Lemmatisierung fängt Flexion ab, Stemming fängt Wortfamilien ab. Dadurch wird aus „string contains“ eine Suche, die deutlich näher an dem ist, was Nutzer erwarten.

Wie die Suche funktioniert

Die Mechanik dahinter lässt sich gut in einem Bild erklären: Der Text wird in Tokens zerlegt, und jedes Token bekommt eine Art „Such-Fingerprint“. Dieser Fingerprint besteht aus mindestens zwei Teilen: dem Lemma (Grundform) und dem Stem (Wortstamm). Das gleiche passiert mit der Suchanfrage. Wenn dann beim Durchlaufen des Textes die Fingerprints übereinstimmen – oder sich zumindest schneiden – ist es ein Treffer.

Der praktische Effekt: Sucht jemand nach „Land“, kann „Ländern“ gefunden werden. Sucht jemand nach „Wahnsinn“, kann „wahnsinnig“ gefunden werden, weil der Wortstamm bzw. die Normalisierung die Brücke baut. Das ist genau die Art „Intelligenz“, die Nutzer im Kopf haben, wenn sie eine Suchbox bedienen ohne dass du gleich eine große semantische Suchmaschine bauen musst.

Typische Fallstricke

  • Wenn man so etwas das erste Mal baut, funktionieren die ersten Schritte meist überraschend schnell. Die Probleme kommen häufig erst dann, wenn man das Ganze „so richtig“ betreiben will. Der wichtigste Stolperstein ist dabei weniger die Logik, sondern die Runtime-Realität im Browser.
  • Ein Klassiker: „Warum lädt er die Wheels bei jedem Seitenaufruf neu?“ selbst wenn du Cache-Header setzt. Die Antwort ist zweischichtig. Ja, du kannst per HTTP-Caching erreichen, dass der Browser die .whl Dateien nicht jedes Mal erneut aus dem Netz ziehen muss. Aber selbst wenn die Dateien aus dem Cache kommen, bleibt ein anderes Problem: Pyodide nutzt standardmäßig ein flüchtiges In-Memory-Dateisystem. Nach einem Reload ist das wieder leer. Wenn du also bei jedem Start die Wheels ins Pyodide-FS schreibst und entpackst, machst du diese Arbeit bei jedem Besuch wieder ganz unabhängig davon, ob der Download gecached ist.
  • Genau hier ist der Schritt von „Demo“ zu „produktiv“: Du brauchst Persistenz. Das erreichst du typischerweise, indem du ein persistentes Dateisystem auf IndexedDB-Basis mountest (IDBFS) und einmalig installierst. Danach reicht bei Folgeaufrufen ein Marker („ist installiert“) plus sys.path und Imports – und die Seite fühlt sich plötzlich wie eine normale Web-App an.
  • Ein weiterer Fallstrick ist die Art, wie man Python in JavaScript einbettet. Am Anfang landet Python-Code oft in Template-Strings (runPythonAsync()). Das ist bequem, aber fragil: Python ist empfindlich bei Einrückungen, JavaScript bei Escaping, und Debugging ist manchmal wie ein Blick in zwei Welten gleichzeitig (JS-Stacktrace + Python-Traceback). In echten Projekten lohnt es sich fast immer, den Python-Code in eigene Dateien auszulagern und zu laden allein schon, weil das Versionsmanagement und die Lesbarkeit deutlich besser werden.
  • Und dann gibt es noch die „kleinen“ Browser-Realitäten: Manche Standard-Library-Module sind in Pyodide nicht automatisch drin, weil sie aus Größen- oder Build-Gründen „optional“ sind. Wenn eine Bibliothek so ein Modul nutzt (bei uns war das lzma), musst du es gezielt nachladen sonst steht man plötzlich vor ModuleNotFoundError und fragt sich, warum „Python ohne Module“ existiert.


Als erster einen Kommentar schreiben.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert