Von TEI-XML zu Volltext und Tokenisierung#

In diesem Abschnitt werden wir die im vorherigen Unterkapitel gesammelten Daten nutzen, um die Volltexte der Briefe in unseren Dataframe zu laden. Hier nochmal der Hinweis, dass wir eine Auswahl getroffen haben: Wir werden nur die Briefe herunterladen, die in der edition humboldt digital enthalten sind und die eine genaue Datierung besitzen. Da wir über die Anfrage die Briefe im TEI-XML Format erhalten, wir aber nur mit den Volltexten arbeiten wollen, ergibt sich daraus für uns das Ziel, die xml-tags zu entfernen. Zunächst aber laden wir die benötigten Python-Pakete und die Daten, die wir in einer csv-Datei gespeichert haben.

Import und Einlesen der Daten#

import string
import requests

import pandas as pd

from bs4 import BeautifulSoup as bs

Beim Einlesen der Daten wird mit dem Argument parse_date= die Spalte mit Datumsangaben in ein Datetime-Objekt umgewandelt. Dies ermöglicht weitere Optionen für das Arbeiten mit Zeitangaben.

df = pd.read_csv('data/AvH-letters-with-date.csv', parse_dates=['date'])
print(df.shape)
df.head()

Definieren von Funktionen#

Pandas bietet den Vorteil, einzelne Spalten eines Dataframes, also die Series, zu bearbeiten. Hierzu können die nötigen Funktionen entsprechend definiert und mit apply() angewandt werden. apply() ist wesentlich performanter als for-Schleifen, die hier ja eigentlich auch in Frage kommen könnten.

Die folgenden Funktionen nutzen wir später, um die TEI-XML Dateien herunterzuladen; hierzu nutzen wir die Spalte reference, denn hier sind die URLs der Dateien zu finden. Danach ziehen wir den reinen Text aus dem XML-Format heraus, um bei dem erhaltenen Text die Zeichensetzung zu entfernen und um diesen in Token zu zerlegen. Die Doc-Strings zu den Funktionen geben zusätzlich kurze Infos dazu, was die jeweilige Funktion ausführt.

def get_xml_file(reference):
    '''
    Function makes URL-request.
    Concatenates string and uses requests to get file.

    Return: requests-Response-Object    
    '''
    response = requests.get(reference + '.xml')
 
    return response
def get_text(reference):
    '''
    Function returns from xml-tags cleaned text.
    Calls helperfunction and makes soup to parse xml.
    Finds relevant part of xml and strips xml-tags.
    
    Return: string
    '''
    response = get_xml_file(reference)

    soup = bs(response.text, 'html.parser')

    text = soup.find('text') 

    clean_text = ' '.join(bs(str(text), 'html.parser').stripped_strings)

    return clean_text
def get_token(text):
    '''
    Function returns tokens from text.
    Removes whitespaces and punctuation.
    Normalizes tokens to lowercase.    
    
    Returns: list    
    '''
    text = ' '.join(text.split())

    for char in text:

        if char in string.punctuation + '—':
            text = text.replace(char, '')

    tokens = text.split()

    tokens = [ token.lower() for token in tokens ]

    return tokens

Aufruf der Funktionen#

In der nachfolgenden Notebookzelle rufen wir die Funktionen mit apply() jeweils auf einer Spalten auf. Die Rückgaben werden direkt neuen Spalten zugewiesen. Wie bei allen Benennungen ist es auch hier empfehlenswert, sprechende Titel für die Spalten zu nutzen. Die Ausführung der Zelle kann etwa 1 Minute dauern, da die Anfragen an einen externen Server gestellt werden. Dann geben wir die Anzahl von Spalten und Zeilen sowie die ersten Zeilen aus, um das Ergebnis zu prüfen.

df.loc[:, 'text'] = df.loc[:, 'reference'].apply(get_text)
df.loc[:, 'token'] = df.loc[:, 'text'].apply(get_token)
df.loc[:, 'nr_token'] = df.loc[:, 'token'].apply(len)

print(df.shape)
df.head()

Speichern der Ergebnisse#

Abschließend speichern wir die Ergebnisse. Diesmal nutzen wir das csv- und das JSON-Format. Der Vorteil von csv ist, das wir dieses Format auch leicht mit anderen Tabellenkalkulationsprogrammen öffnen können. Allerdings bleiben die Datentypen in den Zellen nicht erhalten - wir können beim Einlesen von csv-Dateien nur mit Strings weiterarbeiten und müssten diese dann konvertieren. JSON bietet hingegen den Vorteil, dass die Datentypen in den jeweiligen Spalten erhalten bleiben: Sind Listen oder Dictionaries in einer Spalten, dann stehen diese Datentypen auch nach dem erneuten Einlesen der JSON-Datei wieder direkt zur Verfügung. Lediglich Spalten, die Datetime-Objekte enthalten, müssten von der unix-Zeit wieder in eine Datetime-Objekt umgewandelt werden. Aber das schauen wir uns im nächsten Abschnitt genauer an. Es hängt also am Ende vom jeweiligen Anwendungszenario ab, welches Dateiformat Sie beim Speichern auswählen.

df.to_csv('data/AvH-letters-with-tokens.csv', encoding='utf8', index=False)
df.to_json('data/AvH-letters-with-tokens.json')