zum Blättern · Enter öffnet · Esc schließt

Kontakt aufnehmen.

Projekte

Produktdaten-Pipeline: Web, PDF und Sprachmodell statt Handarbeit.

Festanstellung30. Juli 2026

Bereich
B2B-Handel
Umsetzung
Aug.–Okt. 2025
Kompetenz
Data Engineering · Document AI · Prozessautomatisierung
Stack
Python · Playwright · OpenAI · BeautifulSoup · PyMuPDF · MySQL · Docker · SAP Business ByDesign · Magento · Git

Ergebnis

  • Eine Pipeline aus 15 Schritten: vom Artikel im ERP bis zur fertigen Importdatei für den Shop
  • Vier austauschbare Extraktionswege statt eines Parsers — konfiguriert, nicht einprogrammiert
  • Zielgenaue Scraping-Regeln als Daten in der Datenbank statt als Code je Hersteller
  • In lauffähigem Zustand übergeben — über den späteren Einsatz liegen mir keine Zahlen vor

In drei Sätzen: Ein Fachhändler führt rund 238.000 Artikel in seinem ERP — mit Preisen, Einheiten und Status, aber ohne die technischen Merkmale, Beschreibungen und Bilder, die ein Online-Shop braucht. Dagegen ist eine Pipeline aus fünfzehn Schritten entstanden, die zu einer Artikelnummer selbst Quellen sucht, Webseiten und Datenblätter auswertet, Merkmale gegen das Kategorienschema prüft und eine fertige Importdatei erzeugt. Was daran interessant ist, sind nicht die grünen Haken, sondern die Stellen, an denen der Ansatz nicht funktioniert hat — und ein Projektende ohne Rückmeldung.

Hinweis zu diesem Text: Diese Pipeline ist im Rahmen meiner Festanstellung entstanden, nicht als Freelance-Auftrag. Auftraggeber, Hersteller- und Produktnamen sind anonymisiert, Artikelnummern in den Beispielen erfunden; Abläufe, Kennzahlen und technische Entscheidungen sind unverändert. Anders als in meinen anderen Fallstudien gibt es hier keine Wirkungszahlen — warum, steht weiter unten im Abschnitt zur Übergabe.

Die Ausgangslage

Im ERP steht, was ein Artikel kostet und wie er verwaltet wird: Einkaufspreis, Verkaufspreis, Mengeneinheit, Lagerstatus, Rabattgruppe, Kategorie. Was dort nicht steht, ist alles, wonach ein Kunde im Shop sucht: Temperaturbereich, Fassungsvermögen, Werkstoff, Drehzahl, Anschlussmaße, Schutzart — dazu ein brauchbarer Beschreibungstext und mindestens ein ordentliches Produktfoto.

Diese Angaben existieren durchaus, nur eben außerhalb: auf der Herstellerseite, im PDF-Datenblatt, im Katalog eines Mitbewerbers. Sie dort einzusammeln ist Fleißarbeit — pro Artikel etwa zehn bis zwanzig Minuten für jemanden, der das Sortiment kennt. Bei einem Katalog dieser Größe ist das keine Aufgabe mehr, sondern eine Personalfrage.

Der Auftrag lautete also: Kann eine Maschine das vorbereiten? Nicht fehlerfrei — vorbereiten. So, dass ein Mensch prüft und freigibt, statt zu recherchieren und zu tippen.

Was daraus wurde

Eine Pipeline aus fünfzehn Schritten. Jeder Schritt hat genau eine Aufgabe, schreibt sein Ergebnis in die Datenbank und legt sein Rohmaterial als Datei ab. Das klingt nach Overhead und ist der Grund, warum das System überhaupt diagnostizierbar war: Wenn am Ende ein Merkmal falsch ist, kann man rückwärts gehen und sehen, in welchem Schritt es falsch wurde.

Von der Artikelnummer zur Importdatei — 15 Schritte in fünf Gruppen 1 Eingang Artikelnummer annehmen Stammdaten aus dem ERP Kategorie auflösen Merkmalsschema laden → „Wonach suchen wir?" 2 Beschaffung Quellensuche + Wertung Laden: requests Fallback: Playwright HTML säubern → „Wo steht es?" 3 Auswertung Merkmale aus HTML Merkmale aus PDF Bilder einsammeln Sprachmodell, 4 Wege → „Was steht da?" 4 Prüfung Einheiten normalisieren Quellen abgleichen Qualitätsregeln Schwelle 0,7 Konfidenz → „Stimmt das?" 5 Ausgabe Beschreibung DE erzeugen (200–2.200 Zeichen) Importdatei für den Shop · Einzelbericht je Artikel → „Was bekommt der Mensch zum Prüfen?" Durchgehend mitgeschrieben Zustand je Schritt in MySQL · Rohmaterial als Datei Jeder Lauf rekonstruierbar: welcher Wert kam woher 12 Schema-Migrationen · ~20.000 Zeilen Python Bewusst gebaut wie eine Fertigungsstraße: ein Schritt scheitert, nicht der ganze Lauf. Ein Artikel braucht im Schnitt 5,4 Minuten — fast alles davon Wartezeit auf fremde Server und das Sprachmodell.
Der Ablauf. Die Trennung in Schritte war weniger Architektur-Ästhetik als Notwehr: ohne sie ist bei einem falschen Merkmal nicht feststellbar, wo es entstanden ist.

