<jupyter_start><jupyter_text>Serlo Keywords<jupyter_code>import pandas as pd
import mysql.connector
import re

import json

from IPython.display import display, Markdown, HTML

db = mysql.connector.connect(
    host="localhost",
    user="root",
    password="<PASSWORD>",
    port="3306",
    database="serlo",
    charset="latin1"
)

subjects = [
    #'Psychologie',
    #'English for Spanish Natives',
    #'Medienbildung',
    'Biologie',
    #'Kommunikation',
    'Wirtschaft',
    'Mathe',
    'Chemie',
    #'Geschichte',
    #'Lerntipps',
    'Informatik',
    #'Musik',
    #'Testbereich',
    #'Deutsch als Fremdsprache',
    #'Englisch',
    #'Betriebswirtschaftslehre mit Rechnungswesen',
    'Geographie',
    #'Rechnungswesen',
    'Nachhaltigkeit',
    #'Politik',
    'Physik',
    #'Community',
    #'Latein'
]

def cached(func):
    cache = dict()
    
    def return_func(arg):
        if (arg in cache):
            return cache[arg]
        else:
            result = func(arg)
            cache[arg] = result
            return result
    
    return return_func

def query(sql):
    c = db.cursor()
    c.execute(sql)
    
    return c.fetchall()

def querySingleton(sql):
    return [ x[0] for x in query(sql) ]

def unique(iterator, key_func=lambda x: x["keyword"]):
    keys = set()
    
    for item in iterator:
        key = key_func(item)
        
        if key not in keys:
            keys.add(key)
            yield item
            
@cached
def getParent(termId):
    return querySingleton("""
        select parent_id from term_taxonomy where id = %s;
    """ % termId)[0]

def getTermName(termId):
    return querySingleton("""
        select term.name from term_taxonomy
        join term on term.id = term_taxonomy.term_id
        where term_taxonomy.id = %s;
    """ % termId)[0]

@cached
def getSubject(termId):
    parent = getParent(termId)
    grandparent = getParent(parent)
    
    if (parent == 106081):
        return getTermName(termId)
    
    return getSubject(parent) if grandparent != None else getTermName(termId)

def getSerloKeywords():
    df = pd.read_sql("""
        select term_taxonomy_entity.term_taxonomy_id as termId,
               entity_revision_field.value as title, uuid.id as uuid,
               instance.subdomain as language, "schule" as scope
        from entity
        join entity_revision_field on entity_revision_field.entity_revision_id = entity.current_revision_id
        join type on type.id = entity.type_id
        join uuid on uuid.id = entity.id
        join term_taxonomy_entity on term_taxonomy_entity.entity_id  = entity.id
        join instance on instance.id = entity.instance_id
        where instance.subdomain = "de"
            and uuid.trashed = 0
            and (type.name = "article" or type.name = "course")
            and (entity_revision_field.field = "title" or entity_revision_field.field = "meta_title");
    """, db)
    df["subject"] = df["termId"].apply(getSubject)
    df["title"] = df["title"].apply(fix_encoding)
    df.drop(df[~df["subject"].isin(subjects)].index, inplace=True)
    df["keyword"] = df["title"].apply(normalize)
    df.drop(df[df["keyword"] == ""].index, inplace=True)
    
    return df[["title", "keyword", "uuid", "subject", "language", "scope"]].drop_duplicates(["keyword", "subject"])
    
    keywords = set((normalize(keyword) for keyword in df["title"]))
    
    return [keyword for keyword in keywords if len(keyword) > 0]

def fix_encoding(title):
    return bytes(title, "latin1").decode("utf8")

def normalize(title):
    title = title.replace(" - Lernen mit Serlo!", "").lower()
    
    words = [word for word in re.split("\W+", title) if isKeyword(word)]
    #words = list(words)
    #words.sort()
    
    if (len(words) > 2):
        return ""
    
    return " ".join(words)

words = set()

