Tagger/tagger.py

183 lines
6.6 KiB
Python
Raw Normal View History

2022-05-28 12:49:10 +00:00
import json
2022-05-05 16:01:10 +00:00
import re
2022-05-05 17:30:56 +00:00
from dataclasses import dataclass
from html.parser import HTMLParser
from pathlib import Path
2022-05-28 17:34:37 +00:00
from urllib.parse import unquote_plus
2022-05-05 17:30:56 +00:00
from exclude import EXCLUDED_WORDS
2022-05-28 17:39:24 +00:00
# Einstellungen
2022-05-28 12:49:10 +00:00
SOURCE_DIR = Path('data')
2022-05-28 17:34:21 +00:00
SOURCE_FILENAME = 'index.txt'
2022-05-28 12:49:10 +00:00
OUTPUT_FILE = 'tags.json'
TAGS_PER_ARTICLE = 5
JSON_INDENT = 2
2022-06-06 10:17:02 +00:00
EXCLUDED_HTML_TAGS = {'code'}
2022-05-28 12:49:10 +00:00
2022-05-28 17:39:24 +00:00
# Wegen Performance vordefinierte Variablen
2022-05-28 17:22:30 +00:00
_UPPER_CHECK = re.compile(r'[A-Z]')
_LINK_PATTERN = re.compile(r'https?://\S+')
2022-05-05 16:01:10 +00:00
2022-05-05 17:30:56 +00:00
@dataclass
class Tag:
name: str
score: int
class FileScanner(HTMLParser):
def __init__(self, file: Path):
super().__init__()
self.file = file
self.texte = []
self.links = []
2022-06-06 10:17:02 +00:00
self._current_html_tag = None
2022-05-05 17:30:56 +00:00
def scan_file(self):
2022-05-28 17:22:30 +00:00
# Datei einlesen
2022-05-05 17:30:56 +00:00
content = read_file(self.file)
2022-06-08 16:48:04 +00:00
# HTMLParser aufrufen, um HTML-Syntax-Elemente zu entfernen.
2022-05-05 17:30:56 +00:00
self.feed(content)
words_with_usage = {}
words = []
for text in self.texte:
2022-05-28 17:22:30 +00:00
# Textteile in einzelne Wörter aufteilen
2022-06-06 15:29:22 +00:00
words += re.split(r'[ \n/]', text)
2022-05-28 17:39:24 +00:00
# Die Anzahl, der Wörter in der aktuellen Datei, auf der Konsole ausgeben
title = self.file.parent.name
print(f'\nFile {title} contains {len(words)} words')
2022-05-28 17:22:30 +00:00
# Titel in einzelne Wörter aufteilen
2022-05-28 17:39:24 +00:00
title_words = set(title.split('-'))
2022-05-05 17:30:56 +00:00
for word in words:
2022-05-28 17:22:30 +00:00
# Verschiedene Zeichen vom Anfang und Ende der Wörter entfernen.
2022-06-08 16:15:29 +00:00
tag_name = word.strip(".,:;!?\"'()-„“«» ")
2022-05-28 17:22:30 +00:00
# Leere Wörter ignorieren
2022-05-28 12:50:00 +00:00
if not tag_name:
2022-05-05 17:30:56 +00:00
continue
2022-05-28 17:22:30 +00:00
# Alle Buchstaben verkleinern, aber gleichzeitig originales Wort merken
2022-05-28 12:50:00 +00:00
word = tag_name.lower()
2022-05-28 17:22:30 +00:00
# Standard Bewertung für jedes Wort ist 10
2022-05-05 17:30:56 +00:00
score = 10
2022-05-28 17:22:30 +00:00
# Wörter, die in der Liste der ausgeschlossenen Wörter stehen, ignorieren
2022-05-05 17:30:56 +00:00
if word in EXCLUDED_WORDS:
2022-05-28 13:15:55 +00:00
continue
2022-05-28 17:22:30 +00:00
# Wörter, die nur aus Zahlen bestehen, ignorieren
2022-05-28 13:33:29 +00:00
if word.isdigit():
continue
2022-05-28 17:22:30 +00:00
# Die Bewertung von Wörtern, die im Titel vorkommen, deutlich verbessern.
2022-05-05 17:30:56 +00:00
if word in title_words:
score *= 4
2022-05-28 17:22:30 +00:00
# Die Bewertung von Wörtern, die kürzer oder gleich lang sind als 3 Buchstaben,
# entsprechend der Länge des Wortes verringern.
word_length = len(word)
if word_length <= 3:
score = int(score * word_length / 4)
2022-05-28 17:22:30 +00:00
# Die Anzahl der Großbuchstaben in dem originalen Wort zählen ...
upper_letters_count = len(_UPPER_CHECK.findall(tag_name))
# ... und die Bewertung entsprechen der Anzahl verbessern.
2022-06-08 16:16:05 +00:00
score += upper_letters_count * 10
2022-06-08 16:48:04 +00:00
# Die Bewertung leicht erhöhen, wenn ein Bindestrich im Wort enthalten ist.
2022-06-08 16:25:23 +00:00
if '-' in word:
score += 1
if word not in words_with_usage:
2022-06-08 16:48:04 +00:00
# Die Bewertung für das Wort speichern.
words_with_usage[word] = Tag(name=tag_name, score=score)
else:
2022-06-08 16:48:04 +00:00
# Wenn das Wort bereits eine Bewertung besitzt, werden die beiden Bewertungen zusammen gerechnet.
words_with_usage[word].score += score
link_words = []
for link in self.links:
# Eventuelle URL-codierte Zeichen in die eigentlichen Zeichen umwandeln. (z.B. %2F -> /)
link = unquote_plus(link)
# Link-Teile in einzelne Wörter aufteilen
2022-06-08 17:00:49 +00:00
link_words += re.split(r'[/\-_#.?&=]', link)
for link_word in link_words:
2022-06-08 16:48:04 +00:00
# Alle Buchstaben verkleinern
link_word = link_word.lower()
2022-06-08 16:48:04 +00:00
# Wenn ein Wort aus dem Text auch in einem Link vorkommt, wird die Bewertung erhöht.
# Somit kann verhindert werden, dass Link-Bestandteile als Tags vorgeschlagen werden (z.B. E7xcsFpR).
if link_word in words_with_usage:
2022-06-08 17:00:49 +00:00
words_with_usage[link_word].score += 10
2022-05-28 17:22:30 +00:00
# Die Wörter nach ihrer Bewertung sortieren
2022-05-28 12:49:10 +00:00
return sorted(words_with_usage.values(), key=lambda tag: tag.score, reverse=True)
2022-05-05 17:30:56 +00:00
def handle_starttag(self, tag, attrs):
2022-06-06 10:17:02 +00:00
self._current_html_tag = tag
2022-05-28 17:22:30 +00:00
# Die Links, die in den 'href' Attributen eines <a> HTML-Elements stehen, mit einbeziehen.
if tag != "a":
return
for attr_name, attr_value in attrs:
if attr_name == "href":
self.links.append(attr_value)
break
2022-05-05 17:30:56 +00:00
def handle_data(self, data):
2022-06-08 16:48:04 +00:00
# Den Inhalt des aktuellen HTML-Tags ignorieren, wenn dieser auf der Liste der ausgeschlossenen HTML-Tags steht.
if self._current_html_tag in EXCLUDED_HTML_TAGS:
return
2022-06-08 16:48:04 +00:00
# Links aus dem HTML-Text extrahieren und entfernen
data = _LINK_PATTERN.sub(self._link_replace, data)
# Den restlichen Text (ohne Links) innerhalb eines HTML-Elements mit einbeziehen.
self.texte.append(data)
2022-06-08 16:48:04 +00:00
def _link_replace(self, link_match):
self.links.append(link_match.group(0))
return ''
2022-05-05 16:01:10 +00:00
def display_tags(tags, max_tags):
2022-05-28 17:22:30 +00:00
# Die Ergebnisse auf der Konsole ausgeben.
for index, tag in enumerate(tags,):
if index >= max_tags:
break
2022-05-05 17:30:56 +00:00
print(f"Score: {tag.score:>3} Word: {tag.name}")
2022-05-05 16:01:10 +00:00
2022-05-28 12:49:10 +00:00
class CustomJsonEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, Tag):
return obj.name
return super().default(obj)
def write_tags(tags):
2022-05-28 17:22:30 +00:00
# Die Ergebnisse in JSON umwandeln.
2022-05-29 14:35:01 +00:00
content = json.dumps(tags, indent=JSON_INDENT, cls=CustomJsonEncoder, ensure_ascii=False)
2022-05-28 17:22:30 +00:00
# Das JSON in eine Datei schreiben.
2022-05-28 12:49:10 +00:00
with open(OUTPUT_FILE, 'w') as file:
file.write(content)
2022-05-05 16:01:10 +00:00
def read_file(file: Path) -> str:
2022-05-28 17:22:30 +00:00
# Eine Datei einlesen
2022-05-05 16:01:10 +00:00
with open(file, 'r') as file:
return file.read()
2022-05-28 12:49:10 +00:00
def main():
final_tags = {}
2022-05-28 17:34:21 +00:00
# Nach allen Quelldateien suchen
for file in SOURCE_DIR.glob(f'**/{SOURCE_FILENAME}'):
2022-05-28 17:22:30 +00:00
# Die Dateien, deren Ordner mit 'autosave-' beginnen, ignorieren.
2022-05-28 12:49:10 +00:00
title = file.parent.name
if title.startswith('autosave-'):
2022-05-28 12:08:28 +00:00
continue
2022-05-28 17:22:30 +00:00
# Die Datei analysieren
2022-05-05 17:30:56 +00:00
scanner = FileScanner(file)
2022-05-28 12:49:10 +00:00
tags = scanner.scan_file()
2022-05-28 17:22:30 +00:00
# Die Ergebnisse auf der Konsole ausgeben
display_tags(tags, max_tags=10)
2022-05-28 17:22:30 +00:00
# Die eingestellte Anzahl an Tags für die Ausgabedatei übernehmen, sofern vorhanden.
2022-05-28 12:49:10 +00:00
final_tags[title] = tags[:TAGS_PER_ARTICLE] if len(tags) > TAGS_PER_ARTICLE else tags
2022-05-28 17:22:30 +00:00
# Die Ausgabedatei schreiben
write_tags(final_tags)
2022-05-05 16:01:10 +00:00
if __name__ == '__main__':
main()