Die Infrastruktur lief lokal in Docker: MySQL für Zustand und Stammdaten, daneben ein S3-kompatibler Objektspeicher für alle Zwischenartefakte. Der Objektspeicher flog später wieder raus. Für einen Lauf auf einem einzelnen Rechner war er eine Abhängigkeit ohne Gegenwert — dieselben Dateien liegen heute schlicht im Dateisystem, mit derselben Ordnerstruktur je Auftrag. Das ist einer der wenigen Umbauten, die ich rückblickend zu spät gemacht habe.

Quellensuche: eine Trefferliste ist noch keine Quelle

Der erste ernsthafte Schritt ist nicht das Auslesen, sondern die Frage, welche Seite man überhaupt ausliest. Eine Suchmaschine liefert zu einer Herstellernummer bereitwillig zehn Treffer: die Herstellerseite, drei Händler, zwei Preisvergleiche, ein Marktplatz, ein Forum. Wer davon die erste nimmt, bekommt zuverlässig falsche Daten — Marktplätze schreiben voneinander ab, und sie schreiben falsch ab.

Also bekommt jeder Kandidat eine Punktzahl, bevor überhaupt etwas geladen wird.

Punktwertung eines Quellenkandidaten — vor dem Laden, nicht danach Seite des Herstellers +100 Herstellernummer in URL/Titel +50 Herstellername genannt +30 Wörter der ERP-Beschreibung bis +20 URL sieht nach Produktseite aus +10 Marktplatz, Preisvergleich, Wiki −50 (Abzug) Gewichte stehen in einer YAML-Datei, nicht im Code — eine Fehlwertung ist eine Zeile, kein Release.
Die Wertung entscheidet, welche drei von zehn Treffern überhaupt geladen werden. Jeder Punkt landet mit Begründung im Protokoll.

Dazu kommt eine kleine Liste bekannter Adressen: Für Artikel, deren Hersteller einen Konfigurator statt eines Katalogs betreibt, ist die richtige URL nicht auffindbar, sondern bekannt. 88 solcher Zuordnungen stehen in einer CSV-Datei — unelegant, aber ehrlich: nicht jedes Problem ist ein Suchproblem.

Geladen wird zuerst mit einer einfachen HTTP-Anfrage. Erst wenn die scheitert — Zeitüberschreitung, 403, 500, Zertifikatsfehler —, übernimmt ein echter Browser über Playwright. Diese Reihenfolge ist eine reine Kostenfrage: Die HTTP-Anfrage dauert Millisekunden, der Browser Sekunden. Vorher gab es eine gepflegte Liste „diese Seiten brauchen einen Browser“. Die zu pflegen war Arbeit, die niemand machen wollte, und sie war ständig veraltet.

Vier Wege, dieselbe Frage zu stellen

Der Kern der Sache: Aus einer Produktseite sollen genau die Merkmale werden, die die Kategorie dieses Artikels vorsieht — nicht irgendwelche. Für Zentrifugen sind das andere Felder als für Sicherheitsschränke. Das Schema kommt aus der Datenbank, das Sprachmodell bekommt es als Auftrag mit: „Fülle diese Liste, benutze exakt diese Merkmals-IDs, was du nicht findest, bleibt leer.“

Dass ein einziger Weg dorthin nicht reicht, war die zentrale Lernerfahrung. Deshalb sind es vier, umschaltbar per Konfiguration:

  • Aufgeräumtes HTML ans Modell. Skripte, Stile und Navigation raus, Tabellen bleiben unangetastet. Der Standardfall, der billigste Weg.
  • Nur die Adresse ans Modell. Das Modell holt sich die Seite selbst. Schnell aufgesetzt, aber nicht nachvollziehbar — man sieht nicht, was es gelesen hat.
  • Erst rendern, dann säubern, dann ans Modell. Für Seiten, die ihre Datenblatt-Tabelle per JavaScript nachladen. Teuer, aber oft der einzige Weg.
  • PDF ans Modell. Datenblätter, in denen die Tabelle steht, die auf der Webseite fehlt.