def isKeyword(word):
    if len(word) <= 3 and word not in ["web", "key", "dna", "ggt"]:
        words.add(word)
        return False
    if word in ["eine", "kurs", "eines", "einer", "oder", "sind", "test", "wichtigsten"]:
        return False
    return True

pd.set_option('display.max_rows', None)

serlo_keywords = getSerloKeywords()
display(serlo_keywords)<jupyter_output><empty_output><jupyter_text>Keywords auf Wikibooks<jupyter_code>import requests
import json

def cached(func):
    cache = {}
    
    def cached_func(*args, **kwargs):
        key = json.dumps([args,kwargs])
        
        if key not in cache:
            cache[key] = func(*args, **kwargs)
        
        return cache[key]
    
    return cached_func

@cached
def list_wikibooks_pages(prefix, params={}):
    result = requests.get("https://de.wikibooks.org/w/api.php", {
        "action": "query",
        "format": "json",
        "list": "allpages",
        "aplimit": 500,
        "apprefix": prefix,
        **params
    }).json()
    titles = [r["title"] for r in result["query"]["allpages"]]
    
    if "continue" in result:
        titles += list_wikibooks_pages(prefix, result["continue"])
    
    return titles

@cached
def is_redirect(title):
    result = requests.get("https://de.wikibooks.org/w/api.php", {
        "action": "query",
        "format": "json",
        "prop": "info",
        "titles": title,
    }).json()
    
    return "redirect" in list(result["query"]["pages"].values())[0]
import json

blacklist = [
 'anwendungsbeispiele',
 'autor*innenübersicht',
 'banner',
 'beobachtungsliste',
 'best practices für entscheidungsprozesse',
 'buchanfänge',
 'eine persönliche spielwiese erstellen',
 'erstellen von medien',
 'formatierungskonventionen',
 'formelumgebung',
 'grundlagen der mathematik: zusammenfassung',
 'grundlegende formatierungen',
 'grundvorstellung finden',
 'hilfreiche links',
 'hinweise zu lizenzen',
 'hochladen und einbinden von medien',
 'inhalte doppelt einbinden',
 'interaktion mit der community',
 'kopiere uns',
 'linter',
 'liste mathematischer symbole',
 'mathematische konventionen',
 'mitmachen für (nicht-)freaks',
 'phabricator',
 'print specification',
 'projektbeschreibung',
 'sitemap',
 'spenden',
 'spickzettel für mathe-formeln',
 'sprachliche konventionen',
 'umfragen',
 'unsere didaktische vision',
 'unterstütze uns',
 'volltextsuche',
 'wörterbuch',
 'zitieren und quellenangaben',
 'zugriffszahlen messen',
 'über das projekt',
 'guidelines for translation',
 'translating articles',
"vorlage", "archiv", "aufgaben", "buchanfang",
                "abstellraum", "hilfe", "exercises", "analysis 2",
                "artikel", "mathe für nicht-freaks", "serlo",
"mitmachen für nicht-freaks"]


