Vollautomatischen Blog einrichten und sich selbst überlassen, Teil 1 Text Mining – Erkenntnis durch Basics

Von Mirco Lang

Die Auswertung von Textmaterial kann ungemein spannend sein. Schon mit ein paar Standard-Linux-Tools lassen sich nützliche Erkenntnisse gewinnen. Dies ist der erste Schritt auf dem Weg zu einem vollautomatischen Blog.

Die automatische Erfassung und Auswertung von Textmaterial ist der erste Schritt auf unserem Weg zum vollautomatischen Blog.
Die automatische Erfassung und Auswertung von Textmaterial ist der erste Schritt auf unserem Weg zum vollautomatischen Blog.
(Bild: KevinKing / Pixabay)

In drei Artikeln zum vollautomatischen Blog – als Basis für eigene Entwicklungen. Die für diese Serie geplanten drei Beiträge bauen aufeinander auf, funktionieren aber auch ganz unabhängig. Es geht darum, Daten automatisiert zu beziehen, aufzubereiten und anschließend zu veröffentlichen.

Für unser Beispielprojekt heißt das ganz konkret: Webseiten auf Häufigkeiten von Schlagwörtern zu untersuchen, die Ergebnisse in Diagrammen darzustellen und letztlich in einem Blogsystem zu veröffentlichen – alles ohne menschliche Interaktion und mit Standardwerkzeugen.

Dieser erste Teil widmet sich dem Bezug und der Aufbereitung von Daten, Teil zwei der grafischen Umsetzung via Gnuplot. Im dritten Teil füttern wir dann ein Flat-File-CMS mit den Plots und zusätzlichen Informationen, die automatisch als neue Posts veröffentlicht werden.

Die Verarbeitung und Analyse von Sprache kann tatsächlich ungemein komplex sein. In den High-End-Versionen lässt sich natürliche Sprache mit Hilfe von künstlicher Intelligenz normalisieren, analysieren und in bestimmte Kontexte einordnen, neuronale Netzwerke könnten Krankheiten automatisiert anhand von Standardberichten erkennen und so weiter.

Ebenso zeigen Data-Mining-Plattformen wie Kaggle, die wir kürzlich vorgestellt haben, die Komplexität von Techniken und Problemstellungen im Bereich Big Data. Aber es muss sich nicht immer alles gleich um die ganze Schlagwortbatterie und aufwändige Algorithmen drehen. Text Mining verliert deutlich an Schrecken, wenn man sich die Grundlagen anschaut.

Hier einmal ein Beispiel: Sie kennen mit Sicherheit die hübschen Wortwolken, gerne genutzt als Einstiegsbilder für Artikel oder zum einfachen Navigieren auf Webseiten. Im akademischen Kontext dienen sie der Visualisierung von Informationsräumen, um (Schwerpunkt-)Inhalte auch längerer Texte auf einen Blick zu vermitteln. Für dieses hübsche wie nützliche Produkt benötigt man lediglich eine simple Liste mit Schlagworten und deren Häufigkeiten – die man wiederum mit simplen Text-Mining-Werkzeugen erzeugen kann.

Im Grunde ist das Vorgehen recht trivial, vor allem, wenn man es einfach formuliert:

  • 1. (Roh-)Daten holen
  • 2. Daten formatieren und filtern
  • 3. Häufigkeit von Schlagwörtern notieren

Statt Schlagwörter vorzugeben, könnte man freilich auch alle inhaltlich relevanten (!) Wörter analysieren – was deutlich komplexer ist, wie wir später sehen werden. Als kleines, praktisches Beispiel folgende Fragestellung: Wie häufig kommen gegebene Schlagwörter auf den ersten 50 Seiten im Bereich DevOps auf Dev-Insider.de vor?

So simpel das Prozedere ist, so nützlich ist es auch! Beispielsweise ließen sich so inhaltliche Schwerpunkte der Konkurrenz identifizieren. Oder die Frage beantworten, über welche Parteien Nachrichtenmagazine besonders häufig oder eben selten berichten.

Beschränkt man derlei Abfragen auf Startseiten oder einen Datumsbereich, lassen sich auch fix automatisierte Benachrichtigungssysteme entwickeln. Selbst vollautomatische Blog-Angebote sind mit den folgenden Basics problemlos möglich, die Ergebnisse müssten lediglich in ein simples, textbasiertes CMS eingespeist werden.

Wieviel Cloud steckt im DevOps-Bereich?

Die Menge der Schlagwörter lässt sich ohne Aufwand beliebig steigern, der Einfachheit halber soll es hier schlicht um folgende drei Begriffe gehen:

  • cloud
  • kubernetes
  • git

Und wieder der Einfachheit halber: Die Schlagwörter kommen in eine separate Datei „terms“. Nun geht es im ersten Schritt ums Besorgen der Daten. Dafür muss zunächst der Aufbau der URL bekannt und geeignet sein – nicht immer lassen sich Seitenzahlen oder gar Datumsbereiche URL-kodiert angeben. Im Falle von Dev-Insider ist das transparent:

