NerdkramsProjekte
Mit Com­mon Lisp gegen Webmüll

Im Novem­ber 2015 schrieb ich, der kosten­lo­se Web­dienst feed43 sei zum Ent­mül­len von Web­sites mit­tels RSS prin­zi­pi­ell geeig­net, ver­schlucke sich aber gele­gent­lich. Das ist auf Dau­er ganz schön anstren­gend. Außer­dem besteht wie bei allen kosten­lo­sen Web­dien­sten die Gefahr, dass ich eines Tages ohne ihn aus­kom­men muss. Ich habe mir also selbst eine Alter­na­ti­ve ent­wickelt, die auf einem mei­ner Ser­ver läuft und deren kor­rek­te Funk­ti­ons­wei­se ich im Zwei­fels­fall also selbst sicher­stel­len kann.

Die Wahl der Programmiersprache

Ein Binär­pro­gramm, zum Bei­spiel in C++, wäre hier zu unfle­xi­bel, auf Ser­vern bevor­zu­ge ich – schon aus Grün­den der Wart­bar­keit und damit Sicher­heit – rei­ne Script­spra­chen. Wie üblich stand ich also vor der Ent­schei­dung, ob ich mich für Perl, Python oder Com­mon Lisp (absicht­lich ver­ein­fach­te Lisp-Dia­lek­te wie Sche­me und Racket kann ich nicht ernst neh­men) ent­schei­den sollte.

Alle drei Spra­chen bie­ten her­vor­ra­gen­de HTML- und XML-Biblio­the­ken, sie sind für ein schnel­les pro­to­typ­ing auch glei­cher­ma­ßen effi­zi­ent zu nut­zen. An Python aller­dings stö­ren mich nach wie vor die feh­len­den Klam­mern, die ein refac­to­ring wäh­rend des Ent­wick­lungs­pro­zes­ses nen­nens­wert auf­hal­ten wür­den: Ein­rückung ist Syn­tax, Ände­run­gen im Pro­gramm­ab­lauf las­sen sich also nicht ein­fach mit ein paar Klam­mern vor­neh­men. Python mag ein­fach sein, aber es nervt. – In einem ersten Ent­wurf für die­ses Pro­jekt hat­te ich dank der guten sqlite3-Biblio­thek recht schnell ein lauf­fä­hi­ges Python-Script, das zumin­dest mei­ne Daten­bank ver­wal­ten konn­te (dazu unten mehr), fer­tig­ge­stellt, aber auch unge­zähl­te graue Haa­re mehr am Kör­per; auch, weil Python recht geschwät­zig ist: „Expli­cit is bet­ter than impli­cit“, was pri­ma ist für Leu­te, die nach Code­zei­len bezahlt wer­den, aber ungut für Leu­te, die gern mög­lichst wenig Zeit ver­schwen­den wür­den. (Es wirkt iro­nisch, dass ich das als Teil einer wort­rei­chen Erklä­rung für eigent­lich Tri­via­les anmer­ke, nicht?)

Das von mir anson­sten sehr geschätz­te Perl hat die­ses Pro­blem nicht, aller­dings kommt es mir in einem ande­ren Punkt in die Que­re: Die Ent­wick­ler­grup­pe hin­ter Mojo­li­cious, der freund­lich­sten Biblio­thek zum Ver­ar­bei­ten von Web­sites, hält offen­bar nicht beson­ders viel von sta­bi­len APIs, erst Anfang März muss­te ein auf Mojo­li­cious basie­ren­des Web­pro­jekt, das ich aus Grün­den im Auge behal­te, plötz­lich nach­bes­sern, was mich ver­un­si­chert hat: Ich möch­te das Werk­zeug, wenn es fer­tig ist, wahr­schein­lich gern noch ein paar Jah­re lang nut­zen, ohne wegen eines System­up­dates funk­tio­nie­ren­de abstra­hier­te Rou­ti­nen umschrei­ben zu müssen.

Übrig bleibt also Com­mon Lisp. Das wird lustig.

Die Wahl der Datenbank

Da die Feeds natür­lich nicht nur in Datei­form vor­lie­gen, son­dern zunächst ein­mal gene­riert und gele­gent­lich aktua­li­siert wer­den sol­len, muss eine Liste der zu gene­rie­ren­den Feeds und (um Dupli­ka­te zu ver­mei­den) der bis­he­ri­gen Ein­trä­ge zumin­dest ein­fach edi­tier­bar vor­lie­gen. Eine auf­ge­bla­se­ne Daten­bank wie MariaDB wäre zwar eine funk­tio­nie­ren­de, aber nicht unbe­dingt die offen­sicht­lich beste Lösung. Ich habe mich für SQLi­te ent­schie­den, das die gesam­te Daten­bank platz­spa­rend in einer ein­zi­gen Datei ablegt und mit data­f­ly auch aus Com­mon Lisp her­aus benutz­bar ist.

