In meinem letzten Beitrag habe eine Auswertung zum Inhalt der Buchblogs veröffentlicht. Basis für diese Analyse ist ein Programm, dessen Quelltext ich nun öffentlich zur Verfügung stelle. Jeder kann damit die Ergebnisse validieren, auf meine Auswertung aufbauend selbst eine Studie erstellen (beispielsweise im Rahmen einer Studienarbeit). Oder weiterführend ein Tool oder WebService entwickeln. Oder sich einfach ansehen, wie ich vorgegangen bin. Ich freue mich auf jeden Fall sehr über Feedback und Hinweise, besonders auch wenn jemand Fehler oder Unstimmigkeiten findet. Die Implementierung ist doch ganz schön komplex geworden, allerdings sind alle einzelnen Verarbeitungsschritte ganz nette Programmieraufgaben, die zu lösen doch sehr unterhaltsam war und irgendwann konnte ich dann einfach nicht mehr die Finger davon lassen. Dieser Beitrag ist also primär für Softwareentwickler gedacht, die eine gewisse Erfahrung und bereits Kenntnisse in der Programmierung mitbringen. Wer sich dafür interessiert, wie die Auswertung im Detail erstellt wurde, der kann die spezifischen Details überlesen und aus den einzelnen Verarbeitungsschritten gut die Vorgehensweise herauslesen.

Den SourceCode habe ich auf github abgelegt: https://github.com/SSilence/bookscan

Dort könnt ihr ihn auschecken und bei Verbesserungen auch gerne einen Pull Request erstellen. Oder den Code auch einfach nur im Browser durchklicken.

Vorbereitung

Bevor ich auf die Details eingehe, ist es natürlich wichtig den Rahmen abzustecken und genauer einzuführen, was für eine Sprache, welche Frameworks, welche Bibliotheken und Tools ich benutzt habe. Das ist eine ganze Reihe und wer plant, den SourceCode  selbst auszuführen, der muss ein paar Vorbereitungen treffen.

Als Programmiersprache habe ich Kotlin gewählt. Der neue Stern am Softwareentwicklerhimmel hat seit dem offiziellen Support von Google für die Android Entwicklung einen richtigen Aufwind erlebt. Und ich muss sagen, dass kann ich gut verstehen. Die Sprache ist sehr gelungen und man kann, verglichen mit anderen Sprachen, mit wenig aber gut lesbaren Code viel ausdrücken. Der Lernaufwand ist, wenn man von Java kommt, sehr gering und man ist schnell begeistert davon, wie viel boilerplate code man sich gegenüber Java einsparen kann. Aber auch wer den funktionalen Stil von JavaScript gewohnt ist, wird sich mit Kotlin schnell zurecht finden. Ich bin von Kotlin schwer begeistert und es war klar, dass ich für ein privates Projekt darauf setzen wollte.

Um den SourceCode zu strukturieren, habe ich Spring Boot verwendet. Das Programm für diese Analyse ist als Kommandozeilen Tool ausgelegt, aber eigentlich habe ich es ausschließlich aus meiner Entwicklungsumgebung heraus ausgeführt. Das Spring Framework ist sehr genial und mit Spring Boot ist es sehr einfach geworden, eine Anwendung mit wenig Konfigurationsaufwand aufzubauen. Hier bekommt man als Entwickler eine Menge geschenkt. Wirklich viel nutze ich in diesem Projekt allerdings nicht davon. Die dependency injection und die CLI Geschichten. Aber als Rahmen ist das so schön simpel.

Als Entwicklungsumgebung habe ich meine bevorzugte IDE für Java und Kotlin verwendet: IntelliJ IDEA. Die Community Edition kostet nichts und ist sehr genial. Das ist aber nur eine Empfehlung, jeder kann für dieses Programm die Entwicklungsumgebung verwenden, die er gewohnt ist. Als Build Tool und für die Verwaltung der Dependencies verwende ich gradle. Auch das ist ziemlich von der Stange und nichts Spezielles. Um alles laufen zu lassen, benötigt ihr natürlich noch ein installiertes Java SDK, denn Kotlin nutzt die JVM als Laufzeitumgebung.

