← Blog

Original Poster, JSOUP und gen. AI

Ein kleiner Web-Crawler, 1701 Poster — und was ChatGPT dabei geleistet hat

Grand Prix Poster

Bei jedem Grand Prix seit 1950 gibt es ein "offizielles" Plakat. 1991 bis 1995 haben — in wechselnder Besetzung — Axel, Gero, Martin, Vojko, Elmar, Manne, Achim und ich zu Christi Himmelfahrt einige Tage in Ligurien (meist Alassio) verbracht und das Rennen/Qualifying in Monaco besucht. Das Rennen war früher immer zu Christi Himmelfahrt. Das Auto haben wir in Ventimiglia abgestellt und sind dann mit dem Zug nach Monaco — was für eine Kulisse, was für ein Leben! Mit den unbeschwerten Tagen Ende Mai fing der Sommer an. "I do declare the summer season as opened" lautete die Formel. Als Memorabilia habe ich jedes Jahr eine Postkarte, ein T-Shirt oder das offizielle Programm mit dem Poster drauf gekauft. Manchmal habe ich alle drei Dinge mitgebracht. All diese "Dinge" (und andere) sorgen jetzt dafür, dass das Haus "voll" ist mit (für alle anderen) völlig wertlosen Gegenständen. Es sind aber nicht die Gegenstände die einen Wert haben und an denen man hängt. Es sind die Gefühle und Erinnerungen, die mit ihnen verbunden sind.

Die Rennen selbst sind eigentlich Nebensache gewesen — sportlich ist der GP von Monaco meist nicht sehr interessant. In Erinnerung geblieben sind mir die Rennen aus den Jahren 1992 und 1994. 1992 wegen dem elektrisierenden Finish zwischen Senna und Mansell, und 1994 war der GP Monaco das erste Rennen nach dem tragischen Imola-Wochenende. Ich habe es dem Sieger nicht gegönnt.

Monaco Poster 1991
Monaco Poster 1992
Monaco Poster 1993
Monaco Poster 1994
Paintshop Datei

In den frühen 2000er Jahren habe ich einige historische Monaco-Poster in diversen Varianten ausgedruckt und eingerahmt. Als Jäger und Sammler habe ich sogar die alten Paintshop-Dateien noch gefunden — eine davon ist oben in der Reihe ganz rechts.

Auf der F1 WebSite wollte ich auf den Ergebnisseiten der einzelnen Rennen auch das Original-Poster anzeigen. Es gibt einige Quellen im Netz mit allen Original-Postern in unterschiedlicher Qualität. Die jetzt alle (bei mehr als 1000 GP) separat anklicken und abspeichern — soviel Lebenszeit wollte ich dafür nicht aufwenden.

JSOUP — Parsen von HTML Dateien

In HTML werden Bilddateien durch das Tag <img src="Bilddateiname.jpg"> eingebunden. Wenn man die Bilddateien einer HTML-Seite herunterladen lassen möchte, muss man aus einer *.html-Seite die src-Teile der img-Tags in eine Liste auslesen und die Elemente der Liste (Bilddateien) dann als Binärdatei mit HTTP (und vollständigem Pfad) herunterladen.

Für solche Aufgaben ist die JSOUP Library entwickelt worden. Es ist ein HTML Parser und Extractor, und damit fürs Web Crawling, Web Scraping — wie immer man es nennen möchte — gemacht. Eigentlich geht es um das Wiederverwerten oder weniger elegant formuliert — das blanke Kopieren. Vor allem aus Wikipedia. Die JSOUP Library ist mächtig — allzu mächtig, um in 15 Minuten zu verstehen, wie es genau funktioniert und wie man es nutzen muss.

Schnell ersichtlich ist, dass man mit wenigen Zeilen ein HTML-Dokument parsen und die einzelnen Token (Elemente) in einem Objekttyp Document repräsentieren kann:

File input = new File(htmlFilePath);
Document doc = Jsoup.parse(input, "UTF-8");

Auf die einzelnen Elemente kann dann zugegriffen werden, und auch hier können Listen oder Collections von HTML-Tags des Dokumentes ausgewählt werden. Hier werden alle <img>-Tags mit dem src-Attribut in einer Collection imgTags ausgewählt:

Elements imgTags = doc.select("img[src]");

