Datum (unbekanntes Format) aus einem Text heraussuchen

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Homer-S
User
Beiträge: 24
Registriert: Freitag 1. Dezember 2017, 22:30

Freitag 1. Dezember 2017, 22:44

Hallo,
Danke, dass ich im Forum teilnehmen darf.
Das habe ich vor:
Mein Python Skript soll einen Ordner durchsuchen, pdf Dateien mit OCR "erkennen" und danach die PDF Dateien nach einigen bekannten, öfter vorkommenden Begriffen umbenennen.
Was schon läuft:
Das ganze Thema OCR und Text erkennen läuft schon. Auch von wem der Brief kommt wird erkannt und die am häufigsten vorkommenden Betreffs ebenfalls.

An folgendem Problem scheitere ich nun schon eine Woche.
Ich würde gerne das erste im Text vorkommende Datum erkennen und ausgeben. Da es in manchen Briefen als reine Zahlen, oder mit ausgeschriebenem Monatsnamen oder nur zweistelligem Jahr angegeben ist, komme ich mit meinen bescheidenen Python Kenntnissen an ein Limit.

Mein letzter Gedanke war, wie mit Bruteforce auf die Abfrage zu gehen. Also Tag von 1-31 Monat von 1 bis 12 oder Januar bis Dezember und das mit 3 Jahren (2016-2018 bzw 16-18) durchlaufen zu lassen.

Da das aber eine sehr "unschöne Weise wäre" und ich gesehen habe, dass die Angaben mit ausgeschriebenem Monat dann keine Punkte haben, wird die Anzahl der Varianten etwas hoch.

Daher mein Hilferuf.

Gibt es ein/mehrere Module die sowas vielleicht schon können?
Hat das jemand schon mal realisiert?
Die Erkennungsquote muss nicht so hoch sein.

Danke für eure Hilfe!
nezzcarth
User
Beiträge: 823
Registriert: Samstag 16. April 2011, 12:47

Samstag 2. Dezember 2017, 09:17

An fertigen Modulen habe ich auf die Schnelle nur datefinder gefunden. Das hat bei einem kurzen Test für's Deutsche aber nicht so gut funktioniert. Die Hauptarbeit in dem Modul machen reguläre Ausdrücke. Du könntest dir also nun das Modul nehmen und die regulären Ausdrücke für deine Zwecke anpassen anpassen. Oder du sparst dir den Overhead durch das das Modul und versuchst selbst, die in den Briefen vorkommenden Datums-Variationen mit regulären Ausdrücken abzubilden. Dazu würde ich zunächst (z.B. mit 'grep') nach Jahreszahlen, Monatsnamen oder anderen Hinweisen suchen, um eine Liste von Datumsformaten in deinem Korpus zu erstellen, die dann mit regulären Ausdrücken formulieren kannst. Je nachdem, wie gut das klappt, kannst du dann nach und nach verschiedene Vorverarbeitungsschritte einbauen (z.B. Normalisieren, Tokensieren) oder die regulären Ausdrücke durch eine Parser-Bibliothek ersetzen.

Falls du die Daten nicht nur ausgeben, sondern damit auch Dinge tun möchtest, müsstest du sie anschließend noch parsen (z.B. mit dateparser das kommt auch mit Deutsch klar).

Weil du OCR erwähnst: Wie gut sind denn die Resultate? Falls du nämlich auch noch OCR Fehler ausgleichen musst, wird es noch mal komplizierter.
Homer-S
User
Beiträge: 24
Registriert: Freitag 1. Dezember 2017, 22:30

Samstag 2. Dezember 2017, 17:51

Hallo und schon mal Danke für deine Antwort.

Mein erster Schuss (und ich bitte stark zu Bedenken, das ich Anfänger bin) ist folgendermassen.
Ich bin mir sicher, das kann man wesentlich eleganter, einfacher und kürzer lösen, aber für mich war das schon ein großer Wurf :)
Für eure (gerne positive) Kritik bin ich offen

text ist der im PDF hinterlegte, "geocrte" Text, der Rest ist hier nicht mit aufgeführt und das Renaming muss ich jetzt nur noch machen.

Code: Alles auswählen