Als Datenspeicher, aber besonders dann später zum effizienten Durchsuchen der Datenmengen, habe ich ElasticSearch verwendet. Mich etwas genauer mit ElasticSearch zu beschäftigen, war eines meiner Ziele für diese Auswertung. Diese Suchmaschine verwendet einen NoSQL Speicher, basiert auf einen Lucene Suchindex und bietet eine komfortable RESTful HTTP API. Für den Zugriff gibt es fertige Bibliotheken in allen möglichen Sprachen. Es besteht auch die Möglichkeit ein verteilten und auf hohe Last ausgelegten Cluster zu betreiben. Das war für diese Auswertung nicht notwendig, aber von der hohen Performance des optimierten Lucene Indexes habe ich natürlich stark profitiert. Ohne ElasticSearch wäre es an einigen Stellen schon aufwendiger gewesen.

ElasticSearch kann man sich kostenlos herunterladen. Zu Beginn habe ich Spring Data für den Zugriff verwendet, aber der Support für die Scroll API von ElasticSearch war nicht sonderlich gut dokumentiert und schien mir unausgereift. Deshalb verwende ich direkt den ElasticSearch Java Client, der gefühlt auch wesentlich schneller ist. Die ganzen Abhängigkeiten ziehe ich mir allerdings über die fertige gradle dependency des Spring Boot Starter und daher braucht ihr die Version 5.6 von ElasticSearch, wenn ihr diese Auswertung laufen lassen wollt. Bei den Datenmengen, die ich in die ElasticSearch Instanz gepumpt habe, ist es auch sehr empfehlenswert den Heap Speicher zu erhöhen (mit der environment variable ES_JAVA_OPTS -Xms8g -Xmx8g). Ansonsten stürzt ElasticSearch auch einmal schnell mit einem out of memory ab. Überhaupt soll aus Sicht des Betriebs ElasticSearch nicht der große Knaller sein, aber selbst habe ich bisher damit keine Erfahrungen gesammelt.

Zum Abgreifen der Blogbeiträge verwende ich den neuen Spring Web Flux Client, nutze von den neuen Features so gut wie nichts, da ich nur einfache GET Requests mache. Manche Themes basieren sehr stark auf JavaScript und einige Blogs verwenden auch Security Plugins, die einen Zugriff mit einem einfachen HTTP Client verhindern. Für solche Spezialfälle verwende ich einen Google Chrome Browser, den ich über den ChromeDriver aus Kotlin fernsteuere und für den Abruf der Seiten verwende. Den ChromeDriver könnt ihr euch hier herunterladen und müsst ihn in der applications.yml angeben.

Um die Informationen zur Kategorie der einzelnen Bücher abzugreifen, verwende ich die Amazon Product Advertising API. Hier benötigt ihr einen Account und müsst euren API Key ebenfalls in der applications.yml angeben.

Das Programm ist auf Window, MacOS und Linux lauffähig. Hier unterliegt ihr also keinen Einschränkungen. Einige Verarbeitungsschritte sind etwas Speicherintensiver. Hier sollte beim Ausführen mit -Xmx8g mehr Heap Space zur Verfügung gestellt werden. Grundsätzlich war bei jedem Schritt ein Tradeoff zwischen Speicher und Geschwindigkeit notwendig. Meine Kiste hat 16 GB Speicher, die ich auch gut ausgenutzt habe.

Die einzelnen Verarbeitungsschritte