Für die Elemente dieser Liste kann man jetzt das Attribut src (die Bilddatei als relative URL) auswählen und in einer zweiten Liste die absoluten URLs der Bilddateien hineinschreiben:

for (Element imgTag : imgTags) {
    String src = imgTag.attr("src");
    imageSrcList.add("https://www.eventartworks.de/" + src);
}

Diese Liste muss man nur noch durchlaufen und die Dateien (als Binärdatei) anhand der URLs herunterladen. JAVA hat dafür alles im Gepäck:

for (String imageURL : imageSrcList) {
    System.out.println(src);
    outputPath = WorkDir + "/" + getlastToken(src);
    try {
        URL url = new URL(imageUrl);
        InputStream in = url.openStream();
        FileOutputStream out = new FileOutputStream(outputPath);

        byte[] buffer = new byte[4096];
        int bytesRead;
        while ((bytesRead = in.read(buffer)) != -1) {
            out.write(buffer, 0, bytesRead);
        }
        in.close();
        out.close();
        System.out.println("Bild wurde erfolgreich heruntergeladen und in " + outputPath + " gespeichert.");
    }
    catch (IOException e) { e.printStackTrace(); }
}

Der kleine Crawler/Bot, den ich geschrieben habe, hat von einer Site mit 1701 Postern aller Weltmeisterschaftsläufe und Non-WC-Rennen alle Dateien aus diversen HTML-Seiten rausgesucht, heruntergeladen und in separaten Directories für jedes Jahr gespeichert. Der Code für die Ergebnisseiten war dann schnell angepasst. In ein paar Stunden war alles fertig — die Konvertierung von *.webp nach *.jpg hat da noch am längsten gedauert.

Generative AI

Und was hat das mit AI zu tun? Nun — für den Crawler habe ich ChatGPT genutzt. Der Code ist zu ca. 80% von ChatGPT generiert worden, vor allem die JSOUP-Teile.

Das ist nicht mein erstes Experiment mit ChatGPT gewesen, wobei die ersten Ergebnisse noch etwas "schräg" waren — der Code war eigentlich nicht lauffähig. Er wirkte etwas eklektisch (oder heißt das "eklektizistisch"?) — aus unterschiedlichen Quellen zusammenkopiert. Variablen und Datenobjekttypen passten in unterschiedlichen Codeteilen nicht zueinander. Im Laufe der Zeit habe ich besser verstanden (oder gelernt), wie man die AI "fragen" muss. Wenn man die Aufgabenstellungen abgegrenzt und relativ klein hält, ist das Ergebnis echt gut — man spart sich eine Menge Zeit. Man muss die einzelnen Codeteile (Methoden und Klassen) dann zusammenbauen, aber das ist kein Vergleich zu einer intensiven Einarbeit z.B. in JSOUP.

Die Codegenerierung bei abgegrenzten, bekannten Aufgabenstellungen ist ein prima Case für eine generative AI — das leuchtet ein. Man muss sich vergegenwärtigen, dass wir hier im Bereich von Statistik und Wahrscheinlichkeiten sind — bei bekannten, häufig vorkommenden Fragestellungen reicht das oft schon aus.

Wird die generative AI das Erlernen von Programmiersprachen und das Handwerk des Programmierens überflüssig machen? Nein, das wird sie leider nicht. Die Gründe sind schon vielfach dekliniert worden:

  • Bis dato kann die generative AI Dinge replizieren und adaptieren, die andere originär erstellt haben. Neues wird hier nicht entstehen.
  • Die Fähigkeiten: zur Bewertung, ob die AI etwas Sinnvolles und Vollständiges generiert hat
  • den Code lesen und konzeptionell zu verstehen
  • zur Dekomposition der Aufgabe in Teilaufgaben
  • zur Integration der Teillösungen in ein Ganzes

Ich wiederhole mich — man spart sich Zeit. Und ich fürchte, dass die generative AI die Starken noch stärker und die Schwachen noch schwächer macht. Wie das Buch, das Fernsehen und das Internet.


Ach ja, am Ende habe ich die Poster in die Seiten zu den einzelnen Rennen eingebaut — das Ergebnis ist, so finde ich, doch hübsch anzusehen:

Poster in den Rennergebnissen

Und auch auf dem iPad (Bonjour sei Dank!) passt das:

iPad Ansicht 1iPad Ansicht 2
JSP und Servlets ⌂ Blog GIT und GitHub
Kommentare (0)