Wörter mit Python zählen

Zum Zeitpunkt der Erstellung dieses Beitrags ist noch nicht entschieden, ob die Schwarz-Rote-Koalition zustandekommen wird. Nichtsdestotrotz habe ich den vorliegenden Koalitionsvertrag zum Anlaß genommen, einmal zu demonstrieren, wie man mit Python in einem Textdokument nach Wörtern durchsuchen kann. Sollte das gesuchte Wort vorkommen, soll außerdem die Anzahl ermittelt werden. Der Schwerpunkt dieses Artikels liegt damit bei den Dictionaries sowie regular expressions.

Der als PDF-Dokument vorliegenden Koalitionsvertrag muß zunächst in ein Textdokument umgewandelt werden, was in der Linux-Shell mit pdf2txt schnell erledigt werden kann:

pdf2txt -o koalitionsvertrag.txt Koalitionsvertrag.pdf

Damit Euch diese Anweisung zur Verfügung steht, müßt Ihr zuvor das Paket python-pdfminer installieren:

sudo apt-get install python-pdfminer

oder

sudp apt install python-pdfminer

Hinweis: Ihr könnt den Koalitionsvertrag 2018 und das Python-Programm koalition.py als ZIP-Datei herunterladen: Koalition.zip

Dann geht es an den Python-Code, wobei ich hier keine speziellen Textanalyse-Bibliotheken installiere. Ganz ohne Import-Anweisungen kommen wir aber nicht aus, so dass unser Python-Skript wie folgt beginnt:

import re
import argparse

Für die Datei “koalitionsvertrag.txt” lege ich dann folgende Konstante an:

FILE_NAME = 'koalitionsvertrag.txt'

Das Öffnen dieser Datei könnte aus irgendeinem Grund fehlschlagen. Deswegen geht es mit einer Ausnahmebehandlung weiter:

try:
    file_handle = open(FILE_NAME)
except:
    print('File cannot be opened:', FILE_NAME)
exit()

An dieser Stelle sei angemerkt, dass dieser Code nicht auf Perfektion getrimmt ist. Vielmehr könnte man von einem “Quick-and-Dirty-Beispiel” sprechen. So manches ließe sich “sauberer” programmieren. Es sei Euch überlassen, gegebenenfalls entsprechende Verbesserungen vorzunehmen, mithin ins refactoring einzusteigen.

Die jetzt folgende Funktion find_word() soll die eigentliche Arbeit leisten:

def find_word(input_word):
    for line in file_handle:
        x = re.findall(input_word, line)        
        for w in x:
            di[w] = di.get(w, 0) + 1
    return di

Mit der for-Schleife wird jede Zeile des Textes durchlaufen. Von besonderem Interesse ist dabei die Zeile

x = re.findall(input_word, line)

Wie man in der Python-Shell (IDLE) und dir() feststellen kann, wird mit dem Modul re unter anderem die Methode findall() zur Verfügung gestellt.

>>> import re
>>> dir(re)
'A', 'ASCII', 'DEBUG', 'DOTALL', 'I', 'IGNORECASE', 
'L', 'LOCALE', 'M', 'MULTILINE', 'RegexFlag', 'S', 
'Scanner', 'T', 'TEMPLATE', 'U', 'UNICODE', 'VERBOSE', 
'X', '_MAXCACHE', '__all__', '__builtins__', 
'__cached__', '__doc__', '__file__', '__loader__', 
'__name__', '__package__', '__spec__', '__version__', 
'_alphanum_bytes', '_alphanum_str', '_cache', 
'_compile', '_compile_repl', '_expand', '_locale', 
'_pattern_type', '_pickle', '_subx', 'compile', 'copyreg', 
'enum', 'error', 'escape', 'findall', 'finditer', 
'fullmatch', 'functools', 'match', 'purge', 'search', 
'split', 'sre_compile', 'sre_parse', 'sub', 'subn', 'template']