Um die gewünschten Werte zu bekommen, führe ich nacheinander mehrere einzelne Schritte aus. Manche gehen recht schnell, andere brauchen ein paar Tage Laufzeit (beispielsweise das Abgreifen der einzelnen Blogbeiträge oder das Abrufen der Buchkategorien über die Amazon API). Jeder dieser Schritte wird über ein Kommandozeilenargument aufgerufen. Folgende Schritte müssen nacheinander ausgeführt werden:

  • importbooks: Erst einmal muss eine Datenbank mit allen bisher in Deutschland veröffentlichten Büchern in die ElasticSearch Instanz importiert werden. Nach diesen Büchern wird dann später in den Beiträgen gesucht. Ich verwende die Bücherdatenbank der Deutschen Nationalbibliothek.
  • importauthors: Die Titeldatenbank der Deutschen Nationalbibliothek enthält nur Verweise auf die Autoren aber nicht die tatsächlichen Namen der Autoren. Hierfür werden in diesem Schritt alle Autoren aus der Gemeinsame Normdatei (GND) importiert.
  • prescan: Bevor die einzelnen Beiträge der Blogs in die ElasticSearch Instanz geladen werden, wird erst einmal für jeden Blog geprüft, ob die Blogartikel von dem Parser abgegriffen werden können. Wenn es Probleme gibt, dann können die in diesem Schritt behoben werden, bevor der eigentliche Lauf los geht.
  • blogscan: Hier werden alle Blogs komplett durchsucht und alle Blogartikel in ElasticSearch gespeichert.
  • bookscan: In diesem Schritt werden alle Blogartikel nach allen (in den ersten beiden Schritten importierten) Büchern durchsucht. Für jedes gefundene Buch in einem Blogartikel wird ein Eintrag in ElasticSearch erstellt.
  • amazon: Für alle gefundenen Bücher werden aus Amazon die Kategorien dazu geladen.
  • statsprepare: Alle gefundenen Bücher, die Informationen aus Amazon und die Informationen zu den Blogartikeln werden in einer großen „Tabelle“ zusammengefasst. Darauf aufbauend werden dann später die Statistiken erzeugt.
  • stats: Aus den Daten werden die Statistiken erzeugt. Diese werden in JSON Dateien gespeichert, ein HTTP Server gestartet und eine JavaScript basierte UI bereitet die Ergebnisse für die Ausgabe vor und zeigt sie an.

importbook: DNB Bücher importieren

Eine Datenbank mit allen bisher in Deutschland erschienenen Büchern zu finden war gar nicht so einfach. Zuerst habe ich da an das Libri System gedacht, mit dem die ganzen Buchhandlungen arbeiten und das auch die Basis für genialokal.de ist. Die Bücher-Datenbank betreibt, soweit ich das gesehen habe VLB und dort habe ich auch angefragt, ob man da irgendwie einen Zugang bekommt, wurde allerdings ziemlich schnell abgewimmelt.

Dann bin ich auf die Deutsche Nationalbibliothek und auf deren OpenData Service gestoßen. „Die Deutsche Nationalbibliothek hat die Aufgabe, lückenlos alle deutschen und deutschsprachigen Publikationen ab 1913, im Ausland erscheinende Germanica und Übersetzungen deutschsprachiger Werke sowie die zwischen 1933 und 1945 erschienenen Werke deutschsprachiger Emigranten zu sammeln, dauerhaft zu archivieren, bibliografisch zu verzeichnen sowie der Öffentlichkeit zur Verfügung zu stellen.“ (siehe dnb.de).

Die Bücherdatenbank umfasst mehr als 14 Millionen Titel. Ich habe für die Auswertung nur die Einträge mit ISBN übernommen und da sind dann 4 Millionen Bücher übrig geblieben. Wenn man davon ausgeht, dass seit der Einführung der ISBN Nummer in den 70ern jährlich etwa 80.000 Bücher erschienen sind, dann kommt das ungefähr an die 4 Millionen Bücher hin. Sehr grob geschätzt, denn ich weiß nicht, ob vor fünfzig Jahren schon so viele Bücher im Jahr erschienen sind. Aber es sind auch einige wissenschaftliche Publikationen dabei und fremdsprachige Titel, die ganz sicher nicht in Blogs besprochen wurden. Ich hab stichprobenartig einige aktuelle und einige ältere Bücher gesucht und bin in jedem Fall fündig geworden. Auf mich macht die Datenbank einen gut gepflegten und vollständigen Eindruck. Was mir allerdings aufgefallen ist: Es sind nicht alle Ausgaben aufgeführt. Beispielsweise „Der Drachenbeinthron“ von Tad Williams ist im Krüger Verlag, im Fischer Verlag und in der neuesten Auflage im Klett-Cotta Verlag erschienen. Letztere ist allerdings nicht in der DNB Datenbank zu finden. Für die Auswertung bedeutet dass, das hier hinsichtlich Titel, Autor und später auch dem Genre bzw. der Kategorie Aussagen sehr zuverlässig sind, wohingegen Aussagen zum Verlag nur für Treffer über die ISBN eine vollständig korrekte Aussagekraft besitzen.

