Benutzer:Klexibot/Skripte: Unterschied zwischen den Versionen

Aus Klexikon – das Kinderlexikon
(Lagebeschreibung jetzt mit Kontinent, Blacklist um Schottland und Wales erweitert, + Kleinigkeiten)
Markierung: 2017-Quelltext-Bearbeitung
 
(18 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt)
Zeile 1: Zeile 1:
== Skript zur Aktualisierung der Länder-Infoboxen ==
__TOC__
 
== Aktualisierung der Länder-Infoboxen ==
=== Pywikibot-Skript ===
<syntaxhighlight lang="python">
<syntaxhighlight lang="python">
import requests
import requests
Zeile 5: Zeile 8:
import mwparserfromhell
import mwparserfromhell
import locale
import locale
import datetime
# Testlauf: Ausgabe des Logbuchs in der Konsole ohne Änderungen am Klexikon
dryrun_answer = input ('Nur Testlauf (j/n)? ')
dryrun = dryrun_answer != 'n'


# Verwende deutsches Zahlenformat mit Tausenderpunkt und Dezimalkomma
# Verwende deutsches Zahlenformat mit Tausenderpunkt und Dezimalkomma
locale.setlocale(locale.LC_NUMERIC, 'german')
locale.setlocale(locale.LC_ALL, 'german')


# Zuordnung von Wikidata-Abfrage-Feldern zu Vorlage-Parametern
# Zuordnung von Wikidata-Abfrage-Feldern zu Vorlage-Parametern
fieldmap = {
fieldmap = {
    'Name': 'itemLabel',
'Name': 'itemLabel',
    'Flagge': 'safeFlaggenDatei',
'Flagge': 'safeFlaggenDatei',
    'Hauptstadt': 'hauptstaedte',
'Hauptstadt': 'hauptstaedte',
    'Amtssprache': 'amtssprachen',
'Amtssprache': 'amtssprachen',
    'Staatsoberhaupt': 'oberhaeupter',
'Staatsoberhaupt': 'oberhaeupter',
    'Einwohnerzahl': 'max_ew_in_mio',
'Einwohnerzahl': 'max_ew_in_mio',
    'Fläche': 'max_flaeche_rund',
'Fläche': 'max_flaeche_rund',
    'Lagekarte': 'safeKartenDatei',
'Lagekarte': 'safeKartenDatei',
    'Lagebeschreibung': 'kontinente'
'Lagebeschreibung': 'kontinente'
}
}


# Legt fest, welche Länder-Artikel bzw. Vorlagen-Parameter Klexibot nicht ändern soll
# Legt fest, welche Länder-Artikel bzw. Vorlagen-Parameter Klexibot nicht ändern soll
blacklist = {
blacklist = {
    'Eidgenossenschaft',
'Australien:Staatsoberhaupt',
    'Deutsches Kaiserreich',
'Eidgenossenschaft',
    'Schottland',
'Deutsches_Kaiserreich',
    'Wales'
'Schottland',
'England',
'Eswatini:Name',
'Schweiz:Amtssprache',
'St._Kitts_und_Nevis:Staatsoberhaupt',
'St._Lucia:Staatsoberhaupt',
'St._Vincent_und_die_Grenadinen:Staatsoberhaupt',
'Vatikan:Fläche',
'Vatikan:Hauptstadt',
'Wales'
}
}


# Parameter aus dieser Liste werden nur gefüllt, falls bisher kein Wert enthalten war
# Parameter aus dieser Liste werden nur gefüllt, falls bisher kein Wert enthalten war
preserved_values = {
preserved_values = {
    'Lagekarte',
'Lagekarte',
    'Lagebeschreibung'
'Lagebeschreibung'
}
}


Zeile 41: Zeile 58:
# Lade Wikidata-Tabelle und speichere sie in "data"-Variable
# Lade Wikidata-Tabelle und speichere sie in "data"-Variable
url = 'https://query.wikidata.org/sparql'
url = 'https://query.wikidata.org/sparql'
with open('infobox_data_query.rq', 'r') as query_file: qry = query_file.read()
with open('infobox_data_query.rq', 'r', encoding="utf-8") as query_file: qry = query_file.read()
r = requests.get(url, params = {'format': 'json', 'query': qry }, headers = {
r = requests.get(url, params = {'format': 'json', 'query': qry }, headers = {
    'User-Agent':f'{requests.utils.default_headers()["User-Agent"]} (Klexikon bot by User:Tkarcher)'})
'User-Agent':f'{requests.utils.default_headers()["User-Agent"]} (Klexikon bot by User:Tkarcher)'})
data = r.json()
data = r.json()
# Initialisiere Logbuch-Eintrag
logheader = f'\n== Logbuch vom {datetime.datetime.now().strftime("%c")} ==\n'
newloghead = '=== Neu angelegte Infoboxen ===\n'
newlogdata = ''
chgloghead = '=== Änderungen an bestehenden Infoboxen ===\n' + \
'{| class="wikitable sortable"\n! Vorlage\n! Eigenschaft\n! Alter Wert\n! Neuer Wert\n'
chglogdata = ''
chglogfoot = '|}\n'


# Gehe Wikidata-Tabelle Zeile für Zeile durch
# Gehe Wikidata-Tabelle Zeile für Zeile durch
for item in data['results']['bindings']:
for item in data['results']['bindings']:
    title = item['titel_im_klexikon']['value']
title = item['titel_im_klexikon']['value']


    # Ignoriere Artikel, die in der Ausschlußliste enthalten sind
# Ignoriere Artikel, die in der Ausschlußliste enthalten sind
    if title not in blacklist:
if title not in blacklist:


        # Öffne Vorlagen-Seite oder lege sie neu an
# Öffne Vorlagen-Seite oder lege sie neu an
        page = pywikibot.Page(site, f'Vorlage:Infobox_{title}')
page = pywikibot.Page(site, f'Vorlage:Infobox_{title}')
        code = mwparserfromhell.parse(page.text)
code = mwparserfromhell.parse(page.text)


        # Ersetze Seiteninhalt mit {{Infobox Land}}, falls nicht vorhanden
# Ersetze Seiteninhalt mit {{Infobox Land}}, falls nicht vorhanden
        if not code.filter_templates(matches = lambda n: n.name.matches ('Infobox Land')):
if not code.filter_templates(matches = lambda n: n.name.matches ('Infobox Land')):
            code = mwparserfromhell.parse('{{Infobox Land\n}}')
code = mwparserfromhell.parse('{{Infobox Land\n}}')
newlogdata += f'* [[Vorlage:Infobox {title}]]\n'


        # Fülle alle Vorlagen-Felder
# Fülle alle Vorlagen-Felder
        for prop, field in fieldmap.items():
for prop, field in fieldmap.items():


            # Ignoriere Vorlagen-Parameter, die leer oder in der Ausschlußliste enthalten sind
# Ignoriere Vorlagen-Parameter, die leer oder in der Ausschlußliste enthalten sind
            if field in item and f'{title}:{prop}' not in blacklist:
if field in item and f'{title}:{prop}' not in blacklist:


                # Ignoriere auch Parameter, die nicht überschrieben werden sollen
# Ignoriere auch Parameter, die nicht überschrieben werden sollen
                if not (
if not (
                    prop in preserved_values and
prop in preserved_values and
                    code.filter_templates()[0].has(prop) and
code.filter_templates()[0].has(prop) and
                    code.filter_templates()[0].get(prop).value != ''):
code.filter_templates()[0].get(prop).value.strip() != ''):


                    if prop == 'Einwohnerzahl':
oldval = code.filter_templates()[0].get(prop).value.strip() if code.filter_templates()[0].has(prop) else ''
                        ew = float(item[field]['value'])
val = item[field]['value']
                        if ew > 1:
if prop == 'Einwohnerzahl':
                            val = f'etwa {locale.format_string("%.0f", ew, grouping = True)} [[Million]]en'
ew = float(val)
                        else:
if round(ew) > 1:
                            val = f'etwa {locale.format_string("%.0f", ew * 1000000, grouping = True)}'
val = f'etwa {locale.format_string("%.0f", ew, grouping = True)} [[Million]]en'
                    elif prop == 'Fläche':
elif ew > 0.9:
                        val = locale.format_string('%i', int(item[field]['value']), grouping = True)
val = f'etwa 1 [[Million]]'
                        val = f'etwa {val} [[Meter|Quadratkilometer]]'
else:
                    elif prop == 'Staatsoberhaupt':
val = f'etwa {locale.format_string("%.0f", ew * 1000000, grouping = True)}'
                        if 'oberhaupt_bezeichnung' in item:
elif prop == 'Fläche':
                            val = item['oberhaupt_bezeichnung']['value'] + ' ' + item[field]['value']
val = f'etwa {locale.format_string("%i", int(val), grouping = True)} [[Meter|Quadratkilometer]]'
                        else:
elif prop == 'Staatsoberhaupt':
                            val = item[field]['value']
if 'oberhaupt_bezeichnung' in item:
                    elif prop == 'Lagebeschreibung':
val = f'{item["oberhaupt_bezeichnung"]["value"]} {val}'
                        val = f'Wo das Land in {item[field]["value"]} liegt'
elif prop == 'Lagebeschreibung':
                    else: val = item[field]['value']
val = f'Wo das Land in {val} liegt'
                    code.filter_templates()[0].add(
                        f' {prop} ', f' {val}\n', preserve_spacing = False)


        # Speichere die Änderungen, falls vorhanden, und verschiebe
# Speichere die Änderung nur, wenn sie aus mehr besteht als
        # Artikel ggf. zurück in Kategorie "ungeprüfte Infobox"
# einer geänderten Sortierreihenfolge in der Auslistung
        if page.text != str(code):
# (SPARQL 1.1 erlaubt kein ORDER BY in GROUP_CONCATs)
if sorted (val) != sorted (oldval):
code.filter_templates()[0].add (f' {prop} ', f' {val}\n', preserve_spacing = False)
if not newlogdata.endswith (f'* [[Vorlage:Infobox {title}]]\n'):
chglogdata += f'|- \n| [[Vorlage:Infobox {title}]]\n| {prop}\n| {oldval}\n| {val}\n'


            # Speichere Vorlage
# Speichere die Änderungen, falls vorhanden, und verschiebe
            page.text = str(code)
# Artikel ggf. zurück in Kategorie "ungeprüfte Infobox"
            page.save('Automatische Anlage der Länder-Infobox (erste 12 Länder)')
if (page.text != str(code)) and not dryrun:


            # Öffne Länderartikel
# Speichere Vorlage
            page = pywikibot.Page(site, title)
page.text = str(code)
            code = mwparserfromhell.parse(page.text)
page.save('Automatische Anlage der Länder-Infobox')


            # Binde Länder-Infobox ein, falls noch nicht geschehen
# Öffne Länderartikel
            # Mit "_" und " " wegen https://github.com/earwig/mwparserfromhell/issues/216