Dazu genügt mir fol­gen­des Datenbankschema:

CREATE TABLE entries (
  id INTEGER PRIMARY KEY autoincrement,
  feedid INTEGER,
  title text NOT NULL,
  contents BLOB,
  url text NOT NULL,
  TIMESTAMP INTEGER
);
CREATE TABLE feeds (
  id INTEGER PRIMARY KEY autoincrement,
  feedtitle text NOT NULL,
  url text NOT NULL,
  entryselector text NOT NULL,
  titleselector text NOT NULL,
  contentselector text NOT NULL,
  lastsuccess INTEGER
);

Den „letz­ten Erfolg“ eines Feeds möch­te ich zumin­dest spei­chern, um spä­ter leich­ter zu sehen, ob der Cron­job noch ord­nungs­ge­mäß funk­tio­niert. Zur Funk­ti­ons­wei­se kom­me ich aber wei­ter unten noch.

Die Umge­bung

Da die von mir gewähl­ten Kom­po­nen­ten recht por­ta­bel sind, ist die Wahl des Betriebs­sy­stems erst ein­mal nicht so wich­tig. Vor­han­den sein soll­te neben SQLi­te auch eine Ver­si­on von Com­mon Lisp mit instal­lier­tem Quick­lisp. Ich habe mich für SBCL ent­schie­den. Das Script soll­te im Prin­zip auch unter Clo­zu­re CL, das ich am hei­mi­schen Lap­top für SLIME nut­ze, lau­fen, aber dort ist es momen­tan nicht mög­lich, ein Script so kom­for­ta­bel direkt aus der Kom­man­do­zei­le her­aus auf­zu­ru­fen, wie sbcl – script es mir ermöglicht.

Nun kann es losgehen.

Das Script

Um mir das Testen und Kor­ri­gie­ren so ein­fach wie mög­lich zu machen, habe ich den Code ent­ge­gen übli­chen Gepflo­gen­hei­ten nicht in meh­re­re Datei­en auf­ge­teilt. Beim Laden des Scripts wer­den per Quick­lisp die nöti­gen exter­nen Biblio­the­ken gela­den und gege­be­nen­falls instal­liert, sofern noch nicht vor­han­den; die­se Instal­la­ti­on macht sich beim Auf­ruf in einer kur­zen War­te­zeit bemerk­bar, aber das ist noch erträg­lich. Die ver­schie­de­nen Betriebs­mo­di haben jeweils einen eige­nen Code­be­reich, der aus der Haupt­funk­ti­on her­aus auf­ge­ru­fen wird.

Damit ihr den Code nicht umständ­lich hier her­aus­ko­pie­ren müsst und um den Text hier noch eini­ger­ma­ßen über­sicht­lich zu hal­ten, habe ich ein Pro­jekt auf Bit­bucket ange­legt, wo künf­tig die aktu­ell­ste Ver­si­on des Codes zu fin­den ist.

Die Bedie­nung des RSS-Par­sers ist eigent­lich 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 nach­träg­li­che Ändern von Feeds ist nicht vor­ge­se­hen, um die Inte­gri­tät der bereits erstell­ten Feed­da­tei­en nicht zu gefährden.

Funk­ti­ons­wei­se

Da jeder Feed auf Grund­la­ge von CSS-Selek­to­ren erstellt wird, müs­sen die­se zunächst ermit­telt wer­den. Das geht mit den Ent­wick­ler­werk­zeu­gen, die in den mei­sten aktu­el­len Brow­sern zur Ver­fü­gung ste­hen, ziem­lich ein­fach. Ich mache mal ’n Bei­spiel: Neh­men wir an, ich wür­de gern den jeweils aktu­el­len Ent­wick­lungs­stand von KiT­TY abon­nie­ren. Im Web­brow­ser mei­ner Wahl besu­che ich also die Sei­te mit den aktu­el­len Ände­run­gen und drücke auf F12.

Ich benö­ti­ge hier jeweils den CSS-Selek­tor für einen Nach­rich­ten­ein­trag, sei­nen Titel und sei­nen Inhalt. Nach­rich­ten­ein­trä­ge sind auf der KiT­TY-Web­site, wie der Brow­ser mir zeigt, in DIV-Ele­men­ten mit der Klas­se news grup­piert und ihre Über­schrif­ten in H1-Tags. Geson­der­te Berei­che für die Inhal­te gibt es nicht, ich kann den letz­ten Para­me­ter nun also ent­we­der weg­las­sen (dann wer­den nur die Über­schrif­ten im Feed gespei­chert) oder einen lee­ren String über­ge­ben (dann wird der gan­ze Nach­rich­ten­ein­trag als Inhalt gespei­chert). Ich ent­schei­de mich für Letz­te­res, denn die Liste der Ände­run­gen möch­te ich gern kom­plett – also nicht nur die Ver­si­ons­num­mern – abonnieren.

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