Diese Titel-Datenbank kann man unter https://data.dnb.de/opendata/ im RDF Format herunterladen. Ein Import wird dann über die Kommandozeile gestartet:

java -jar bookscan-0.0.1-SNAPSHOT.jar --importbooks=C:\path\to\dnb\DNBTitelgesamt.rdf

Entpackt hat die Titeldatenbank über 26 GB. Beim Import kann man also nicht einfach die ganze Datenbank in den Speicher laden und parsen, sondern muss den SAXParser verwenden, der die XML Datei als Stream einliest und on the fly verarbeitet. Dafür ist die Klasse RdfParser zuständig. Der SAXParser verarbeitet Element für Element, d.h. man muss selbst die Hierarchie d.h. die Ineinanderschachtelung der Elemente wieder rekonstruieren. Auch das macht mein RdfParser und führt für jedes geparste rdf:Description Element aus der DNB RDF Datei eine Callback aus. Diese Callback bekommt dann ein RdfElement Objekt übergeben, dass dann ausgewertet und verarbeitet werden kann.

Den gesamten Importvorgang wird von der DnbBookImporter Klasse ausgeführt. Diese liest die Informationen aus dem RdfElement aus und speichert sie in die ElasticSearch Instanz. Allerdings nicht Buch für Buch, denn das würde zu lange dauern. Jeder Zugriff auf ElasticSearch bedeutet einen HTTP Call mit all seinem Overhead. Wenn man also beispielsweise 10000 Bücher einfügt würden das 10000 HTTP Requests sein, was sehr lange dauert. Zu lange für 4 Millionen Bücher. Daher bündle ich immer 100.000 Bücher und speicher sie in einem Schwung ab, wofür die sogenannten bulk requests in ElasticSearch zuständig sind.

importauthors: GND Autorennamen importieren

Die Titeldatenbank enthält sämtliche Bücher und für einzelne Bücher auch die Namen der Autoren. Allerdings nur für einen kleinen Bruchteil. Ansonsten werden Autoren über eine ID referenziert. Die Autoreninformationen könnte man sich dann über die URL (die ID ist auch gleichzeitig eine URL) oder einem Webservice der DNB abfragen. Sie stehen aber auch in der gemeinsamen Normdatei, die ebenfalls über die OpenData Seite heruntergeladen werden kann. Dieser Schritt liest nun alle Autoren aus der GND Datei aus, hält sie im Speicher über eine HashMap und anschließend wird Buch für Buch der volle Autorenname eingetragen. Das ist zwar redundant in der Datenspeicherung, aber an Speicher mangelt es nicht und später soll es dann bei der Suche auch schnell gehen, ohne nochmal für jedes Buch die Autoren zu laden.  Der Import der Autoren wird wie folgt gestartet:

java -jar bookscan-0.0.1-SNAPSHOT.jar --importauthors=C:\path\to\dnb\GND.rdf

Grundsätzlich ist es so, dass man sich bei diesem Herumgewurste von Daten genau Gedanken machen muss, wie man sie im Speicher oder im ElasticSearch Index vorhält. Für diese Autorenzuordnung kann man auch Tage brauchen, wenn man die falschen Datenstrukturen verwendet. Die Collections von Java bzw. Kotlin muss man also gut kennen. Aber das macht auch den Reiz dieser Problemstellung aus.

prescan: Vorprüfung ob die Blogartikel auch geparsed werden können