Sie wird hier dazu genutzt, jede Zeile des Textes nach der gesuchten Zeichenkette zu durchsuchen. Das Ergebnis – eine Liste – wird dann der Variablen x zugewiesen.

Das Modul re ermöglicht die Verwendung von regular expressions (kurz: regex). Ohne allzu sehr ins Deteil gehen zu wollen, kann man mit regular expressions in Zeichenketten nach bestimmten Übereinstimmungen suchen. Dafür bedient man sich der durch re bereitsgestellten Methoden und einer bestimmten Syntax, die die Suchkriterien festlegen.

Im Ausgangsbeispiel wird von dieser Syntax kein Gebrauch gemacht. Am Ende dieses Artikels gehe ich aber in einer Abwandlung etwas genauer auf die Syntax von regular expressions ein.

Von der Liste müssen wir nur noch zur Zeichenkette kommen, die gezählt werden soll:

for w in x:  
    di[w] = di.get(w, 0) + 1

Dafür wird das Dictionary di genutzt, das als Ergbnis ein Schlüssel-Wert-Paar, bestehend aus dem Wort und der Anzahl enthält, z.B.:

{'Sicherheit': 113}

Die Zeile

di.get(w, 0) + 1

zählt den Wert für jedes im Dictionary gefundene Wort um eins hoch. Das mag befremdlich aussehen, doch letztendlich ist es eine elegante Kurzform von

words = ['SPD', 'CDU', 'Sicherheit', 'SPD']
di = dict()
for word in words :
    if word not in di: 
        di[word] = 1
    else :
        di[word] = di[word] + 1
print(di)

# Ausgabe:
{'SPD': 2, 'CDU': 1, 'Sicherheit': 1}

Für jedes Wort, das nicht im Dictionary enthalten ist, wird ein neuer Eintrag im Dictionary vorgenommen. Ist das Wort bereits vorhanden, wird der dazugehörige Wert um eins erhöht. Dabei wird im Codebeispiel die Anzahl sämtlicher Wörter ermittelt. (Während uns in koalition.py nur die zu suchende Zeichenkette interessiert.)

Die Überprüfung, ob ein Dictionary einen bestimmten Schlüssel enthält, ist derartig praxisrelevant, dass es dafür die Methode get() gibt. Dieser Methode wird die zu durchsuchende Liste übergeben sowie der Standardwert 0, mit dem gekennzeichnet wird, dass sich das Wort bisher nicht im Dictionary befindet.

Als Ergebnis erhält man dadurch ein Dictionary mit den vorhandenen bzw. gesuchten Wörtern sowie deren Anzahl.

Zum Schluss müssen wir uns noch um den Code kümmern, mit dem beim Aufruf von koalition.py das zu suchende Wort übergeben werden kann:

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('-w', type=str, default='SPD', \
        help='add the string you want to search for')
    args = parser.parse_args()
    result = find_word(args.w)

    print('')
    for word, count in result.items():
        print('Searched word: {}\nCount: {}'.format(word, count))


if __name__ == '__main__':
    di = dict()
    main()
    print('')

Über die Zeile