Wird rssparser.lisp par­se aus­ge­führt oder gleich als Cron­job instal­liert, so wer­den regel­mä­ßig alle Feeds als RSS-XML-Datei­en im Ord­ner feeds/​ abge­legt bezie­hungs­wei­se aktua­li­siert; even­tu­ell nicht mehr erreich­ba­re Web­sites wer­den nach einer Sta­tus­mel­dung auto­ma­tisch aus der Liste gelöscht. Die erzeug­ten Feeds las­sen sich dann im RSS-Leser eurer Wahl abon­nie­ren. Ein Auf­ruf des Feeds im Fire­fox zeigt: Es hat geklappt.

rssparser Screenshot

Ihr könnt den Ord­ner für die XML-Datei­en über die Kon­stan­te +feed-fol­der+ auch ändern, beach­tet aber, dass Schreib­rech­te für den Ziel­ord­ner vor­han­den sein müssen.

Gibt es kein GUI?

Gra­fi­sche Ober­flä­chen sind bei Pro­gram­men, die auf Web­ser­vern lau­fen und nicht stän­dig bemut­tert wer­den müs­sen, im Prin­zip nur unnüt­zer Über­hang, ein Web­in­ter­face für Leu­te, die Angst vor der Kon­so­le haben, wäre mit­tels Caveman2 oder Lucer­ne aber ohne all­zu gro­ßen Auf­wand nach­rüst­bar. Die SQLi­te-Daten­bank lässt sich aller­dings auch mit Werk­zeu­gen wie dem SQLi­te Data­ba­se Brow­ser pflegen.

Fazit

Das war jetzt ein biss­chen mehr Arbeit als ange­nom­men, aber das Ergeb­nis ist ein auf mei­nen Zweck zuge­schnit­te­nes Pro­gramm, das ohne über­flüs­si­ge „Extras“ und vor allem ohne Wer­bung aus­kommt und das nicht zu funk­tio­nie­ren auf­hört, so lan­ge ich es brau­che. Theo­re­tisch lie­ße sich das Script auch in eine por­ta­ble Binär­da­tei kom­pi­lie­ren, das wür­de die Wart­bar­keit aber wie­der­um verringern.

Ergän­zun­gen, Lob­prei­sun­gen und Gewinn­be­tei­li­gun­gen sind willkommen.

Senfecke:

Comments are closed.

https://tuxproject.de/blog/wp-content/plugins/wp-monalisa/icons/smiley_emoticons_smilenew.gif  https://tuxproject.de/blog/wp-content/plugins/wp-monalisa/icons/smiley_emoticons_biggrin2.gif  https://tuxproject.de/blog/wp-content/plugins/wp-monalisa/icons/smiley_emoticons_sadnew.gif  https://tuxproject.de/blog/wp-content/plugins/wp-monalisa/icons/smiley_emoticons_eek.gif  https://tuxproject.de/blog/wp-content/plugins/wp-monalisa/icons/smiley_emoticons_shocked.gif  https://tuxproject.de/blog/wp-content/plugins/wp-monalisa/icons/smiley_emoticons_confusednew.gif  https://tuxproject.de/blog/wp-content/plugins/wp-monalisa/icons/smiley_emoticons_coolnew.gif  https://tuxproject.de/blog/wp-content/plugins/wp-monalisa/icons/smiley_emoticons_lol.gif  https://tuxproject.de/blog/wp-content/plugins/wp-monalisa/icons/smiley_emoticons_madnew.gif  https://tuxproject.de/blog/wp-content/plugins/wp-monalisa/icons/smiley_emoticons_aufsmaul.gif  https://tuxproject.de/blog/wp-content/plugins/wp-monalisa/icons/smiley_emoticons_seb_zunge.gif  https://tuxproject.de/blog/wp-content/plugins/wp-monalisa/icons/smiley_emoticons_blushnew.gif  https://tuxproject.de/blog/wp-content/plugins/wp-monalisa/icons/smiley_emoticons_frown.gif  https://tuxproject.de/blog/wp-content/plugins/wp-monalisa/icons/smiley_emoticons_twistedevil1.gif  https://tuxproject.de/blog/wp-content/plugins/wp-monalisa/icons/smiley_emoticons_twistedevil2.gif  https://tuxproject.de/blog/wp-content/plugins/wp-monalisa/icons/icon_mad.gif  https://tuxproject.de/blog/wp-content/plugins/wp-monalisa/icons/smiley_emoticons_rolleyesnew.gif  https://tuxproject.de/blog/wp-content/plugins/wp-monalisa/icons/smiley_emoticons_wink2.gif  https://tuxproject.de/blog/wp-content/plugins/wp-monalisa/icons/smiley_emoticons_idea2.gif  https://tuxproject.de/blog/wp-content/plugins/wp-monalisa/icons/smiley_emoticons_arrow2.gif 
mehr...
 

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

Datenschutzhinweis: Deine IP-Adresse wird nicht gespeichert. Details findest du hier.