curl https://www.dev-insider.de/devops/?p=1

Die Aufgabe erledigt also schlicht das Standardwerkzeug curl, der DevOps-Bereich findet sich direkt in der URL wieder, ebenso die Seitenzahl, hier „1“. Allerdings erhalten wir damit natürlich nur den HTML-Code.

Um ordentlich verarbeitbaren Text zu erhalten, kommt mit „html2text“ das einzige Tool ins Spiel, das nicht unbedingt zu den Standards gehört. Dafür lässt es sich aber überall via Paketmanager nachinstallieren. Da die Seiten 1 bis 50 abgerufen werden sollen, hilft eine einfahce for-Schleife, die die Daten dann in eine Hilfsdatei „rawdata“ schreibt:

Jetzt Newsletter abonnieren

Täglich die wichtigsten Infos zu Softwareentwicklung und DevOps

Mit Klick auf „Newsletter abonnieren“ erkläre ich mich mit der Verarbeitung und Nutzung meiner Daten gemäß Einwilligungserklärung (bitte aufklappen für Details) einverstanden und akzeptiere die Nutzungsbedingungen. Weitere Informationen finde ich in unserer Datenschutzerklärung.

Aufklappen für Details zu Ihrer Einwilligung
for a in {1..50}; do
   curl https://www.dev-insider.de/devops/?p=$a |
   html2text.py >> rawdata
done

Somit liegen die Rohdaten lokal vor und können verarbeitet werden. Allerdings finden sich in diesen Rohdaten mitunter massenhaft unerwünschte Dinge, in diesem Fall beispielsweise die angeteaserten Artikel aus dem Hauptmenü (Start, Specials, Development, DevOps etc.). Hier müssen Sie wieder manuell tätig werden und den Text analysieren: Wie lassen sich relevante Inhalte (hier die jeweils zwei Artikel-Überschriften) abgrenzen?

Die Identifikation von relevantem Content – immer noch eine Aufgabe für Menschen.
Die Identifikation von relevantem Content – immer noch eine Aufgabe für Menschen.
(Bild: Lang / html2text)

In diesem Abschnitt kann es ganz schön wild werden. Fixe Header- und Footer-Bereiche ließen sich mit den Tools „head“ und „tail“ abschneiden, manchmal gibt es besondere Zeichenketten, die den eigentlichen Inhaltsbereich im Ganzen markieren, in anderen Fällen muss mit schier endlosen regulären Ausdrücken gearbeitet werden. Es hilft nur ein Blick in die Rohdaten – und auch hier ist Dev-Insider wieder sehr entwicklerfreundlich: Überschriften beginnen mit „####“ und Subüberschriften mit „#####“ - insofern kann man hier schlicht mit grep arbeiten, um die relevanten zeilen zu erhalten:

for a in {1..50}; do
   curl https://www.dev-insider.de/devops/?p=$a |
   html2text.py |
   grep -i #### >> rawdata
done

Ab dieser Stelle wird es dann wieder etwas universeller. Zunächst ist es meistens angebracht, Sonderzeichen durch Leerzeichen zu ersetzen, sei es mit ein paar sed-Zeilen oder etwas einfacher und oft ausreichend mit tr, zum Beispiel so:

tr '{[]}:(),\.#/"' ' '

Es bleibt also eine Liste mit Zeilen ohne Sonderzeichen, gewünscht ist aber eine reine Wortliste. Hier hilft – Standard, aber eher nicht sonderlich bekannt – das wunderbare Tool fmt, das mit …

fmt -0

… schlicht und ergreifend jeden Absatz auf eine maximale Breite von „0“ formatiert, was in der fmt-Welt genau einem Wort entspricht. Und nun, da Zeilen einzelnen Wörtern entsprechen, lässt sich wieder grep einsetzen, um die Schlagworte aus der Datei „terms“ darin zu suchen:

grep -oif terms

Übrig bleibt eine unsortierte Liste mit jedem Vorkommen eines der Schlagwörter in der Datei „rawdata“. Das Skript soweit sieht also so aus:

for a in {1..50}; do
   curl https://www.dev-insider.de/devops/?p=$a |
   html2text.py |
   tr '{[]}:(),\.#/"' ' ' |
   fmt -0 |
   grep -oif terms >> rawdata
done

Nach 50 Durchläufen müssen die Schlagworte/Zeilen in der Datei natürlich noch sortiert und final gezählt werden, was mit dem immer gleichen Dreisatz funktioniert:

cat rawdata |
sort |
uniq -ci |
sort -nr

Erst wird alphabetisch sortiert, dann zählt (c) „uniq“ kontextunsensitiv (i) das Vorkommen der Schlagworte, dann wird abermals sortiert, nun numerisch (n) und reverse (r), also beginnend mit dem größten Vorkommen, um eine hübsche Liste zu bekommen:

1885 cloud
343 git
264 kubernetes

Der Vollständigkeit halber hier nochmal das komplette Skript:

for a in {1..50}; do
   curl https://www.dev-insider.de/devops/?   p=$a |
   html2text.py |
   tr '{[]}:(),\.#/"' ' ' |
   fmt -0 |
   grep -oif terms >> rawdata
done
cat rawdata |
sort |
uniq -ci |
sort -nr

Natürlich ließe sich hier an einigen Stellen optimieren. Daten per cat in eine Pipe zu werfen ist nicht schön, aber gut les- und nachvollziehbar; die tr-Anweisung ließe sich durch ein paar sed-Zeilen ersetzen und in einigen Fällen könnte man auf den fmt-Part verzichten und grep weiter strapazieren. Aber so ist das Vorgehen schon sehr universell, die eigentliche Arbeit steckt in der manuellen Analyse des per curl und html2text gezogenen rohen Texts.

Ein kleiner Tipp: Keine Angst vor Spaghetticode. Den Text kann man ruhig nach und nach mit mehreren Tools (awk, sed, tr) bereinigen, mal bietet sich dieses, mal jenes Vorgehen an. Ein konsolidiertes sed-Statement kann man am Ende immer noch einbauen. Hier ein Beispiel für diese Art der Textsäuberung aus einem mehrere Jahre alten Projekt, das es auf Artikel eines großen Nachrichtenmagazins abgesehen hatte:

sed '/\* Home/,/Artikel von/d' |
sed '/Archiv\n\*/,/\* Impressum/d' |
tr '_' ' ' |
sed 's/[[:punct:]](?<!-)/ /g' |
tr ':(),\."' ' '|
tr '[\*[[:digit:]]]' ' '

Diese grausigen Doppelungen von sed- und tr-Aufrufen hatten sich damals tatsächlich so ergeben, einfach weil ständig irgendwelche Artefakte auftauchten – nun, möglich, dass das an der Scripting-Umgebung Windows lag.

Viel Potenzial zur Optimierung steckt aber vor allem in der Ablösung der for-Schleife durch Shell-Globbing (sofern die Shell dies zulässt), da sich damit ganze Nachrichtenarchive durchforsten lassen, in der Art:

curl https://www.example.com/archive/artikel-[01-30].[1-12].2020.html

Damit würde curl für jeden Monat eines Jahres die ersten dreißig Tage durchforsten. Klar, möchte man genau arbeiten, müsste hier noch beispielsweise über eine case- oder if-Abfrage die genauen Bereiche für die einzelnen Monate festlegen.

Arbeit mit Stoppwörtern

Die Analyse auf bestimmte Terme ist simpel und präzise. Was aber, wenn Sie nicht wissen wollen, wie populäre der Term „kubernetes“ ist, sondern, welche Terme am häufigsten vorkommen? Dann brauchen Sie zum einen eine Stoppwortliste, also eine Datei mit Wörtern wie der, die, das, eine, um, als und anderen, die schlicht keine inhaltliche Bedeutung haben. Solche Stoppwortlisten bekommt man überall im Netz, zum Beispiel bei GitHub. Diese könnte man nach dem obigen fmt-Kommando auf die Liste loslassen:

grep -wvif my_stopwords

Damit würde einfach alles gefunden, was eben nicht in der Stoppwortliste steht. Das eigentliche Problem: Das Bereinigen des Textes erfordert tendenziell deutlich mehr Arbeit, ebenso wie das Erweitern der Stoppwortliste um Zeichenketten, die eben nicht allgemeingültig als Stoppwort gelten, sondern nur für Ihr spezielle Aufgabe. Ein Wort wie „Insider“ wäre auf Seiten wie Dev-Insider oder CloudComputing-Insider nicht sonderlich aussagekräftig.

Am Ende beider Wege steht eine simple Liste mit Worthäufigkeiten – wie gesagt, im Grunde eine äußerst triviale Angelegenheit. Und doch lässt sich viel damit anfangen: Vergleiche von Bereichen oder Webseiten, grafische Aufbereitung in Wordclouds, als Basis für Alarmierungssysteme, die Identifikation relevanter Personen und Themen und und und.

Es gibt viele weitere Standard-Tools, die sich für weitere Aufarbeitungen andienen, beispielsweise cat, join, split, paste und awk – wobei das Beherrschen einer Sache alle Aufgaben dieser Art enorm vereinfacht: Reguläre Ausdrücke.

Im nächsten Teil wird dann aus dieser Liste mit Worthäufigkeiten ein schickes Diagramm – und absolut erstaunlich: Statt des omnipräsenten Excels erledigt das das über 35 Jahre alte Kommandozeilen-Tool Gnuplot.

(ID:47706637)