page = pywikibot.Page(site, title)
            if not code.filter_templates(matches = lambda n:
code = mwparserfromhell.parse(page.text)
                n.name.matches ({f'Infobox_{title}', f'Infobox {title}'})):
                code = mwparserfromhell.parse(f'{{{{Infobox_{title}}}}}\n{str(code)}')


            # Lösche [[Kategorie:Artikel mit geprüfter Infobox]], falls vorhanden
# Binde Länder-Infobox ein, falls noch nicht geschehen
            if '[[Kategorie:Artikel mit geprüfter Infobox]]' in str(code):
# Mit "_" und " " wegen https://github.com/earwig/mwparserfromhell/issues/216
                code.remove ('[[Kategorie:Artikel mit geprüfter Infobox]]')
if not code.filter_templates(matches = lambda n:
n.name.matches ({f'Infobox_{title}', f'Infobox {title}'})):
code = mwparserfromhell.parse(f'{{{{Infobox_{title}}}}}\n{str(code)}')
 
# Lösche [[Kategorie:Artikel mit geprüfter Infobox]], falls vorhanden
if '[[Kategorie:Artikel mit geprüfter Infobox]]' in str(code):
code.remove ('[[Kategorie:Artikel mit geprüfter Infobox]]')
 
# Füge [[Kategorie:Artikel mit ungeprüfter Infobox]] ein, falls noch nicht geschehen
if '[[Kategorie:Artikel mit ungeprüfter Infobox]]' not in str(code):
code.append ('\n[[Kategorie:Artikel mit ungeprüfter Infobox]]')
 
# Speichere Länderartikel
page.text = str(code)
page.save('Automatische Einbindung der Länder-Infobox')
 
# Dokumentiere Änderungen in Logbuch
log = logheader \
+ newloghead + (newlogdata if newlogdata != '' else 'Es wurden keine neuen Infoboxen angelegt.\n') \
+ chgloghead + (chglogdata if chglogdata != '' else '|- \n| colspan=4 | Es wurden keine bestehenden Daten geändert.\n') + chglogfoot
 
if dryrun:
print (log)
else:
page = pywikibot.Page(site, 'Benutzer:Klexibot/Logbuch')
page.text += log
page.save('Logbuch aktualisiert')
</syntaxhighlight>
 
=== Wikidata-SPARQL-Abfrage ===
 