parser.add_argument('-w', type=str, default='SPD'

wird definiert, dass der Nutzer über die Option -w das zu suchende Wort festlegen kann. Sollte er das nicht tun, wird standardmäßig nach “SPD” gesucht.

Die print()-Funktion

print('Searched word: {}\nCount: {}'.format(word, count))

gibt uns dann das Ergebnis aus; für “SPD” also:

Searched word: SPD
Count: 6

Und für

koalition.py -w Glasfaser

würde man

Searched word: Glasfaser
Count: 6

erhalten.

Dieser Code erfasst nicht nur das gesuchte Wort an sich, sondern zählt auch jene Zeichenketten, in denen das Wort als Bestandteil enthalten ist (z.B. “Sicherheit” in “Sicherheitsrat”). Insofern ist die ausgegebene Anzahl irreführend, denn sie bezieht sich beispielsweise eben nicht nur auf “Sicherheit”. Deswegen zeige ich im nächsten Abschnitt, wie durch regular expressions eine Differenzierung erreicht werden kann.

Mehr zu regular expressions

Im Ausgangsbeispiel wird die übergebene Zeichenkette an sich gezählt und als Teil-Zeichenkette (Substring) gezählt. Dies erfolgt mit:

 x = re.findall(input_word, line)

Verantwortlich dafür ist findall() und der Umstand, dass dieser Methode hier nur das zu suchende Wort, ohne der Angabe weiterer Suchkriterien, also regular expressions, übergeben wird.

Durch Verwendung weiterer Suckriterien können wir dies anpassen. Um das zu demonstrieren, soll jetzt nach einem bestimmten Wort und den Zeichenketten, in denen dieses Wort vorkommt, differenziert werden.

Um das zu erreichen, wird obige Zeile wie folgt geändert:

x = re.findall(input_word + '[^ ]*', line)

Das zu suchende Wort wird um '[^ ]*' ergänzt. Dabei meint [^ ] das als Übereinstimmung nach Zeichen gesucht werden soll, die nicht Leerzeichen sind. Und * bedeutet, dass es mehrere Zeichen sein sollen. Damit wird also nach “Sicherheit” gesucht (und die Anzahl bestimmt) und den daran direkt anschließenden Zeichen, endend mit dem nächsten Leerzeichen.

Sucht man nun nach Sicherheit

koalition.py -w Sicherheit

erhält man folgende Ausgabe (Auszug):

Searched word: Sicherheit
Count: 49
Searched word: Sicherheitsbehör-
Count: 1
Searched word: Sicherheit.
Count: 10
Searched word: Sicherheitspolitik
Count: 4

Das Wort “Sicherheit” an sich kommt also “nur” 49 mal vor, ansonsten ist es eine Teil-Zeichenkette anderer Wörter. Wobei man sagen muss, dass unser Ergebnis nicht ganz korrekt ist. Denn wie ebenfalls ersichtlich ist, gilt “Sicherheit.” (also „Sicherheit“ mit einem Punkt) als ein Wort. Um das zu ändern, müssten wir die Suchkriterien dahingehend verfeinern, dass ein Punkt oder ein anderes Sonderzeichen nicht berücksichtigt werden sollen. Dies erspare ich mir aber an dieser Stelle. Statt dessen sei es Euch überlassen, sich näher mit den regular expressions zu befassen.

Wenn Ihr Euch die Ausgabe genauer anseht, dann werdet Ihr feststellen, dass teilweise Leerzeilen ausgegeben werden. Dies hängt damit zusammen, dass wir darauf verzichtet haben, etwaige Zeilenumbrüche zu entfernen. Mit dem Hinzufügen dieser Zeile können wir dies ändern:

line = line.rstrip()

Dadurch sieht die Funktion find_word() nun folgendermaßen aus:

def find_word(input_word):
    for line in file_handle:
        line = line.rstrip()
        x = re.findall(input_word + '[^ ]*', line)

        for w in x:
            di[w] = di.get(w, 0) + 1
    return di

Der gezeigte Code sollte ausreichend Anregung dafür bieten, dieses Programm noch zu verbessern. Viel Spaß dabei!

Weiterführende Links

Durch Benutzung dieser Website erklären Sie sich mit der Verwendung von Cookies einverstanden. Mehr Informationen

Diese Website speichert Cookies. Die Verwendung von Cookies dient dazu, Inhalte und Anzeigen zu personalisieren, Funktionen für soziale Medien anbieten zu können und die Zugriffe auf diese Website zu analysieren. Außerdem werden Informationen zur Nutzung dieser Webseite an Partner für soziale Medien, Werbung und Analysen weitergegeben. Mit der Verwendung dieser Internetpräsenz stimmen Sie zu, dass diese Seiten Cookies für Analysen, personalisierte Inhalte und Werbung verwenden dürfen. Weitere Informationen hierzu finden Sie in meiner Datenschutzerklärung.

Schließen