# Jahreszahlen die gesucht werden sollen
jahre = ["2017", "2018", "17", "18"]
tage = ["1.", "2.", "3.", "4.", "5.", "6.", "7.", "8.", "9.", "10.", "11.", "12.", "13.", "14.", "15.", "16.", "17.", "18.", "19.", "20.", "21.", "22.", "23.", "24.", "25.", "26.", "27.", "28.", "29.", "30.", "31.","01.", "02.", "03.", "04.", "05.", "06.", "07.", "08.", "09."]
monate = ["1.", "2.", "3.", "4.", "5.", "6.", "7.", "8.", "9.", "10.", "11.", "12.", "01.", "02.", "03.", "04.", "05.", "06.", "07.", "08.", "09.", "Jan", "Feb", "Mrz", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep",	 "Okt",	 "Nov",	 "Dez",  "Januar",	 "Februar",	 "März",	 "April",	 "Mai",	 "Juni",	 "Juli",	 "August", "September", "Oktober", "November", "Dezember"]

#Pruefen ob ein Datum  enthalten ist
    text_leerzeichen = text.replace(" ", "")
    liste_len = []
    liste_datum = []
    liste_tag = []
    liste_monat = []
    liste_jahr = []
    for jahreszahl in jahre:
        for monatsname in monate:
            for tageszahl in tage:
                datum_brute = tageszahl + monatsname + jahreszahl
                datum_vorhanden = re.search(datum_brute, text_leerzeichen)
                if datum_vorhanden:
		    l = len(datum_brute)
                    liste_len.append(l)
                    liste_datum.append(datum_brute)
                    liste_tag.append(tageszahl)
                    liste_monat.append(monatsname)
                    liste_jahr.append(jahreszahl)
    
    if liste_len != []:
	index = liste_len.index(max(liste_len))
    	rename_tag = liste_tag[index]
    	m = liste_monat[index]
	if m == "Januar" or "Jan":
	        rename_monat = "01"
    	elif m == "Februar" or "Feb":
        	rename_monat = "02"
    	elif m == "März" or "Mrz":
        	rename_monat = "03"
    	elif m  == "April" or "Apr":
        	rename_monat = "04"
    	elif m == "Mai" or "Mai":
        	rename_monat = "05"
    	elif m == "Juni" or "Jun":
        	rename_monat = "06"
    	elif m == "Juli" or "Jul":
        	rename_monat = "07"
    	elif m == "August" or "Aug":
        	rename_monat = "08"
    	elif m == "September" or "Sep":
        	rename_monat = "09"
    	elif m == "Oktober" or "Okt":
        	rename_monat = "10"
    	elif m == "November" or "Nov":
        	rename_monat = "11"
    	elif m == "Dezember" or "Dez":
        	rename_monat = "12"

    	rename_jahr = liste_jahr[index]
    else:
        rename_tag = "XX"
        rename_monat = "XX"
        rename_jahr = "XXXX"
    rename_datum = rename_jahr + "_" + rename_monat + "_" + rename_tag 
    rename_datum = rename_datum.replace(".", "")
    print(rename_datum)
Benutzeravatar
Sr4l
User
Beiträge: 1091
Registriert: Donnerstag 28. Dezember 2006, 20:02
Wohnort: Kassel
Kontaktdaten:

Samstag 2. Dezember 2017, 19:16

Ich denke nezzcarth hat an etwas anderes gedacht. Ich bin ultra schlecht mit regex deshalb ist das Ergebnis auch ultra schlecht und bei weitem nicht vollständig aber in etwa so:

Code: Alles auswählen

import re
text = "He was 12 Maerz 2017 carefully disguised 12.03.2017 but 12.03.17 captured  12.3.17 quickly by police."

regex1 = ur"([0-3][0-9]\.[0-1][0-9]\.[1-3][0-9][0-9][0-9]) "
regex2 = ur"([0-3][0-9]\.[0-1][0-9]\.[0-9][0-9]) "
regex3 = ur"([0-3][0-9]\.[1-9]\.[0-9][0-9]) "
regex4 = ur"([0-3][0-9] .*? [1-3][0-9][0-9][0-9]) "

regex = "|".join([regex1, regex2, regex3, regex4])
person = re.findall(regex, text)
print(person)
#[('', '', '', '12 Maerz 2017'), ('12.03.2017', '', '', ''), ('', '12.03.17', '', ''), ('', '', '12.3.17', '')]
Hier findet sich bestimmt noch jmd der RegEx kann und mir was bei bringt. Ich habe nämlich auf die schnell die Order Verknüpfung nicht hin bekommen. Habe lauter leere Einträge drin. :D
nezzcarth
User
Beiträge: 823
Registriert: Samstag 16. April 2011, 12:47

Samstag 2. Dezember 2017, 19:26

@homer-s:
Was mir an einem Code nicht so gut gefällt, sind insbesondere die vielen Wiederholungen. Das geht unter Verwendung von regulären Ausdrücken kürzer. Hier mal ein Beispiel, das ich anhand einiger Texte von gutenberg.org zusammengebaut habe:

Code: Alles auswählen

In [1]: import re

In [2]: files = ['werther2.txt', 'buddenbrooks.txt', 'traumdeutung.txt']

In [3]: DATE_PATTERN = re.compile(r'\d{1,2}\.?\s*(?:j[aä]n|feb|m[aä]r|apr|ma[iy]|jun|jul|aug|sep|o[ck]t|nov|de[cz])\w*(?:\s*\d{4}|\d{2})?', re.IGNORECASE)

In [4]: for file in files:
   ...:     print(file)
   ...:     with open(file) as fh:
   ...:         text = fh.read()
   ...:         dates = re.findall(DATE_PATTERN, text)
   ...:         print(dates)
   ...:         
werther2.txt
['20. Oktober 1771', '26. November', '24. Dezember', '8. Januar 1772', '20. Januar', '8. Februar', '17. Februar', '20. Februar', '15. März', '16. März', '24. März', '19. April', '5. Mai', '9. Mai', '25. Mai', '11. Junius', '16. Junius', '16. Junius', '29. Julius', '4. August', '21. August', '3. September', '4. September', '5. September', '6. September', '12. September', '15. September', '10. Oktober', '10. Oktober', '19. Oktober', '19. Oktober', '27. Oktober', '27. Oktober', '30. Oktober', '8. November', '15. November', '21. November', '22. November', '24. November', '26. November', '30. November', '1. Dezember', '4. Dezember', '6. Dezember', '12. Dezember', '14. Dezember', '20. Dezember']
buddenbrooks.txt
['14. April 1838', '00 Mark', '1 Mark', '22. September', '22. September 1845', '00\nMark', '30. April 1846', '2. August 1846', '8. Oktober 1846', '00 Mark', '00 Mark', '20. Juli', '2. April 1857', '00 Mark', '27. Mai', '3. Juni', '7. Juli', '15. April 1869', '15. April', '15. April\n1861']
traumdeutung.txt
['1. Dezember 1788', '24. Juli 1895', '2. Oktober 1910', '5. September', '11. Oktober', '17. September', '3. Oktober 1910', '1.\n  Oktober', '22.\n  August', '25.\n  August', '19. Juli', '6. August 1838', '6. August 1838', '12. März 1828', '6. November 1843', '15. Januar 1848', '1. Dec', '11. Juli 1891', '5. September 1895', '8. Dez', '20 mars 1898', '3. April 1896', '9. Januar\n1892', '15. Nov', '30. oct', '11. Nov', '26. April 1913', '13. May 1911', '3 März', '8. Dez', '8. Dez', '13. May 1911', '13. May 1911']
Wie man sieht, sind da durchaus auch ein paar Falsch-Positive dabei; andererseits werden aber auch ein paar kompliziertere Fälle gefuden. Wenn man diesen Ansatz wählt, ist recht viel Fine-Tuning an den regulären Ausdrücken notwendig. Man kommt dann vermutlich auch bei mehr als einem einzigen heraus, für die verschiedenen strukturellen Typen. Ab einer gewissen Komplexität wäre auch ein "richtiger" Parser wie z.B. ply oder pyparsing angebracht, da echte reguläre Ausdrücke nicht Kontext-sensitiv sind (manche Implementationen haben Look-Around-Assertions, die aber auch beschränkt sind).

Ich hatte wegen des OCR gefragt, da per OCR generierter Text i.d.R. auch Fehler enthält. Wenn die Suche nach Datumsangaben auch in solchen defekten Texten funktionieren soll, muss man das anders angehen.

EDIT: Sr4l war schneller :(
Homer-S
User
Beiträge: 24
Registriert: Freitag 1. Dezember 2017, 22:30

Sonntag 10. Dezember 2017, 19:19

Hat ein wenig gedauert, aber ich habe viel probiert.

Meinen Code habe ich weiter entwickelt und der funktioniert nun recht gut. Dass er vom Grad der Feinheit eher ein Vorschlaghammer ist, ist ir wohl bewusst.
Ab einer gewissen Komplexität wäre auch ein "richtiger" Parser wie z.B. ply oder pyparsing angebracht, da echte reguläre Ausdrücke nicht Kontext-sensitiv sind (manche Implementationen haben Look-Around-Assertions, die aber auch beschränkt sind).
Ich bin anscheinend zu weit weg vom Programmieren, denn hier hab nur Bahnhof verstanden.

Mit eurer beiden Ansätze habe ich auch experimentiert, die funktionieren auf den ersten Schuss auch gut, aber auch hier fehlt mir das Wissen, was man aus anderen Erweiterungen vielleicht nutzen kann um das agnze zu verfeinern.
Im Moment bin ich mit dem erreichten zufrieden. Danke trotzdem für eure Hilfe
Antworten