Die Regel beim Säubern war bewusst grob: nur wegwerfen, was offensichtlich Beiwerk ist, und Tabellen niemals anfassen. Frühere Versuche, „intelligent“ den Produktbereich herauszuschneiden, haben verlässlich das Falsche weggeschnitten. Ein Sprachmodell verkraftet zu viel Kontext deutlich besser als zu wenig.

Eine spätere Ergänzung hat mehr gebracht als jede Prompt-Feinarbeit: typische Werte je Merkmal. Für 1.032 Merkmale wurden aus dem bestehenden Shop-Bestand die tatsächlich vorkommenden Ausprägungen gezogen und in den Auftrag geschrieben.

vorher:   - Werkstoff (Typ: Auswahl) [ID: pattr123]
nachher:  - Werkstoff (Typ: Auswahl) [ID: pattr123]
            übliche Werte: Edelstahl; Glas; PTFE; Polypropylen (PP)

Der Unterschied ist der zwischen „schreib etwas Passendes“ und „ordne dich in diese Systematik ein“. Freitext-Antworten wie rostfreier Stahl, Edelstahl 1.4301 und V2A meinen dasselbe und sind als Filter im Shop drei verschiedene Werte — und damit unbrauchbar.

Lieber leer als plausibel

Für Produktname und Beschreibungstext hat allgemeine Extraktion nie gut genug funktioniert. Bei Merkmalen ist ein Fehler sichtbar: „Temperaturbereich: blau“ fällt auf. Bei einem Fließtext ist ein Fehler unsichtbar — er liest sich gut und beschreibt das Nachbarmodell.

Die Antwort darauf war ein zweiter, viel engerer Mechanismus: zielgenaue Regeln je Domain, gespeichert als Daten. Eine Tabelle hält je Domain und je Feld eine JSON-Konfiguration — welcher Selektor, welches Ankerelement, wie weit gelesen wird, welche Tags erhalten bleiben, welche Attribute wegfallen, ob die Sprache geprüft und ins Deutsche übersetzt wird.

Damit ist eine neue Quelle ein INSERT, kein Release. Und es widerspricht frontal dem Prinzip, mit dem das Projekt gestartet ist — „keine herstellerspezifische Logik, das Modell wird das schon regeln“. Das Prinzip war für Merkmale richtig und für Fließtexte falsch. Beides gleichzeitig stehen zu lassen, war die brauchbarste Entscheidung im ganzen Projekt.

Der zweite Teil davon war unpopulär, hat sich aber gehalten: Greift eine Regel nicht, bleibt das Feld leer. Kein Rückfall auf die allgemeine Erzeugung, kein „irgendwas ist besser als nichts“. Ein leeres Feld sieht der Mensch in der Prüfliste sofort. Ein falsch gefülltes Feld sieht er nie — es wandert in den Shop und wird dort zur Kundenfrage.

Für die erzeugten Beschreibungen gelten harte Grenzen: 200 bis 2.200 Zeichen, zwei bis sechs Absätze, erlaubt sind p, b, ul, li, i, br — und sonst nichts. Keine Überschriften, keine Links, keine Bilder, keine Tabellen. Der Grund ist banal und teuer: Fremdes HTML in einem Shop-Feld zerlegt das Layout, und zwar erst im Live-System.

Bilder und Datenblätter

Bilder sind der Teil, bei dem die Automatik am peinlichsten scheitert, wenn man sie nicht bremst. Ein Bot lädt begeistert das Herstellerlogo, das Zertifikats-Icon, den Newsletter-Banner und ein Platzhalterbild herunter.

Die Filter sind deshalb mehrstufig und komplett langweilig: Mindestmaße 240 × 240 Pixel, höchstens fünf Bilder je Artikel, Negativ-Stichwörter in Alternativtext und Umgebungstext (logo, icon, banner, Zertifikat, Platzhalter, …), strengere Negativ-Muster im URL-Pfad (/logo, /banner, …) und eine Ausschlussliste für konkrete Dateinamen — jene drei Kampagnengrafiken, die auf jeder zweiten Seite eines Händlers liegen.

Was durchkommt, wird auf ein einheitliches Format gebracht: 1.000 × 1.000 Pixel, 300 dpi, PNG, Dateiname aus Artikelnummer plus Kurz-Hash des Inhalts. Der Hash ist der eigentliche Nutzen: Dasselbe Bild von drei Händlern ist dreimal dieselbe Datei — und wird einmal behalten.