predefined_keywords = {
    "ableitung und lokale extrema": ["lokale extrema", "lokale extrema ableitung"],
    "aufzählende und beschreibende mengenschreibweise": ["aufzählende mengenschreibweise", "beschreibende mengenschreibweise"],
    "beispiele und eigenschaften von folgen": ["beispiele folgen", "eigenschaften folgen"],
    "beschränkte reihen und konvergenz": ["beschränkte reihen konvergenz", "beschränke reihen"],
    "direkter und indirekter beweis": ["direkter beweis", "indirekter beweis"],
    "eigenschaften des sinus und kosinus": ["eigenschaften sinus", "eigenschaften kosinus"],
    "explizite und rekursive bildungsgesetze für folgen": ["explizite bildungsgesetze folgen", "rekursive bildungsgesetze folgen"],
    "exponential- und logarithmusfunktion in den komplexen zahlen": [
        "exponentialfunktion komplexe zahlen", "logarithmusfunktion komplexe zahlen"
    ],
    "folgenkriterium der stetigkeit: folgenstetigkeit": [
        "folgenkriterium stetigkeit", "folgenstetigkeit"
    ],
    "grenzwert: konvergenz und divergenz": [
        "grenzwert", "konvergenz", "divergenz"
    ],
    "grenzwertsätze: grenzwert von folgen berechnen": [
        "grenzwertsätze", "grenzwert berechnen"
    ],
    "hauptsatz der differential- und integralrechnung": [
        "hauptsatz differentialrechnung", "hauptsatz integralrechnung", "hauptsatz der differential- und integralrechnung"
    ],
    "herleitung und definition der exponentialfunktion": [
        "herleitung exponentialfunktion", "definition exponentialfunktion"
    ],
    "häufungspunkt und berührpunkt einer menge": [
        "häufungspunkt menge", "berührpunkt menge"
    ],
    "intuition hinter isomorphiesatz und dimensionsformel": [
        "isomorphiesatz", "dimensionsformel"
    ],
    "kern einer linearen abbildung": [ "kern lineare abbildung" ],
    "komplexen zahlen: definition": ["definition komplexe zahlen"],
    "konvergenz und divergenz beweisen": ["konvergenz beweis", "divergenz beweis"],
    "konstanzkriterium: zusammenhang zwischen konstanz einer funktion und ihrer ableitung": [
        "konstanzkriterium", "konstante funktion ableitung"
    ],
    "konvergenz und divergenz einer reihe beweisen: konvergenzkriterien": [
        "konvergenz reihe", "divergenz reihe", "konvergenzkriterien"
    ],
    "leibniz-formel der determinante": ["leibniz-formel"],
    "lineare abbildung und darstellende matrix": [
        "darstellende matrix", "darstellende matrix lineare abbildung"
    ],
    "mengendiagramme: euler- und venn-diagramm": [
        "mengendiagramm", "eulerdiagramm", "venn-diagramm"
    ],
    "mengenlehre: menge": ["mengenlehre", "menge"],
    "monotoniekriterium: zusammenhang zwischen monotonie und ableitung einer funktion": [
        "monotoniekriterium", "monotonie ableitung"
    ],
    "notwendige und hinreichende bedingungen": [
        "notwendige bedingung", "hinreichende bedingung"
    ],
    "polarform bzw. polardarstellung komplexer zahlen": [
        "polarform", "polardarstellung"
    ],
    "satz vom minimum und maximum": ["satz maximum", "satz minimum"],
    "eigenschaften supremum infimum": [
        "eigenschaften supremum", "eigenschaften infimum"
    ],
    "supremum und infimum bestimmen und beweisen": [
        "beweis supremum", "beweis infimum", "supremum bestimmen", "infimum bestimmen"
    ],
    "uneigentliches supremum und infimum": [
        "uneigentliches supremum", "uneigentliches infimum"
    ],
    "vektoren vektorräume schule": [
        "vektoren schule", "vektorräume schule"
    ],
    "vektorraum: summe von unterräumen": ["summe vektorraum", "summe unterräume"],
    "vereinigung und durchschnitt von vektorräumen": ["vereinigung vektorraum", "durschschnitt vektorraum"],
    "vektorraum: summen und innere direkte summen von mehr als zwei unterräumen": [
        "direkte summe vektorraum", "indirekte summe vektorraum"
    ],
    "derivation and definition of the exponential series": [
        "derivation exponential series", "definition exponential series"
    ],
    "derivative and local extrema": [
        "local extrema", "derivative local extrema"
    ],
    "examples and properties of sequences": [
        "sequence example", "sequence properties"
    ],
    "exp and log functions for complex numbers": [
        "exp complex numbers", "log complex numbers"
    ],
    "explicit and recursive description": [
        "explicit description sequence", "recursive description sequence"
    ],
    "how to prove convergence and divergence": [
        "prove convergence", "prove divergence"
    ],
    "prove existence supremum infimum": [
        "prove supremum", "prove infimum"
    ],
    "introduction and motivation": [],
    "properties of supremum and infimum": ["properties supremum", "properties infimum"],
    "telescoping sums and series": ["telescoping sum", "telescoping series"]
}