<syntaxhighlight lang="sparql">
#Länder-Infoboxen im Klexikon
SELECT
?item
?itemLabel
(MAX(?ew_in_millionen) AS ?max_ew_in_mio)
(GROUP_CONCAT(DISTINCT (IF(BOUND(?asklextitel),CONCAT('[[',?asklextitel,'|',?amtssprachenLabel,']]'),?amtssprachenLabel)); SEPARATOR=", ") AS ?amtssprachen)
(MAX(?flaeche_rund) AS ?max_flaeche_rund)
(GROUP_CONCAT(DISTINCT (IF(BOUND(?hsklextitel),CONCAT('[[',REPLACE(?hsklextitel, "_", " "),']]'),?hauptstadtLabel)); SEPARATOR=", ") AS ?hauptstaedte)
(GROUP_CONCAT(DISTINCT (COALESCE (?oberhaupt_titel_ausnahme, ?oberhaupt_bezLabel)); SEPARATOR=", ") AS ?oberhaupt_bezeichnung)
(GROUP_CONCAT(DISTINCT (IF(BOUND(?ohklextitel),CONCAT('[[',REPLACE(?ohklextitel, "_", " "),']]'),?oberhauptLabel)); SEPARATOR=", ") AS ?oberhaeupter)
(GROUP_CONCAT(DISTINCT (CONCAT('[[',?kontinentLabel,']]')); SEPARATOR=" und ") AS ?kontinente)
?safeFlaggenDatei
?safeKartenDatei
?titel_im_klexikon
WHERE
{
?item p:P31 [ps:P31 wd:Q6256]. # Finde alle Länder
?item wdt:P6573 ?titel_im_klexikon. # die es im Klexikon gibt
?item wdt:P1082 ?ewzahl. # und zeige ihre Einwohnerzahl
BIND (STR(ROUND(?ewzahl / 1000) / 1000) AS ?ew_in_millionen) # in Millionen
?item wdt:P2046 ?flaeche. # ihre Fläche
BIND (STR(IF(?flaeche > 9999, ROUND(?flaeche / 1000) * 1000,IF(?flaeche > 999, ROUND(?flaeche / 100) * 100,ROUND(?flaeche)))) AS ?flaeche_rund) # gerundet
?item wdt:P37 ?amtssprache. # ihre Amtssprache
# ?article schema:about ?amtssprache. # Zeige aber nur die bekannteren Amtssprachen, d.h. solche
# ?article schema:inLanguage "de" . # für die ein deutschsprachiger Wikipedia-Artikel existiert
OPTIONAL {?amtssprache wdt:P6573 ?asklextitel}
OPTIONAL { # Optional wg. Vatikanstadt
  ?item wdt:P36 ?hauptstadt. # ihre Hauptstädte
  OPTIONAL {?hauptstadt wdt:P6573 ?hsklextitel}
}
?item wdt:P35 ?oberhaupt. # das Staatsoberhaupt
OPTIONAL {?oberhaupt wdt:P6573 ?ohklextitel}
OPTIONAL {
  ?item wdt:P1906/wdt:P279* ?oberhaupt_titel.
  FILTER (?oberhaupt_titel IN ( # einschränken auf bekannte / kinderverständliche Titel
  wd:Q30461, # Präsident
  wd:Q2304859, # Souverän
  wd:Q7645115, # Oberster Führer
  wd:Q166382, # Emir
  wd:Q683337, # Kofürst von Andorra
  wd:Q258045, # Capitano Reggente
  wd:Q844944 # Präsidium von Bosnien und Herzegowina
  ))
  OPTIONAL {
  ?oberhaupt_titel wdt:P2521 ?oberhaupt_titel_weiblich.
  FILTER (LANG(?oberhaupt_titel_weiblich) = "de").
  }
}
OPTIONAL {?oberhaupt wdt:P21 ?geschlecht.}
BIND (IF (BOUND(?oberhaupt_titel_weiblich) && BOUND(?geschlecht) && (?geschlecht = wd:Q6581072),
  IF(STR(?oberhaupt_titel_weiblich) = "Souveränin", wd:Q16511993, ?oberhaupt_titel_weiblich),
  IF(?oberhaupt_titel = wd:Q2304859, wd:Q12097, ?oberhaupt_titel)) AS ?oberhaupt_bez
)
OPTIONAL { # Länderspezifische Titel
  VALUES (?item ?oberhaupt_titel_ausnahme) {
  (wd:Q794 "Religionsführer") # Iran
  (wd:Q17 "[[Kaiser]]") # Japan
  (wd:Q40 "Bundespräsident") # Österreich
  (wd:Q183 "Bundespräsident") # Deutschland
  (wd:Q32 "Großherzog") # Luxemburg
  (wd:Q237 "[[Papst]]") # Vatikan
  (wd:Q347 "Fürst") # Liechtenstein
  (wd:Q683 "Häuptling") #Samoa
  }
}
?item wdt:P41 ?flagge.
BIND(REPLACE(wikibase:decodeUri(STR(?flagge)), "http://commons.wikimedia.org/wiki/Special:FilePath/", "") AS ?flaggenDatei).
BIND(REPLACE(?flaggenDatei, " ", "_") AS ?safeFlaggenDatei)
?item wdt:P30 ?kontinent_alt.
OPTIONAL {
  ?item wdt:P361*/wdt:P706*/wdt:P361* wd:Q27611
  BIND (wd:Q29876 AS ?mittelamerika)
}
BIND (COALESCE(?mittelamerika, ?kontinent_alt) AS ?kontinent)
 
OPTIONAL { # Lagekarte: Grünes Land auf dem Globus
  ?item p:P242 ?p_karte1.
  ?p_karte1 pq:P195 wd:Q21167586.
  ?p_karte1 ps:P242 ?karte1.
}
OPTIONAL { # Lagekarte: Rotes Land auf dem Globus
  ?item p:P242 ?p_karte2.
  ?p_karte2 pq:P195 wd:Q47008743.
  ?p_karte2 ps:P242 ?karte2.
}
# Nimm Globus, falls vorhanden, sonst 2:1-Ausschnitt
BIND(REPLACE(wikibase:decodeUri(STR(COALESCE(?karte1, ?karte2))), "http://commons.wikimedia.org/wiki/Special:FilePath/", "") AS ?kartenDatei).
BIND(REPLACE(?kartenDatei, " ", "_") AS ?safeKartenDatei)
 
SERVICE wikibase:label {
  bd:serviceParam wikibase:language "[AUTO_LANGUAGE],de".
  ?item rdfs:label ?itemLabel.
  ?amtssprache rdfs:label ?amtssprachenLabel.
  ?hauptstadt rdfs:label ?hauptstadtLabel.
  ?oberhaupt_bez rdfs:label ?oberhaupt_bezLabel.
  ?oberhaupt rdfs:label ?oberhauptLabel.
  ?kontinent rdfs:label ?kontinentLabel.
}
}
GROUP BY ?item ?itemLabel ?safeFlaggenDatei ?safeKartenDatei ?titel_im_klexikon
ORDER BY ?itemLabel
</syntaxhighlight>
 
== Validierungen / Qualitätssicherung ==
=== Artikel mit Klammerfehlern ===
<syntaxhighlight lang="python">
import pywikibot
 
opening = '{['
closing = '}]'
mapping = dict(('{}', '[]'))
pages_with_unmatched_brackets = []
 
site = pywikibot.Site()
 
for page in site.allpages(namespace = ""):
    queue = []
    if page.title() != 'ß': # Komische Sache! Die Seite gibt's, aber dann irgendwie doch nicht...
        for letter in page.text:
            if letter in opening:
                queue.append(mapping[letter])
            elif letter in closing:
                if not queue or letter != queue.pop():
                    pages_with_unmatched_brackets.append(page.title())
        if queue: pages_with_unmatched_brackets.append(page.title())
 
print ('[['+ ']], [['.join(list(dict.fromkeys(pages_with_unmatched_brackets))) + ']]')
</syntaxhighlight>
 
=== Artikel mit Links in Dateinamen ===
<syntaxhighlight lang="python">
import re
import pywikibot
 
pattern_1 = re.compile(r'atei\:[^\|]*\[\[')
pattern_2 = re.compile(r'allery[\s\S]+\n[^\|]*\[\[[\s\S]+allery')
pages_with_links_in_filename = []
 
site = pywikibot.Site()
 
for page in site.allpages(namespace = ""):
    queue = []
    if page.title() != 'ß': # Komische Sache! Die Seite gibt's, aber dann irgendwie doch nicht...
        if str(pattern_1.search(page.text)) != 'None' or str(pattern_2.search(page.text)) != 'None':
            pages_with_links_in_filename.append(page.title())
 
print ('[['+ ']], [['.join(list(dict.fromkeys(pages_with_links_in_filename))) + ']]')
</syntaxhighlight>
 
=== Wörter mit Wikilink im zweiten Teil ===
<syntaxhighlight lang="python">
import re
import pywikibot
 
