NerdkramsProjekte
Mit Common Lisp gegen Webmüll

Im November 2015 schrieb ich, der kostenlose Webdienst feed43 sei zum Entmüllen von Websites mittels RSS prinzipiell geeignet, verschlucke sich aber gelegentlich. Das ist auf Dauer ganz schön anstrengend. Außerdem besteht wie bei allen kostenlosen Webdiensten die Gefahr, dass ich eines Tages ohne ihn auskommen muss. Ich habe mir also selbst eine Alternative entwickelt, die auf einem meiner Server läuft und deren korrekte Funktionsweise ich im Zweifelsfall also selbst sicherstellen kann.

Die Wahl der Programmiersprache

Ein Binärprogramm, zum Beispiel in C++, wäre hier zu unflexibel, auf Servern bevorzuge ich – schon aus Gründen der Wartbarkeit und damit Sicherheit – reine Scriptsprachen. Wie üblich stand ich also vor der Entscheidung, ob ich mich für Perl, Python oder Common Lisp (absichtlich vereinfachte Lisp-Dialekte wie Scheme und Racket kann ich nicht ernst nehmen) entscheiden sollte.

Alle drei Sprachen bieten hervorragende HTML- und XML-Bibliotheken, sie sind für ein schnelles prototyping auch gleichermaßen effizient zu nutzen. An Python allerdings stören mich nach wie vor die fehlenden Klammern, die ein refactoring während des Entwicklungsprozesses nennenswert aufhalten würden: Einrückung ist Syntax, Änderungen im Programmablauf lassen sich also nicht einfach mit ein paar Klammern vornehmen. Python mag einfach sein, aber es nervt. – In einem ersten Entwurf für dieses Projekt hatte ich dank der guten sqlite3-Bibliothek recht schnell ein lauffähiges Python-Script, das zumindest meine Datenbank verwalten konnte (dazu unten mehr), fertiggestellt, aber auch ungezählte graue Haare mehr am Körper; auch, weil Python recht geschwätzig ist: „Explicit is better than implicit“, was prima ist für Leute, die nach Codezeilen bezahlt werden, aber ungut für Leute, die gern möglichst wenig Zeit verschwenden würden. (Es wirkt ironisch, dass ich das als Teil einer wortreichen Erklärung für eigentlich Triviales anmerke, nicht?)

Das von mir ansonsten sehr geschätzte Perl hat dieses Problem nicht, allerdings kommt es mir in einem anderen Punkt in die Quere: Die Entwicklergruppe hinter Mojolicious, der freundlichsten Bibliothek zum Verarbeiten von Websites, hält offenbar nicht besonders viel von stabilen APIs, erst Anfang März musste ein auf Mojolicious basierendes Webprojekt, das ich aus Gründen im Auge behalte, plötzlich nachbessern, was mich verunsichert hat: Ich möchte das Werkzeug, wenn es fertig ist, wahrscheinlich gern noch ein paar Jahre lang nutzen, ohne wegen eines Systemupdates funktionierende abstrahierte Routinen umschreiben zu müssen.

Übrig bleibt also Common Lisp. Das wird lustig.

Die Wahl der Datenbank

Da die Feeds natürlich nicht nur in Dateiform vorliegen, sondern zunächst einmal generiert und gelegentlich aktualisiert werden sollen, muss eine Liste der zu generierenden Feeds und (um Duplikate zu vermeiden) der bisherigen Einträge zumindest einfach editierbar vorliegen. Eine aufgeblasene Datenbank wie MariaDB wäre zwar eine funktionierende, aber nicht unbedingt die offensichtlich beste Lösung. Ich habe mich für SQLite entschieden, das die gesamte Datenbank platzsparend in einer einzigen Datei ablegt und mit datafly auch aus Common Lisp heraus benutzbar ist.

Dazu genügt mir folgendes Datenbankschema:

Den „letzten Erfolg“ eines Feeds möchte ich zumindest speichern, um später leichter zu sehen, ob der Cronjob noch ordnungsgemäß funktioniert. Zur Funktionsweise komme ich aber weiter unten noch.

Die Umgebung

Da die von mir gewählten Komponenten recht portabel sind, ist die Wahl des Betriebssystems erst einmal nicht so wichtig. Vorhanden sein sollte neben SQLite auch eine Version von Common Lisp mit installiertem Quicklisp. Ich habe mich für SBCL entschieden. Das Script sollte im Prinzip auch unter Clozure CL, das ich am heimischen Laptop für SLIME nutze, laufen, aber dort ist es momentan nicht möglich, ein Script so komfortabel direkt aus der Kommandozeile heraus aufzurufen, wie sbcl --script es mir ermöglicht.

Nun kann es losgehen.

Das Script

Um mir das Testen und Korrigieren so einfach wie möglich zu machen, habe ich den Code entgegen üblichen Gepflogenheiten nicht in mehrere Dateien aufgeteilt. Beim Laden des Scripts werden per Quicklisp die nötigen externen Bibliotheken geladen und gegebenenfalls installiert, sofern noch nicht vorhanden; diese Installation macht sich beim Aufruf in einer kurzen Wartezeit bemerkbar, aber das ist noch erträglich. Die verschiedenen Betriebsmodi haben jeweils einen eigenen Codebereich, der aus der Hauptfunktion heraus aufgerufen wird.