Nun haben wir eine gut gefüllte Bücherdatenbank. Allerdings fehlt es natürlich noch an den Blogbeiträgen, die es zu durchsuchen gilt. Bevor es aber ans fleißige Grabben der Beiträge geht, habe ich einen Check eingebaut, ob mein Parser und Webseitencrawler auch auf jedem Blog seine Arbeit korrekt machen kann. Viele Blogs haben eine ganz ähnliche Struktur, denn Basis ist zumeist WordPress oder Blogspot. Allerdings werden die unterschiedlichsten Themes verwendet. Die CSS Klassen für Beiträge, Datum und Titel sind zumeist identisch in der Namensgebung, aber es gibt doch immer wieder Ausnahmen. Zudem muss ich die einzelnen Artikelseiten erkennen, den das Parsen von Übersichtsseiten würde sonst zu doppelten Treffern führen.

Der PreScan macht grundsätzlich das identische wie der eigentliche Scan. Nur dass er nachdem er vier Artikel erfolgreich geparsed hat, abbricht und eine Erfolgsmeldung ausgibt. Oder er bricht nach 350 Seiten ab und gibt im Detail aus, was schief gegangen ist. Umgesetzt ist er in der Klasse BlogPreScan. Das Crawlen der Seiten, also das Absuchen aller Unterseiten einer URL ist in der Klasse WebSiteFetcher implementiert. Dort werden sechs Threads gestartet, welche ausgehend von der Startseite nach Links suchen, diese wiederum laden und erneut darin nach internen Links suchen und so weiter machen, bis es keine neuen Links mehr gibt. Für jede Seite, die gefunden wurde, wird dann eine Callbackfunktion aufgerufen. Der WebSiteFetcher ist also generisch implementiert und kann für den PreScan und für den eigentlichen Scan wiederverwendet werden.

java -jar bookscan-0.0.1-SNAPSHOT.jar --prescan=/path/to/your/urls.json

Das Parsen der einzelnen Seite wird vom WebSiteParser übernommen. Dieser bekommt die fertig geladene Seite und verarbeitet das HTML. Es wird nach den Titel, dem Datum und den Artikelinhalt gesucht und ausgelesen. Als HTML Parser verwende ich jsoup, eine hervorragende Bibliothek, die auch mit kaputtem HTML zurecht kommt und mit CSS Selektoren arbeitet. Damit lässt sich auch HTML bereinigen, was ich mit dem Inhalt und Titel des Beitrages mache, denn es ist nur der Text interessant und keine Formatierungen.

Jede einzelne Seite wird vom WebSiteFetcher mit sechs Threads ausgelesen. Also je URL gibt es eine parallele Verarbeitung. Allerdings werden auch mehrere Blogs parallel verarbeitet. Das übernimmt der BlogScanStarter. Er gruppiert nach Anbieter und liest immer gleichzeitig zwei Blogs von blogspot.com, zwei Blogs von wordpress.com und zehn von eigenen Domains aus. Für blogspot oder wordpress bedeutet das jeweils 12 parallele Zugriffe. Damit mein Crawler nicht gesperrt wird oder bei einem Provider nicht negativ auffällt, habe ich das auf diese Werte begrenzt.

Nun gibt es Blogs, die ein stark auf JavaScript basierendes Theme haben. Blogspot hat hier beispielsweise ein ganz schreckliches Theme, das nicht nur hässlich und träge ist, sondern auch noch von seiner Struktur sehr unaufgeräumt ist. Und es gibt Blogs, die Security Plugins haben oder die auf andere Weise verhindern, dass ich mit meinem Spring Client auf diese Seiten zugreife. Ich gebe mich zwar mit dem User Agent als Google Bot aus, aber an einige Stellen funktioniert auch das nicht. Für diesen Fall, habe ich meinen WebSiteFetcher aufgebohrt, mir den ChromeDriver heruntergeladen und mit Hilfe der Selenium Bibliothek starte ich nun meinen lokalen Chrome Browser headless und rufe darin die Seiten auf. Selenium erlaubt dann recht komfortabel den Zugriff auf den Seiteninhalt der so geladenen Webseite. Das funktioniert ganz gut. Der Zugriff ist zwar wesentlich langsamer, und ich habe für diese Seiten auch nur einen Thread, aber nachdem es sich hier eher um Ausnahmen handelt, funktioniert das ganz gut. Theoretisch könnte man auch hier mehrere ChromeDriver Instanzen starten und die Seiten parallel abgrasen, aber den Aufwand habe ich mir dann nicht mehr gemacht.