def get_wb_keywords():
    titles = (list_wikibooks_pages("Mathe für Nicht-Freaks") +
              list_wikibooks_pages("Serlo"))
    titles = [ normalize(t) for t in titles if not is_redirect(t)]
    titles = [ t for t in titles if is_article(t)]
    
    for title in titles:
        yield from get_keyword_entries(title)

def is_article(title):
    for word in blacklist:
        if word in title:
            return False
    
    for word in ["wer", "was", "wie", "what", "why", "wozu"]:
        if title.startswith(word):
            return False
    
    return True

def normalize(title):
    title = remove_prefix(title.lower()).strip()
    title = title.replace("ableitungsregeln", "ableitung").replace("(", "").replace(")", "")
    
    return title

def remove_prefix(title):
    return title.replace("mathe für nicht-freaks: ", "").replace("serlo: en: ", "")

def list_keywords(title):
    if title in predefined_keywords:
        yield from predefined_keywords[title]
    else:
        suffix = ""

        if ":" in title:
            parts = title.split(":")

            title = parts[-1]
            suffixes = parts[:-1]
            suffixes.reverse()
            suffix = " ".join(suffixes)

        kexwords = title.split(",")
        keywords = sum((x.split(" und ") for x in kexwords), [])
        keywords = sum((x.split(" and ") for x in kexwords), [])

        for keyword in keywords:
            keyword = (keyword.strip() + " " + suffix).strip()

            words = [normalize_word(w) for w in keyword.split(" ") if is_keyword(w)]
            yield " ".join(words)

def get_keyword_entries(title):
    keywords = list_keywords(title)
    language = "en" if title.startswith("Serlo") else "de"
    
    for keyword in keywords:
        yield {"keyword": keyword, "language": language, "scope": "hochschule",
               "subject": "Mathe", "title": title}
                   
def is_keyword(word):
    if word in ["1", "2", "lim", "sup", "inf", "map"]:
        return True
    if len(word) <= 3:
        return False
    if len(word) == 4 and word.startswith("ein"):
        return False
    if word in ["einer", "eines", "führen", "besonderer", "einführung", "bzw.", "zwischen"]:
        return False
    
    return True

def normalize_word(word):
    if word == "integrale":
        return "integral"
    
    return word

def display_keywords(keywords):
    display(Markdown("### Liste aller Keywords (Anzahl = %s):" % len(keywords)))
    
    keyword_by_title = {}
    
    for keyword in keywords:
        if keyword["title"] not in keyword_by_title:
            keyword_by_title[keyword["title"]] = []
            
        keyword_by_title[keyword["title"]].append(keyword["keyword"])
    
    for title, keywords in keyword_by_title.items():
        display(Markdown("**=== %s ===**" % title))
    
        for keyword in keywords:
            display(Markdown(keyword))

wb_keywords = list(unique(get_wb_keywords()))
display_keywords(wb_keywords)<jupyter_output><empty_output><jupyter_text>Speichern der Keywords<jupyter_code>import json

with open("/home/kulla/workspace/serlo/seo/keywords.json", "w") as fd:
    keywords = list(serlo_keywords.to_dict("records")) + wb_keywords
    
    json.dump(keywords, fd)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.12892953929539294, 'fraction_numerical': 0.002100271002710027, 'mean_word_length': 3.224670864338867, 'pattern_counts': {'":': 62, '<': 16, '<?xml version=': 0, '>': 16, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '3', 'dir': 'jupyter-structured-clean-dedup', 'id': '604050', 'license': 'CC0-1.0', 'n_tokens_mistral': 4865, 'n_tokens_neox': 4859, 'n_words': 1176, 'path': '2021/2021-02-08 Keywords of Serlo.ipynb', 'repo_name': 'kulla/serlo-evaluations', 'size': '14756'}