Datenblätter laufen denselben Weg: höchstens drei PDFs je Artikel, Text und Tabellen extrahieren, Tabellen mit weniger als zwei Zeilen oder zwei Spalten verwerfen, den Rest ans Modell. Bei Geräten ist das PDF oft die einzige Quelle, in der Anschlussmaße und Leistungsaufnahme sauber stehen.

Was ein Lauf gekostet hat

Ein Artikel braucht im Mittel 5,4 Minuten. Ein Stapel von 13 Artikeln lief in rund einer Stunde und zehn Minuten durch. Diese Zahl ist die ehrlichste Kennzahl des Projekts — und sie sieht so aus:

Wo die 5,4 Minuten je Artikel hingehen Merkmale aus HTML 48,7 % · 158 s Einheiten 32,4 % · 105 s Bilder 15,9 % · 52 s Rest 3 % Schnell genug, um nicht aufzufallen: ERP-Abruf 0,0 s · Quellensuche 0,0 s · Schema laden 0,1 s · Laden 0,1 s · PDF 1,6 s Die unbequeme Lesart Ein Drittel der Rechenzeit ging in die Normalisierung von Maßeinheiten — nicht in Beschaffung, nicht in Auswertung. Optimiert habe ich zuerst die Extraktion. Ob die Einheiten den Nutzer je gestört haben, weiß ich bis heute nicht.
Gemessen, nicht geschätzt. Die Messung war da — was sie für die Priorität bedeutet, hätte nur der Anwender beantworten können.

Für größere Mengen kam ein Stapelbetrieb dazu: Artikelliste aus Excel rein, ein gemeinsames Protokoll, ein Statusstand als JSON-Datei zum Mitlesen, ein Sicherungsschalter, der nach drei Fehlern in Folge pausiert, und ein Zeitlimit von 20 Minuten je Artikel — großzügig, weil ein gerenderter Browserlauf auf einer trägen Herstellerseite tatsächlich so lange braucht. Zusätzlich lassen sich Auswertungen über die Batch-Schnittstelle des Modellanbieters bündeln: deutlich günstiger, dafür ohne Zusage, wann das Ergebnis vorliegt.

Was ich nicht gebaut habe, gehört zur ehrlichen Bilanz: Die Verarbeitung läuft mit einem Arbeiter, streng nacheinander. Parallelität stand als Konfigurationsschalter da — und blieb auf eins. Für einen Stapel von 140 Artikeln über Nacht war das in Ordnung. Für einen Katalog dieser Größe ist es das offensichtlich nicht.

Wo der Ansatz an Grenzen stieß

Kategorien, die es im Werkzeug noch nicht gab. Trifft die Pipeline auf einen Kategorie-Code, den ihre Stammdaten nicht kennen, legt sie einen Platzhalter an — Name „Unknown Category“, kein Elternknoten, null Merkmale. Der Auftrag läuft danach formal sauber durch und liefert praktisch nichts. Fachlich ist das ein Abbruchgrund, technisch war es ein Erfolg mit Status completed. Genau die Art von Fehler, die ohne einen Anwender, der „hier fehlt doch alles“ sagt, monatelang unentdeckt bleibt.

Selektoren altern. Jede zielgenaue Regel ist eine Wette darauf, dass ein fremdes Unternehmen sein Seitenlayout nicht ändert. Die Wette verliert man irgendwann immer. Was fehlt, ist die Kehrseite: eine Statistik, wie oft jede Regel greift und wie oft sie ins Leere läuft. Ohne die merkt man den Bruch erst an leeren Feldern in der Importdatei.

Kosten skalieren linear. Jeder Artikel kostet Modellaufrufe, jede Übersetzung noch einen. Bei zwanzig Artikeln ist das ein Rundungsfehler. Bei zwanzigtausend ist es eine Position im Budget, über die vorher jemand entscheiden muss.

Und die Standardregel, die nicht funktioniert hat. Das Projekt startete mit dem Vorsatz „keine Sonderlocken je Hersteller — das Modell versteht auch unbekannte Strukturen“. Für Merkmale in Tabellen stimmt das erstaunlich gut. Für Produktnamen und Beschreibungen stimmt es nicht, und zwar unrettbar: Diese Felder sind nicht falsch oder richtig, sie sind passend oder unpassend, und das kann ein Modell ohne Kenntnis der Hausnorm nicht wissen.

Die Übergabe — und was gefehlt hat

Hier endet die Fallstudie anders als üblich.