Einen Parser zu erstellen und so aufzubereiten, dass er für alle Seiten gut funktioniert, war das Aufwendigste an dem ganzen Vorhaben. Gleichzeitig ist es aber eine perfekte Möglichkeit um Kotlin ordentlich auszutesten und hat mir gezeigt, was für eine geniale Sprache das ist. Das geht da alles einfach sehr gut von der Hand und lässt sich mit wenig Code umsetzen. Die Kotlin Standardlibrary bietet, auch für die weiteren Tasks, einige hervorragende Hilfsfunktionen.

blogscan: Das Auslesen der Blogbeiträge

Das Auslesen der Blogbeiträge ist von der Implementierung dann nur wenig verschieden vom prescan. Auch hier füge ich die abgegriffenen Blogbeiträge immer in größeren Mengen in ElasticSearch mit einem BulkRequest ein, da sonst der Overhead zu groß wäre.

java -jar bookscan-0.0.1-SNAPSHOT.jar --blogscan=/path/to/your/urls.json

Mit den oben beschriebenen Einstellungen zur parallelen Verarbeitung braucht das Auslesen ungefähr 24 Stunden. Zudem ist die Konfiguration so, dass ich parallel meinen Rechner noch benutzen konnte. Dreht man zu sehr aus, dann blockiert dass das Notebook, was auch wieder nervig ist.

bookscan: Suche nach den Büchern in den Blogbeiträgen

Nun kommt der wesentliche Schritt: Die Blogbeiträge werden nach Büchern durchsucht. Hier ist es wieder wichtig, ganze Pakete zu verarbeiten und nicht einzelne Elemente. Der Faktor gegenüber der einzelnen Verarbeitung ist beträchtlich. Würde man jedes Buch einzeln aus ElasticSearch laden und einzeln wieder danach suchen, würde das wohl viele Tage brauchen. Ich lade aber immer einen ganzen Schwung Bücher in den Speicher und mache dann ein MultiSearchRequest gegen ElasticSearch. Auf diese Weise dauert die Suche nach den Büchern etwa fünf Stunden.

Dabei erzeuge ich eine Query für die Suche nach der ISBN Nummer und eine Query nach Titel und vollen Autornamen. Interessant in dem Zusammenhang ist die ISBN Nummer. Die geben einige formatiert an, also mit Bindestrichen. Die ISBNs aus der Buchdatenbank liegen aber ohne Formatierung vor. Die Position der Bindestriche variiert aber, da die einzelnen Bestandteile der ISBN Nummer unterschiedliche Längen haben. Glücklicherweise habe ich eine Bibliothek gefunden, die das für mich übernimmt. Die Möglichkeit Java-Code mit Kotlin zu kombinieren erlaubt es das gesamte Java Ökosystem zu nutzen, was ein weiteres Argument für Kotlin ist.

Beim bookscan zeigt sich die krasse Performance von ElasticSearch bzw. dem Lucene Index, der unter der Haube verwendet wird. Die CPU Auslastung hat schnell offenbart, dass hier ordentlich parallel gearbeitet wird und wenn ich mir überlege, dass in weit über 600.000 Artikel knapp 4 Millionen Bücher gesucht werden und das auf meiner Kiste nur wenige Stunden dauert, dann ist das schon beeindruckend.

amazon: Erweiterte Buchinformationen