pattern_1 = re.compile(r'\W(\w+\[\[[^\]]+\]\])')
matches = ''
 
site = pywikibot.Site()
 
for page in site.allpages(namespace = ""):
  if page.title() != 'ß': # Komische Sache! Die Seite gibt's, aber dann irgendwie doch nicht...
        match = pattern_1.search(page.text)
        if match: matches += '* [[' + page.title() + ']]: "' + match.group(1) + '"\n'
 
print (matches)
</syntaxhighlight>
 
=== Artikel mit 3 oder mehr Bildern in der Einleitung ===
<syntaxhighlight lang="python">
import re
import pywikibot
 
pattern_5 = re.compile(r'^\[\[Datei\:.+\n?\[\[Datei\:.+\n?\[\[Datei\:.+\n?\[\[Datei\:.+\n?\[\[Datei\:')
pattern_4 = re.compile(r'^\[\[Datei\:.+\n?\[\[Datei\:.+\n?\[\[Datei\:.+\n?\[\[Datei\:')
pattern_3 = re.compile(r'^\[\[Datei\:.+\n?\[\[Datei\:.+\n?\[\[Datei\:')
matches = []
 
site = pywikibot.Site()
 
for page in site.allpages(namespace = ""):
  if page.title() != 'ß':
        if pattern_5.search(page.text):
            matches.append("| [[" + page.title() + "]] || 5\n")
        elif pattern_4.search(page.text):
            matches.append("| [[" + page.title() + "]] || 4\n")
        elif pattern_3.search(page.text):
            matches.append("| [[" + page.title() + "]] || 3\n")
 
print ('{| class="wikitable sortable"\n! Artikel !! Bilder zu Beginn\n|-\n'+ '|-\n'.join(matches) + '|}')
</syntaxhighlight>
 
== Statistik ==
 
=== Anzahl neu erstellter Artikel ===
<syntaxhighlight lang="python">
import pywikibot


            # Füge [[Kategorie:Artikel mit ungeprüfter Infobox]] ein, falls noch nicht geschehen
new_pages = {}
            if '[[Kategorie:Artikel mit ungeprüfter Infobox]]' not in str(code):
target_user = pywikibot.User(pywikibot.Site(), "Thomas Karcher")
                code.append ('\n[[Kategorie:Artikel mit ungeprüfter Infobox]]')


            # Speichere Länderartikel
for page, oldid, ts, comment in target_user.contributions(total = 5000, showMinor = False, namespaces = [0]):
            page.text = str(code)
    if page.oldest_revision.user == target_user.username:
            page.save('Automatische Einbindung der Länder-Infobox (erste 12 Länder)')
        new_pages[page.title()] = {'title': page.title(), 'timestamp': page.oldest_revision.timestamp}
       
print (target_user.username + ' hat ' + str(len(new_pages)) + ' Artikel erstellt:')
print ('{| class = "wikitable sortable"\n! Artikel !! Erstellt am')
for p in new_pages.values(): print ("|-\n| [[%s]] || %s"%(p['title'], p['timestamp']))
print ('|}')
</syntaxhighlight>
</syntaxhighlight>

Aktuelle Version vom 28. April 2021, 23:40 Uhr

Aktualisierung der Länder-Infoboxen

Pywikibot-Skript

import requests
import pywikibot
import mwparserfromhell
import locale
import datetime

# Testlauf: Ausgabe des Logbuchs in der Konsole ohne Änderungen am Klexikon
dryrun_answer = input ('Nur Testlauf (j/n)? ')
dryrun = dryrun_answer != 'n'

# Verwende deutsches Zahlenformat mit Tausenderpunkt und Dezimalkomma
locale.setlocale(locale.LC_ALL, 'german')

# Zuordnung von Wikidata-Abfrage-Feldern zu Vorlage-Parametern
fieldmap = {
	'Name': 'itemLabel',
	'Flagge': 'safeFlaggenDatei',
	'Hauptstadt': 'hauptstaedte',
	'Amtssprache': 'amtssprachen',
	'Staatsoberhaupt': 'oberhaeupter',
	'Einwohnerzahl': 'max_ew_in_mio',
	'Fläche': 'max_flaeche_rund',
	'Lagekarte': 'safeKartenDatei',
	'Lagebeschreibung': 'kontinente'
}

# Legt fest, welche Länder-Artikel bzw. Vorlagen-Parameter Klexibot nicht ändern soll
blacklist = {
	'Australien:Staatsoberhaupt',
	'Eidgenossenschaft',
	'Deutsches_Kaiserreich',
	'Schottland',
	'England',
	'Eswatini:Name',
	'Schweiz:Amtssprache',
	'St._Kitts_und_Nevis:Staatsoberhaupt',
	'St._Lucia:Staatsoberhaupt',
	'St._Vincent_und_die_Grenadinen:Staatsoberhaupt',
	'Vatikan:Fläche',
	'Vatikan:Hauptstadt',
	'Wales'
}

# Parameter aus dieser Liste werden nur gefüllt, falls bisher kein Wert enthalten war
preserved_values = {
	'Lagekarte',
	'Lagebeschreibung'
}

# Anmeldung beim Klexikon
site = pywikibot.Site()

# Lade Wikidata-Tabelle und speichere sie in "data"-Variable
url = 'https://query.wikidata.org/sparql'
with open('infobox_data_query.rq', 'r', encoding="utf-8") as query_file: qry = query_file.read()
r = requests.get(url, params = {'format': 'json', 'query': qry }, headers = {
	'User-Agent':f'{requests.utils.default_headers()["User-Agent"]} (Klexikon bot by User:Tkarcher)'})
