Dublettenprüfung
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
"""Textnormalisierung fuer Phase 2.
|
||||
|
||||
Gemeinsame Vorstufe der Module. Jede Aenderung hier aendert die
|
||||
coder_version - siehe version.py.
|
||||
|
||||
Grundsatz: die Normalisierung ist rein und ohne Zustand. Gleiche Eingabe,
|
||||
gleiche Ausgabe, auf jeder Maschine und in jedem Prozess.
|
||||
"""
|
||||
|
||||
import html
|
||||
import re
|
||||
import unicodedata
|
||||
|
||||
# Was hier steht, geht in den Versions-Hash ein.
|
||||
REGELWERK_VERSION = "norm-3"
|
||||
|
||||
_TAGS = re.compile(r"<[^>]{1,200}>")
|
||||
_MEHRFACH_LEER = re.compile(r"\s+")
|
||||
|
||||
# Anfuehrungszeichen und Striche vereinheitlichen. Verschiedene Haeuser setzen
|
||||
# fuer dieselbe Agenturmeldung verschiedene Zeichen; ohne das scheitert schon
|
||||
# die Trigrammbildung.
|
||||
_ZEICHEN = str.maketrans({
|
||||
"„": '"', "“": '"', "”": '"', "»": '"', "«": '"', "‟": '"',
|
||||
"‚": "'", "‘": "'", "’": "'", "‹": "'", "›": "'", "´": "'", "`": "'",
|
||||
"–": "-", "—": "-", "‑": "-", "‒": "-", "−": "-",
|
||||
"…": "...", " ": " ", " ": " ", " ": " ", "": " ",
|
||||
})
|
||||
|
||||
# "+++ Newsblog +++" und Verwandtes: redaktioneller Aufmacher, der nichts
|
||||
# ueber den Inhalt sagt. Dieselbe Meldung traegt ihn bei einem Haus und beim
|
||||
# naechsten nicht.
|
||||
_PLUSKASTEN = re.compile(r"\+{2,}[^+]{0,120}?\+{2,}")
|
||||
|
||||
# Satzzeichen fallen weg: sie sind der haeufigste Unterschied zwischen zwei
|
||||
# Fassungen derselben Meldung und tragen nichts zur Unterscheidung bei.
|
||||
_KEIN_WORT = re.compile(r"[^\w\s]", re.UNICODE)
|
||||
|
||||
# Ressortkuerzel, das Haeuser ihren Titeln voranstellen: "Raumfahrt: Deutsche
|
||||
# Rakete ...", "Notfaelle: Fuenf Tote ...", "Fussball: FIFA-Praesident ...".
|
||||
# Dieselbe Agenturzeile bekommt bei jedem Haus ein anderes - oder gar keins.
|
||||
#
|
||||
# Bewusst eng gefasst: hoechstens vier Woerter vor dem Doppelpunkt, und was
|
||||
# danach kommt, muss fuer sich stehen koennen. Sonst frisst das Muster
|
||||
# Titel, bei denen der Teil vor dem Doppelpunkt die eigentliche Aussage
|
||||
# traegt ("Habeck: Wir haben uns geirrt").
|
||||
_RESSORT = re.compile(r"^\s*(?P<kopf>[^:]{2,34}):\s+(?P<rest>.+)$", re.DOTALL)
|
||||
_RESSORT_MAX_WOERTER = 4
|
||||
_REST_MIN_WOERTER = 5
|
||||
|
||||
|
||||
def _ressort_abtrennen(text):
|
||||
treffer = _RESSORT.match(text)
|
||||
if not treffer:
|
||||
return text
|
||||
kopf, rest = treffer.group("kopf"), treffer.group("rest")
|
||||
if len(kopf.split()) > _RESSORT_MAX_WOERTER:
|
||||
return text
|
||||
if len(rest.split()) < _REST_MIN_WOERTER:
|
||||
return text
|
||||
return rest
|
||||
|
||||
|
||||
def normalisiere(*teile, ressort_abtrennen=False):
|
||||
"""Vergleichstext aus einem oder mehreren Feldern.
|
||||
|
||||
Reihenfolge ist bedeutsam: Tags vor Entities, NFKC vor der
|
||||
Zeichentabelle, Kleinschreibung vor den Mustern, und das Ressortkuerzel
|
||||
vor dem Entfernen der Satzzeichen - danach gibt es keinen Doppelpunkt
|
||||
mehr, an dem es zu erkennen waere.
|
||||
|
||||
ressort_abtrennen gilt nur fuer Titel. Im Teaser steht der Doppelpunkt
|
||||
fuer gewoehnlich mitten im Satz.
|
||||
"""
|
||||
text = " ".join(t for t in teile if t)
|
||||
if not text:
|
||||
return ""
|
||||
text = _TAGS.sub(" ", text)
|
||||
text = html.unescape(text)
|
||||
text = unicodedata.normalize("NFKC", text)
|
||||
text = text.translate(_ZEICHEN)
|
||||
text = text.casefold()
|
||||
text = _PLUSKASTEN.sub(" ", text)
|
||||
if ressort_abtrennen:
|
||||
text = _ressort_abtrennen(text)
|
||||
text = _KEIN_WORT.sub(" ", text)
|
||||
return _MEHRFACH_LEER.sub(" ", text).strip()
|
||||
|
||||
|
||||
def woerter(text):
|
||||
return text.split()
|
||||
|
||||
|
||||
def trigramme(text):
|
||||
"""Wort-Trigramme als Menge.
|
||||
|
||||
Menge, nicht Liste: der Jaccard-Wert braucht sie so, und Wiederholungen
|
||||
innerhalb einer kurzen Meldung sind kein Signal.
|
||||
"""
|
||||
w = woerter(text)
|
||||
if len(w) < 3:
|
||||
# Zu kurz fuer Trigramme. Das Wort selbst ist besser als nichts,
|
||||
# aber der Aufrufer prueft ohnehin auf Mindestlaenge.
|
||||
return {(x,) for x in w}
|
||||
return {tuple(w[i:i + 3]) for i in range(len(w) - 2)}
|
||||
Reference in New Issue
Block a user