Was mich natürlich bei diesen Auswertungen immer interessiert, sind die Genres. Die Informationen zur Eingruppierung in der DNB Datenbank ist auf den Bibliotheksbetrieb optimiert und hat mit der Kategorisierung, die der normalsterbliche Leser vornimmt, wenig zu tun. Eine der best gepflegtesten Datenbanken ist die von Amazon. Man mag zu dem Laden stehen wie man will, technisch sind sie einfach vorne dabei. Amazon bietet für ihr Partnerprogramm eine API an, über die man nach Bücher suchen kann und Informationen dazu abrufen kann. Je nachdem wie viele Verkäufe man vermittelt, um so mehr Zugriffe erlaubt Amazon. Nachdem ich das Partnerprogramm nicht wirklich nutze, habe ich einen Request pro Sekunde frei. Das bedeutet, das Abrufen der Kategorien der einzelnen Bücher hat einige Tage in Anspruch genommen. Das braucht keine Rechenleistung, stört also auch nicht, wenn das im Hintergrund läuft.

java -jar bookscan-0.0.1-SNAPSHOT.jar --amazon

Damit ich meine ElasticSearch Instanz immer zurücksetzen kann, habe ich einen Export und einen Import für diese Daten implementiert, der diese Informationen in eine SQLite Datenbank abspeichert:

java -jar bookscan-0.0.1-SNAPSHOT.jar --export=isbn
java -jar bookscan-0.0.1-SNAPSHOT.jar --import=isbn --file=isbn_your_file.db

Zuerst habe ich eine Bibliothek für den Zugriff auf die Amazon API ausprobiert, aber die waren alle so schlecht, dass ich mir selbst einen Client gebaut habe. Was aber auch nicht so schwer ist. Die Anfrage, also die URL Parameter müssen signiert werden. Der Java Code dafür ist in den Amazon Docs zu finden und ich habe ihn einfach in Kotlin umkonvertiert (was zum großen Teil IntelliJ für einen macht).

statsprepare: Informationen für Statistik aufbereiten

Nun sind alle Informationen beisammen. Um sie auswerten zu können, gibt es noch diesen einen Aufruf, der alle Informationen zu den gefunden Büchern und den Artikeln in einer Entität zusammenfasst.

java -jar bookscan-0.0.1-SNAPSHOT.jar --statsprepare

Diese kann ich dann für das Erzeugen der Statistiken einmal in den Speicher laden und von allen Seiten auswerten und durchsuchen. In StatsPrepare wird das gemach, ist ziemlich unspektakulär und braucht etwa 30 Minuten.

stats: Erzeugen der Daten für die Auswertung

In diesem Schritt lade ich alle Buchtreffer, die ich im vorhergehenden Schritt zusammengefasst habe, aus ElasticSearch und erzeuge nacheinander alle Auswertungen.

java -jar bookscan-0.0.1-SNAPSHOT.jar --stats

Die Ergebnisse werden in .json Dateien zusammengefasst abgespeichert. Die Anzeige dieser Daten übernimmt dann ein JavaScript basiertes Frontend. Mit jQuery lade ich die Daten und visualisiere sie mit c3.js und mit selbst erstellten Ausgaben (z.B. der Chart mit den Querbalken habe ich dann lieber selbst gemacht, denn da passt das Ergebnis der Chart Libraries einfach nicht so, wie ich mir das vorstelle).

Wenn die Statistiken fertig erstellt sind (also alle .json Dateien vorliegen), starte ich einen WebServer, der mir diese Statistiken über http://localhost:8888/stats.html verfügbar macht.

Eine echtes Problem war das Bild von der Matrix. Zuerst habe ich das in HTML erstellt, was natürlich zu einer riesigen Seite geführt hat. Anzeigen konnten die Browser diese Matrix auch, aber einen Screenshot der gesamten Seite zu erzeugen, hat kein Browser und kein Addon geschafft. Eine schwache Leistung, dass muss man schon sagen. Ähnlich erging es mir auch mit der langen stats.html. Am Ende habe ich das Bild der Matrix dann direkt in Java mit Graphics2d von AWT erstellt, was erstaunlich einfach ging und obendrein noch sehr schnell ist.

Sentiment Analyzer