Ich habe die Pipeline bis in einen lauffähigen Zustand entwickelt und dann an die Fachseite übergeben. Über die gesamte Laufzeit entstanden 317 Einzelberichte und 191 Stapel-Exporte — das ist die Menge, die durchgelaufen ist, nicht die Menge, die im Shop gelandet ist. Wie viele Artikel danach tatsächlich importiert wurden, ob die Beschreibungen die Prüfung überstanden haben und ob das Werkzeug heute noch läuft, weiß ich nicht. Nach der Übergabe kam keine Rückmeldung mehr, und ich habe auch keine eingefordert.

Das ist keine Randnotiz, sondern der Kern dieses Kasus. Man sieht die Folgen im Text weiter oben, an drei Stellen gleichzeitig:

  • Ich habe die Merkmalsextraktion optimiert, weil sie 48 % der Laufzeit kostete. Ob Laufzeit überhaupt das Problem war oder eher die Trefferquote, hat mir nie jemand gesagt.
  • Ich habe an Prompts und Strategien gefeilt, statt eine simple Trefferstatistik je Regel zu bauen — die Statistik hätte ein Anwender nach einer Woche verlangt.
  • Der Fehler mit der leeren Kategorie war fachlich fatal und technisch unsichtbar. Ein Anwender hätte ihn am ersten Tag gemeldet.

Ein Vergleich drängt sich auf: Das Werkzeug für die Stammdatenpflege desselben Katalogs, das ich seit Jahren baue und betreue, ist deshalb brauchbar, weil fast jede Seite darin aus einer Frage entstanden ist, die jemand zum dritten Mal gestellt hat. Diese Pipeline hier ist aus einer Spezifikation entstanden. Sie ist technisch die anspruchsvollere Arbeit — und die schwächere Software.

Was ich daraus mitnehme

Ein kurzer Rückkopplungszyklus ist kein Prozessluxus, sondern ein technischer Bestandteil. Ohne Anwender, der widerspricht, optimiert man das Messbare statt das Wichtige. Die Messung war korrekt; die Priorität, die ich daraus abgeleitet habe, war ungeprüft.

Leer schlägt plausibel. In jeder Datenübernahme ist der teuerste Wert nicht der fehlende, sondern der falsche, der gut aussieht. Ein leeres Feld erzeugt fünf Minuten Arbeit, ein falsches Feld erzeugt eine Reklamation.

Ein universelles Verfahren und Sonderregeln schließen sich nicht aus. Der Streit „generisch oder spezifisch“ ist meist falsch gestellt. Merkmale ließen sich generisch lösen, Fließtexte nur spezifisch. Beide Wege parallel zu fahren war kein Kompromiss, sondern die richtige Antwort.

Sonderregeln gehören in die Datenbank, nicht in den Code. Sobald eine Regel ein Datensatz ist, kann sie jemand anderes ändern als ich — und genau das ist der Punkt bei einem Werkzeug, das übergeben werden soll.

Erfolg mit Status completed ist gefährlich. Ein Lauf, der ohne Merkmale durchgeht, muss scheitern und es sagen. „Technisch fertig“ ist keine Aussage über das Ergebnis.

Was davon für kleine Betriebe übrig bleibt

Ein Katalog mit 238.000 Artikeln klingt nach einer anderen Welt als ein Handwerksbetrieb oder ein Händler mit 800 Positionen. Das Muster darunter ist dasselbe, und es hängt nicht an der Menge:

  • Die Daten, die Ihr Shop braucht, stehen nicht in Ihrer Warenwirtschaft. Sie stehen beim Hersteller, im PDF, in einer alten Preisliste. Jemand trägt sie ab, oder es passiert nicht.
  • Automatisch heißt nicht unbeaufsichtigt. Der brauchbare Zuschnitt ist „Maschine bereitet vor, Mensch gibt frei“ — nicht „Maschine schreibt direkt in den Shop“. Der zweite Zuschnitt spart genau so lange Zeit, bis die erste falsche Angabe beim Kunden ankommt.
  • Ein Pilot mit dreißig Artikeln beantwortet die Frage. Erst danach weiß man, ob die Trefferquote bei 80 % oder bei 30 % liegt — und ob sich das Ganze überhaupt rechnet. Diese dreißig Artikel sind billiger als jede Schätzung.
  • Und der wichtigste Punkt aus diesem Projekt: Wer so etwas beauftragt, sollte alle zwei Wochen zwanzig Minuten Zeit haben, sich Ergebnisse anzusehen. Ohne diese zwanzig Minuten entsteht saubere Software, die am eigentlichen Bedarf vorbeiläuft. Das ist keine Frage des Budgets, sondern die billigste Versicherung im ganzen Projekt.

← Alle Projekte