Damit ihr den Code nicht umständlich hier herauskopieren müsst und um den Text hier noch einigermaßen übersichtlich zu halten, habe ich ein Projekt auf Bitbucket angelegt, wo künftig die aktuellste Version des Codes zu finden ist.

Die Bedienung des RSS-Parsers ist eigentlich selbsterklärend:

% ./rssparser.lisp
Syntax:
* rssparser.lisp add <Title> <URL> <EntrySelector> <TitleSelector> [<ContentSelector>]
* rssparser.lisp delete <ID>
* rssparser.lisp list

If you're a bot:
* rssparser.lisp parse

Das nachträgliche Ändern von Feeds ist nicht vorgesehen, um die Integrität der bereits erstellten Feeddateien nicht zu gefährden.

Funktionsweise

Da jeder Feed auf Grundlage von CSS-Selektoren erstellt wird, müssen diese zunächst ermittelt werden. Das geht mit den Entwicklerwerkzeugen, die in den meisten aktuellen Browsern zur Verfügung stehen, ziemlich einfach. Ich mache mal ’n Beispiel: Nehmen wir an, ich würde gern den jeweils aktuellen Entwicklungsstand von KiTTY abonnieren. Im Webbrowser meiner Wahl besuche ich also die Seite mit den aktuellen Änderungen und drücke auf F12.

Ich benötige hier jeweils den CSS-Selektor für einen Nachrichteneintrag, seinen Titel und seinen Inhalt. Nachrichteneinträge sind auf der KiTTY-Website, wie der Browser mir zeigt, in DIV-Elementen mit der Klasse news gruppiert und ihre Überschriften in H1-Tags. Gesonderte Bereiche für die Inhalte gibt es nicht, ich kann den letzten Parameter nun also entweder weglassen (dann werden nur die Überschriften im Feed gespeichert) oder einen leeren String übergeben (dann wird der ganze Nachrichteneintrag als Inhalt gespeichert). Ich entscheide mich für Letzteres, denn die Liste der Änderungen möchte ich gern komplett – also nicht nur die Versionsnummern – abonnieren.

% ./rssparser.lisp add "KiTTY" "http://www.9bis.net/kitty/?action=news&zone=en" ".news" "h1" ""
Success!

Wird rssparser.lisp parse ausgeführt oder gleich als Cronjob installiert, so werden regelmäßig alle Feeds als RSS-XML-Dateien im Ordner feeds/ abgelegt beziehungsweise aktualisiert; eventuell nicht mehr erreichbare Websites werden nach einer Statusmeldung automatisch aus der Liste gelöscht. Die erzeugten Feeds lassen sich dann im RSS-Leser eurer Wahl abonnieren. Ein Aufruf des Feeds im Firefox zeigt: Es hat geklappt.

rssparser Screenshot

Ihr könnt den Ordner für die XML-Dateien über die Konstante +feed-folder+ auch ändern, beachtet aber, dass Schreibrechte für den Zielordner vorhanden sein müssen.

Gibt es kein GUI?

Grafische Oberflächen sind bei Programmen, die auf Webservern laufen und nicht ständig bemuttert werden müssen, im Prinzip nur unnützer Überhang, ein Webinterface für Leute, die Angst vor der Konsole haben, wäre mittels Caveman2 oder Lucerne aber ohne allzu großen Aufwand nachrüstbar. Die SQLite-Datenbank lässt sich allerdings auch mit Werkzeugen wie dem SQLite Database Browser pflegen.

Fazit

Das war jetzt ein bisschen mehr Arbeit als angenommen, aber das Ergebnis ist ein auf meinen Zweck zugeschnittenes Programm, das ohne überflüssige „Extras“ und vor allem ohne Werbung auskommt und das nicht zu funktionieren aufhört, so lange ich es brauche. Theoretisch ließe sich das Script auch in eine portable Binärdatei kompilieren, das würde die Wartbarkeit aber wiederum verringern.

Ergänzungen, Lobpreisungen und Gewinnbeteiligungen sind willkommen.

Senfecke

Bisher gibt es 2 Senfe:

  1. PINGBACK: Hirnfick 2.0 » Mit Common Lisp gegen Webmüll (fortg.: jetzt mit noch mehr Web)
  2. PINGBACK: Hirnfick 2.0 » Schöner lesezeichnen mit Buku

Senf dazugeben:

:) 
:D 
:( 
:o 
8O 
:? 
8) 
:lol: 
:x 
:aufsmaul: 
:P 
:ups: 
:cry: 
:evil: 
:twisted: 
mehr...
 

Erlaubte Tags:
<strong> <em> <pre class="" title="" data-url=""> <code class="" title="" data-url=""> <a href="" title=""> <img src="" title="" alt=""> <blockquote> <q> <b> <i> <del> <span style="" class="" title="" data-url=""> <strike>

Datenschutzhinweis: Ihre IP-Adresse wird nicht gespeichert. Details finden Sie hier.

Du willst deinen Senf dazugeben, dir ist aber der Senf ausgegangen? Dann nutz den SENFOMATEN! Per einfachem Klick kannst du fertigen Senf in das Kommentarfeld schmieren, nur dazugeben musst du ihn noch selbst.