Für die inhaltliche Bewertung der Beiträge, also wie positiv bzw. negativ die Artikel sind, habe ich nach einer Java Bibliothek gesucht und bin leider nicht fündig geworden. Vielversprechend war die Stanford CoreNLP Bibliothek, die ich eingebunden hatte und dann feststellen musste, dass nur Englisch als Sprache unterstützt wird. Schließlich habe ich für JavaScript eine vielversprechende Bibliothek gefunden, die ich dann verwendet habe. Wie sich gezeigt hat, ist diese Sentiment Analyse aber eine ziemlich billige Sache. Es wird basierend auf einer Wortdatenbank jedes gefundene Wort bewertet und das Ergebnis summiert und gemittelt. Aber als ich das gemerkt hatte, war das schon in JavaScript für Node implementiert und das habe ich dann auch beibehalten. Die mitgelieferte Wortdatenbank war jedoch ziemlich mager und wenig aussagekräftig. Allerdings hat die Uni Leipzig eine sehr gute Datenbank, die ich heruntergeladen und für die JavaScript Bibliothek aufbereitet habe. Das Ergebnis erschien mir bei Stichproben als ganz gut.

Fazit

Um ehrlich zu sein, ist dieser Aufwand für diese Auswertung nicht gerechtfertigt. Für mich hat sich das aber doch gelohnt, denn ich habe eine ganze Menge damit austesten können. Kotlin ist definitiv eine meiner absoluten Favoriten und es ist ein Genuss damit zu entwickeln. Alleine das ist es schon wert. Aber auch ElasticSearch konnte mich aus Entwicklersicht sehr begeistern. Das ist schon sehr genial, auch wenn ich nicht beurteilen kann, wie gut es im Betrieb ist. Als Cloudservice, wo man sich darüber keine Gedanken machen muss, würde ich es sofort bedenkenlos verwenden.

Besonders schön fand ich es zu sehen, wie wichtig hier der Einsatz von richtigen Datenstrukturen und das Ausloten von Parallelität ist. Als Studienaufgabe wären die einzelnen Schritte hervorragend geeignet. Ich habe immer darauf los entwickelt und der erste Wurf war immer so langsam, dass selbst der Import der DNB Bücher einige Wochen gedauert hätte. Hier ist man gezwungen sich Gedanken zu machen was man im Speicher halten kann, was man in welchen Paketen von oder zu ElasticSearch transferiert, wie man Datenstrukturen vorbereitet um vielleicht vorher etwas Zeit zu investieren, die man dann später aber um ein Vielfaches wieder herein holt.

In Summe war das ein sehr unterhaltsames kleines Projekt, das echt Spaß gemacht hat. Und ich hoffe, ich konnte vielleicht den ein oder anderen inspirieren. Ich glaube hier kann man noch ziemlich viel machen: nützliche Tools entwickeln, weitere Auswertungen ermitteln (vielleicht für andere Bereiche als Buchblogs) oder einen Watchdog für spezielle Inhalte entwickeln. Über Feedback freue ich mich auf jeden Fall und stehe gerne auch für Fragen zur Verfügung.

verfasst von Tobi

    1 Kommentar

  1. Miss Booleana 12. März 2018 at 20:44 Antworten

    Uiiii das ist ja mal ein Artikel zum Austoben für Softwareentwickler/Blogger wie mich. 😀 Ich fand ja deine Auswertung schon stark und habe mich gefragt wieviele Stunden du da reingesteckt hast, wie lange dein Rechner rödelte und wie warm er dabei lief v.A. bei der Arbeit auf den Datenmengen. Aber Elas.-Seach und Lucene waren sicherlich eine gute Entscheidung. Lucene kenne ich von der Arbeit, habe aber leider nur kurz damit zutun gehabt. Allerdings kann ich mir vorstellen, was für ein Aufwand insgesamt in deiner Analyse steckt und die Ergebnisse sind ja auch sehr cool und ausführlich.

    Der Ruf von Kotlin ist mir auch zu Ohren gekommen, aber ich habe bisher verpasst mir mal ein Hobby-Projekt zu suchen, um mich damit zu beschäftigen. Vielleicht browse ich einfach etwas durch deinen Code um mal ein Gefühl dafür zu bekommen wie sich Kotlin „anfühlt“. In jedem Fall sehr cool, dass du das alles zur Verfügung stellst – finde ich immer sehr lobenswert die Mentalität.

Kommentar verfassen