data = r.json()

# Initialisiere Logbuch-Eintrag
logheader = f'\n== Logbuch vom {datetime.datetime.now().strftime("%c")} ==\n'
newloghead = '=== Neu angelegte Infoboxen ===\n'
newlogdata = ''
chgloghead = '=== Änderungen an bestehenden Infoboxen ===\n' + \
	'{| class="wikitable sortable"\n! Vorlage\n! Eigenschaft\n! Alter Wert\n! Neuer Wert\n'
chglogdata = ''
chglogfoot = '|}\n'

# Gehe Wikidata-Tabelle Zeile für Zeile durch
for item in data['results']['bindings']:
	title = item['titel_im_klexikon']['value']

	# Ignoriere Artikel, die in der Ausschlußliste enthalten sind
	if title not in blacklist:

		# Öffne Vorlagen-Seite oder lege sie neu an
		page = pywikibot.Page(site, f'Vorlage:Infobox_{title}')
		code = mwparserfromhell.parse(page.text)

		# Ersetze Seiteninhalt mit {{Infobox Land}}, falls nicht vorhanden
		if not code.filter_templates(matches = lambda n: n.name.matches ('Infobox Land')):
			code = mwparserfromhell.parse('{{Infobox Land\n}}')
			newlogdata += f'* [[Vorlage:Infobox {title}]]\n'

		# Fülle alle Vorlagen-Felder
		for prop, field in fieldmap.items():

			# Ignoriere Vorlagen-Parameter, die leer oder in der Ausschlußliste enthalten sind
			if field in item and f'{title}:{prop}' not in blacklist:

				# Ignoriere auch Parameter, die nicht überschrieben werden sollen
				if not (
					prop in preserved_values and
					code.filter_templates()[0].has(prop) and
					code.filter_templates()[0].get(prop).value.strip() != ''):

					oldval = code.filter_templates()[0].get(prop).value.strip() if code.filter_templates()[0].has(prop) else ''
					val = item[field]['value']
					if prop == 'Einwohnerzahl':
						ew = float(val)
						if round(ew) > 1:
							val = f'etwa {locale.format_string("%.0f", ew, grouping = True)} [[Million]]en'
						elif ew > 0.9:
							val = f'etwa 1 [[Million]]'
						else:
							val = f'etwa {locale.format_string("%.0f", ew * 1000000, grouping = True)}'
					elif prop == 'Fläche':
						val = f'etwa {locale.format_string("%i", int(val), grouping = True)} [[Meter|Quadratkilometer]]'
					elif prop == 'Staatsoberhaupt':
						if 'oberhaupt_bezeichnung' in item:
							val = f'{item["oberhaupt_bezeichnung"]["value"]} {val}'
					elif prop == 'Lagebeschreibung':
						val = f'Wo das Land in {val} liegt'

					# Speichere die Änderung nur, wenn sie aus mehr besteht als
					# einer geänderten Sortierreihenfolge in der Auslistung
					# (SPARQL 1.1 erlaubt kein ORDER BY in GROUP_CONCATs)
					if sorted (val) != sorted (oldval):
						code.filter_templates()[0].add (f' {prop} ', f' {val}\n', preserve_spacing = False)
						if not newlogdata.endswith (f'* [[Vorlage:Infobox {title}]]\n'):
							chglogdata += f'|- \n| [[Vorlage:Infobox {title}]]\n| {prop}\n| {oldval}\n| {val}\n'

		# Speichere die Änderungen, falls vorhanden, und verschiebe
		# Artikel ggf. zurück in Kategorie "ungeprüfte Infobox"
		if (page.text != str(code)) and not dryrun:

			# Speichere Vorlage
			page.text = str(code)
			page.save('Automatische Anlage der Länder-Infobox')

			# Öffne Länderartikel
			page = pywikibot.Page(site, title)
			code = mwparserfromhell.parse(page.text)

			# Binde Länder-Infobox ein, falls noch nicht geschehen
			# Mit "_" und " " wegen https://github.com/earwig/mwparserfromhell/issues/216
			if not code.filter_templates(matches = lambda n:
				n.name.matches ({f'Infobox_{title}', f'Infobox {title}'})):
				code = mwparserfromhell.parse(f'{{{{Infobox_{title}}}}}\n{str(code)}')

				# Lösche [[Kategorie:Artikel mit geprüfter Infobox]], falls vorhanden
				if '[[Kategorie:Artikel mit geprüfter Infobox]]' in str(code):
					code.remove ('[[Kategorie:Artikel mit geprüfter Infobox]]')

				# Füge [[Kategorie:Artikel mit ungeprüfter Infobox]] ein, falls noch nicht geschehen
				if '[[Kategorie:Artikel mit ungeprüfter Infobox]]' not in str(code):
					code.append ('\n[[Kategorie:Artikel mit ungeprüfter Infobox]]')

				# Speichere Länderartikel
				page.text = str(code)
				page.save('Automatische Einbindung der Länder-Infobox')

# Dokumentiere Änderungen in Logbuch
log = logheader \
	+ newloghead + (newlogdata if newlogdata != '' else 'Es wurden keine neuen Infoboxen angelegt.\n') \
	+ chgloghead + (chglogdata if chglogdata != '' else '|- \n| colspan=4 | Es wurden keine bestehenden Daten geändert.\n') + chglogfoot

if dryrun:
	print (log)
else:
	page = pywikibot.Page(site, 'Benutzer:Klexibot/Logbuch')
	page.text += log
	page.save('Logbuch aktualisiert')

Wikidata-SPARQL-Abfrage

#Länder-Infoboxen im Klexikon
SELECT
 ?item
 ?itemLabel
 (MAX(?ew_in_millionen) AS ?max_ew_in_mio)
 (GROUP_CONCAT(DISTINCT (IF(BOUND(?asklextitel),CONCAT('[[',?asklextitel,'|',?amtssprachenLabel,']]'),?amtssprachenLabel)); SEPARATOR=", ") AS ?amtssprachen)
 (MAX(?flaeche_rund) AS ?max_flaeche_rund)
 (GROUP_CONCAT(DISTINCT (IF(BOUND(?hsklextitel),CONCAT('[[',REPLACE(?hsklextitel, "_", " "),']]'),?hauptstadtLabel)); SEPARATOR=", ") AS ?hauptstaedte)
 (GROUP_CONCAT(DISTINCT (COALESCE (?oberhaupt_titel_ausnahme, ?oberhaupt_bezLabel)); SEPARATOR=", ") AS ?oberhaupt_bezeichnung)
 (GROUP_CONCAT(DISTINCT (IF(BOUND(?ohklextitel),CONCAT('[[',REPLACE(?ohklextitel, "_", " "),']]'),?oberhauptLabel)); SEPARATOR=", ") AS ?oberhaeupter)
 (GROUP_CONCAT(DISTINCT (CONCAT('[[',?kontinentLabel,']]')); SEPARATOR=" und ") AS ?kontinente)
 ?safeFlaggenDatei
 ?safeKartenDatei
 ?titel_im_klexikon
WHERE
{
 ?item p:P31 [ps:P31 wd:Q6256]. # Finde alle Länder
 ?item wdt:P6573 ?titel_im_klexikon. # die es im Klexikon gibt
 ?item wdt:P1082 ?ewzahl. # und zeige ihre Einwohnerzahl
 BIND (STR(ROUND(?ewzahl / 1000) / 1000) AS ?ew_in_millionen) # in Millionen
 ?item wdt:P2046 ?flaeche. # ihre Fläche
 BIND (STR(IF(?flaeche > 9999, ROUND(?flaeche / 1000) * 1000,IF(?flaeche > 999, ROUND(?flaeche / 100) * 100,ROUND(?flaeche)))) AS ?flaeche_rund) # gerundet
 ?item wdt:P37 ?amtssprache. # ihre Amtssprache
 # ?article schema:about ?amtssprache. # Zeige aber nur die bekannteren Amtssprachen, d.h. solche
 # ?article schema:inLanguage "de" . # für die ein deutschsprachiger Wikipedia-Artikel existiert
 OPTIONAL {?amtssprache wdt:P6573 ?asklextitel}
 OPTIONAL { # Optional wg. Vatikanstadt
  ?item wdt:P36 ?hauptstadt. # ihre Hauptstädte
  OPTIONAL {?hauptstadt wdt:P6573 ?hsklextitel}
 }
 ?item wdt:P35 ?oberhaupt. # das Staatsoberhaupt
 OPTIONAL {?oberhaupt wdt:P6573 ?ohklextitel}
 OPTIONAL {
  ?item wdt:P1906/wdt:P279* ?oberhaupt_titel.
  FILTER (?oberhaupt_titel IN ( # einschränken auf bekannte / kinderverständliche Titel
  wd:Q30461, # Präsident
  wd:Q2304859, # Souverän
  wd:Q7645115, # Oberster Führer
  wd:Q166382, # Emir
  wd:Q683337, # Kofürst von Andorra
  wd:Q258045, # Capitano Reggente
  wd:Q844944 # Präsidium von Bosnien und Herzegowina
  ))
  OPTIONAL {
   ?oberhaupt_titel wdt:P2521 ?oberhaupt_titel_weiblich.
   FILTER (LANG(?oberhaupt_titel_weiblich) = "de").
  }
 }
 OPTIONAL {?oberhaupt wdt:P21 ?geschlecht.}
 BIND (IF (BOUND(?oberhaupt_titel_weiblich) && BOUND(?geschlecht) && (?geschlecht = wd:Q6581072),
  IF(STR(?oberhaupt_titel_weiblich) = "Souveränin", wd:Q16511993, ?oberhaupt_titel_weiblich),
  IF(?oberhaupt_titel = wd:Q2304859, wd:Q12097, ?oberhaupt_titel)) AS ?oberhaupt_bez
 )
 OPTIONAL { # Länderspezifische Titel
  VALUES (?item ?oberhaupt_titel_ausnahme) {
   (wd:Q794 "Religionsführer") # Iran
   (wd:Q17 "[[Kaiser]]") # Japan
   (wd:Q40 "Bundespräsident") # Österreich
   (wd:Q183 "Bundespräsident") # Deutschland
   (wd:Q32 "Großherzog") # Luxemburg
   (wd:Q237 "[[Papst]]") # Vatikan
   (wd:Q347 "Fürst") # Liechtenstein
   (wd:Q683 "Häuptling") #Samoa
  }
 }
 ?item wdt:P41 ?flagge.
 BIND(REPLACE(wikibase:decodeUri(STR(?flagge)), "http://commons.wikimedia.org/wiki/Special:FilePath/", "") AS ?flaggenDatei).
 BIND(REPLACE(?flaggenDatei, " ", "_") AS ?safeFlaggenDatei)
 ?item wdt:P30 ?kontinent_alt.
 OPTIONAL {
  ?item wdt:P361*/wdt:P706*/wdt:P361* wd:Q27611
  BIND (wd:Q29876 AS ?mittelamerika)
 }
 BIND (COALESCE(?mittelamerika, ?kontinent_alt) AS ?kontinent)

 OPTIONAL { # Lagekarte: Grünes Land auf dem Globus
  ?item p:P242 ?p_karte1.
  ?p_karte1 pq:P195 wd:Q21167586.
  ?p_karte1 ps:P242 ?karte1.
 }
 OPTIONAL { # Lagekarte: Rotes Land auf dem Globus
  ?item p:P242 ?p_karte2.
  ?p_karte2 pq:P195 wd:Q47008743.
  ?p_karte2 ps:P242 ?karte2.
 }
 # Nimm Globus, falls vorhanden, sonst 2:1-Ausschnitt
 BIND(REPLACE(wikibase:decodeUri(STR(COALESCE(?karte1, ?karte2))), "http://commons.wikimedia.org/wiki/Special:FilePath/", "") AS ?kartenDatei).
 BIND(REPLACE(?kartenDatei, " ", "_") AS ?safeKartenDatei)

 SERVICE wikibase:label {
  bd:serviceParam wikibase:language "[AUTO_LANGUAGE],de".
  ?item rdfs:label ?itemLabel.
  ?amtssprache rdfs:label ?amtssprachenLabel.
  ?hauptstadt rdfs:label ?hauptstadtLabel.
  ?oberhaupt_bez rdfs:label ?oberhaupt_bezLabel.
  ?oberhaupt rdfs:label ?oberhauptLabel.
  ?kontinent rdfs:label ?kontinentLabel.
 }
}
GROUP BY ?item ?itemLabel ?safeFlaggenDatei ?safeKartenDatei ?titel_im_klexikon
ORDER BY ?itemLabel

Validierungen / Qualitätssicherung

Artikel mit Klammerfehlern

import pywikibot

opening = '{['
closing = '}]'
mapping = dict(('{}', '[]'))
pages_with_unmatched_brackets = []

site = pywikibot.Site()

for page in site.allpages(namespace = ""):
    queue = []
    if page.title() != 'ß': # Komische Sache! Die Seite gibt's, aber dann irgendwie doch nicht... 
        for letter in page.text:
            if letter in opening:
                queue.append(mapping[letter])
            elif letter in closing:
                if not queue or letter != queue.pop():
                    pages_with_unmatched_brackets.append(page.title())
        if queue: pages_with_unmatched_brackets.append(page.title())

print ('[['+ ']], [['.join(list(dict.fromkeys(pages_with_unmatched_brackets))) + ']]')

Artikel mit Links in Dateinamen

import re
import pywikibot

pattern_1 = re.compile(r'atei\:[^\|]*\[\[')
pattern_2 = re.compile(r'allery[\s\S]+\n[^\|]*\[\[[\s\S]+allery')
pages_with_links_in_filename = []

site = pywikibot.Site()

for page in site.allpages(namespace = ""):
    queue = []
    if page.title() != 'ß': # Komische Sache! Die Seite gibt's, aber dann irgendwie doch nicht... 
        if str(pattern_1.search(page.text)) != 'None' or str(pattern_2.search(page.text)) != 'None':
            pages_with_links_in_filename.append(page.title())

print ('[['+ ']], [['.join(list(dict.fromkeys(pages_with_links_in_filename))) + ']]')

Wörter mit Wikilink im zweiten Teil

import re
import pywikibot

pattern_1 = re.compile(r'\W(\w+\[\[[^\]]+\]\])')
matches = ''

site = pywikibot.Site()

for page in site.allpages(namespace = ""):
   if page.title() != 'ß': # Komische Sache! Die Seite gibt's, aber dann irgendwie doch nicht... 
        match = pattern_1.search(page.text) 
        if match: matches += '* [[' + page.title() + ']]: "' + match.group(1) + '"\n'

print (matches)

Artikel mit 3 oder mehr Bildern in der Einleitung

import re
import pywikibot

pattern_5 = re.compile(r'^\[\[Datei\:.+\n?\[\[Datei\:.+\n?\[\[Datei\:.+\n?\[\[Datei\:.+\n?\[\[Datei\:')
pattern_4 = re.compile(r'^\[\[Datei\:.+\n?\[\[Datei\:.+\n?\[\[Datei\:.+\n?\[\[Datei\:')
pattern_3 = re.compile(r'^\[\[Datei\:.+\n?\[\[Datei\:.+\n?\[\[Datei\:')
matches = []

site = pywikibot.Site()

for page in site.allpages(namespace = ""):
   if page.title() != 'ß': 
        if pattern_5.search(page.text):
            matches.append("| [[" + page.title() + "]] || 5\n")
        elif pattern_4.search(page.text):
            matches.append("| [[" + page.title() + "]] || 4\n")
        elif pattern_3.search(page.text):
            matches.append("| [[" + page.title() + "]] || 3\n")

print ('{| class="wikitable sortable"\n! Artikel !! Bilder zu Beginn\n|-\n'+ '|-\n'.join(matches) + '|}')

Statistik

Anzahl neu erstellter Artikel

import pywikibot

new_pages = {}
target_user = pywikibot.User(pywikibot.Site(), "Thomas Karcher")

for page, oldid, ts, comment in target_user.contributions(total = 5000, showMinor = False, namespaces = [0]):
    if page.oldest_revision.user == target_user.username:
        new_pages[page.title()] = {'title': page.title(), 'timestamp': page.oldest_revision.timestamp}
        
print (target_user.username + ' hat ' + str(len(new_pages)) + ' Artikel erstellt:')
print ('{| class = "wikitable sortable"\n! Artikel !! Erstellt am')
for p in new_pages.values(): print ("|-\n| [[%s]] || %s"%(p['title'], p['timestamp']))
print ('|}')