Links in Wikipedia-Artikeln auf Wikidata-Basis automatisiert ergänzen

Im Wikimedians-in-Bibliotheken-Treffen sitzend fiel mir wieder ein altes To Do ein, das wir immer noch nicht umgesetzt haben, obwohl die Voraussetzungen eigentlich bestens sind. Da wir mit dem Wikiversum nicht so vertraut sind, haben wir das immer wieder aufgeschoben. Jetzt nehme ich mir mal vor, das umzusetzen und hier zu dokumentieren, falls andere ähnliche Dinge umsetzen wollen.

Ziel

Wikipedia-Einträge zu Orten in Nordrhein-Westfalen sollen automatisiert eine Link auf die entsprechende Literaturliste in der Nordrhein-Westfälischen Bibliographie (NWBib) erhalten.

Beispiel:

Literatur über Lindenthal (Köln) in der Nordrhein-Westfälischen Bibliographie

Gegebene Voraussetzungen

In der NWBib wird der Großteil der Ortsverschlagwortung seit einigen Jahren auf Basis von Wikidata gemacht. Das heißt der allergößte Teil von Einträgen in der NWBib-Raumsystematik ist bereits durch eine Q-ID identifiziert, z.B. Köln: https://nwbib.de/spatial#Q365. (Zum Hintergrund siehe How we built a spatial subject classification based on Wikidata .)

Auf Basis der jeweiligen Q-ID lässt sich dann eine Liste von NWBib-Literaturangaben über den jeweiligen Ort anzeigen, für unser Beispiel „Köln“ ist das https://nwbib.de/search?nwbibspatial=https%3A%2F%2Fnwbib.de%2Fspatial%23Q365.

In Wikidata sind die entsprechenden Orte mittels eines Statements mit der Property P6814/NWBib-ID markiert, siehe https://www.wikidata.org/wiki/Q365#P6814 für das Köln-Beispiel und diese SPARQL-Query für alle ca. 4500 Einträge.

Umsetzung

Ich sammel hier mal ein paar mir bekannte Ansätze, so etwas umzusetzen.

Wikipedia-Template erstellen und einbinden

Für Orte in der Rheinland-Pfälzischen Bibliographie wurde eine ähnliche Funktionalität bereits umgesetzt, siehe z.B. Kempenich – Wikipedia.

Die benutzte Wikipedia-Vorlage ist folgende: Vorlage:RPB ORT – Wikipedia

Nachteil: Wenn ich das richtig sehe, wurde der Link auf Basis des Templates in jedem Artikel manuell ergänzt. (Dafür hat das Lanesbibliothekszentrum Rheinand-Pfalz übrigens ein dediziertes (und verifiziertes) Wikipedia-Benutzerkonto, Plrlb.) Das sollte sich bei unserem Ansatz.

Einbindung über die bestehende Wikidata-Verknüpfung

Es gibt jede Menge Wikidata-Templates für Wikipedia, besonders relevant sind wohl jene in er Kategorie External link templates using Wikidata.

Fragen / Nächste Schritte

Ich frage mich jetzt, wie ich die nächsten Schritte am besten angehe:

  • Wie erstelle ich am besten selbst ein Link-Template?
  • Wie binde ich das Template automatisiert auf den entsprechenden ~4.500 Wikpedia-Seiten ein?

Hat jemand hier damit bereits Erfahrung?

2 Likes

kewle Idee Adrian! :slight_smile:

Guckst du mal: https://de.wikipedia.org/wiki/Lindenthal\_(Köln)#Weblinks

Vorlage einmal in meinem BNR https://de.wikipedia.org/wiki/Benutzer:Mfchris84/Sandbox/Vorlage:NWBib ganz simpel ohne Fallback und auch einmal auf Parameter verzichtet. QID ist ja fix vorhanden bei Orten aus NRW und Pagetitle/Articlebase als Name - könnte man ggf. durch einen optionalen Parameter reinholen, wenn man denn möchte.

Vorlage jetzt unter Vorlage:NWBib – Wikipedia verfügbar. Standardmässig, wenn im korrespondierenden Wikidata-Item die NWBibID (P6814) gesetzt ist, reicht es aus, nur {{NWBib}} einzusetzen und die Linkzeile erscheint, wie von dir gewünscht. Die Vorlage kennt aber dennoch zwei Parameter

  • NWBibID (Hier kann bei fehlendem Eintrag in Wikidata oder wenn ein Link auf irgendeiner nicht korrespondierendne Seite sinnvollerweise dennoch eingesetzt werden kann, gesetzt werden - einfachster Anwendungsfall ist das Beispiel in der Vorlagen-Doku)
  • Ort (Name ist standardmäßig das Label aus Wikidata. Hier kann ein Ortsnamen indidivudell angepasst werden - ggf. auch für grammatikalische Notwendigkeiten vgl. Siebengebirge – Wikipedia)

Einbindung in großer Zahl wäre wohl eher was für einen schnellen Bot bzw. weiß ich gerade nicht genau, ob Admins nicht auch die Möglichkeit hätten die Vorlage über bestimmte Such/Ersetzen-Funktionen zu platzieren… Laut meinem Admin-Spezi ist das die Spezialseite „Text ersetzen“ nur Bürokraten zugänglich.

PS: Es sind übrigens knapp 4.300 NWBib-IDs in WD-Items mit deWP-Artikel (https://w.wiki/Fzf5)

1 Like

Das ist ja super cool, danke dir vielmals! Da hätten wir uns auf jeden Fall reinarbeiten müssen, der Quelltext der Vorlage sieht nicht leicht aus für Leute wie mich, die mit Lua nicht vertraut sind:

<onlyinclude><includeonly>Literatur über [https://nwbib.de/search?nwbibspatial=https%3A%2F%2Fnwbib.de%2Fspatial%23{{#if:{{{NWBibID|}}}|{{{NWBibID}}}|{{#invoke:Wikidata|claim|1=P6814}}}} {{#if:{{{Ort|}}}|{{{Ort}}}|{{#invoke: WLink|getArticleBase}}}} in der Nordrhein-Westfälischen Bibliographie]</includeonly></onlyinclude>

Die richtige Wikipedia-Anlaufstelle wäre wohl Wikipedia:WikiProjekt Vorlagen/Werkstatt – Wikipedia gewesen, aber die Variante über das Forum hier hat mir sehr gut gefallen. :smiley:

Für die 4.300 Bearbeitungen schauen wir uns dann bald an, wie ein entsprechender Bot eingerichtet werden kann. Help:Creating a bot - Wikipedia sieht ja schonmal gut aus. Falls jemand einen Bot kennt, der etwas Ähnliches gemacht hat, freue ich mich über Hinweise, dann könnten wir den ggf. als Vorlage nehmen.

1 Like

@LibrErli ich will mich mit der Erstellung eines Bots beschäftigen. Ich finde die Help:Creating_a_bot Seite leider nicht hilfreich.

Hast du einen Tipp, vielleicht auch ein (Code)-Beispiel für Bots die templates ergänzen?

@TobiasNx und ich haben jetzt ein NWBibBot-Konto angelegt und angefangen mit pywikibot in einem PAWS Jupyter Notebook zu experimentieren. Wir melden uns, wenn wir etwas Funktionierendes vorzuweisen haben.

Hier ist schonmal eine erste Version, die gegen die test.wikipedia.org läuft.

Das vorfiltern klappt schonmal gut.

was uns fehlt ist eine smarte Variante für das Suchen und Ersetzen:

            # TODO: HOW TO REPLACE THIS IN A SMART WAY
            # test = page.text + "\n* {{NWBib}}"
            # page.put(test, summary='Bot: Test edit')
1 Like

Ich hab es geschafft die Weblinks auszulesen ABER den content dieses Abschnitts zu überschreiben, schaffe ich nicht:

site = pywikibot.Site(None)
sect = textlib.extract_sections(page.text, site)
linkPos = sect.sections.index(„Weblinks“)
sect.sections[linkPos].content.strip()


Mittlerweile müsste ich weitergekommen sein:



site = pywikibot.Site(None)
sect = textlib.extract_sections(page.text, site)
linkPos = sect.sections.index(„Weblinks“)
webLinkContent = sect.sections[linkPos].content.strip()
neu = webLinkContent + „\n DAS IST EIN TEST“
new_content = page.text.replace(str(webLinkContent), str(neu))
page.put(new_content, summary=‚Bot: Test edit‘)

ABER anscheinend ist die API des Testwikis gerade inaktiv:

Sleeping for 9.4 seconds, 2026-03-05 16:25:58 WARNING: API error readonly: The wiki is currently in read-only mode. ERROR: Detected MediaWiki API exception internal_api_error_readonly: The wiki is currently in read-only mode.

:woman_shrugging:

Aktueller Script Stand:

1 Like

ich nutze für Template-Ergänzungen gerne wikitextparser
und arbeite auch vielfach lieber direkt mit der MediaWiki-API, mit requests.get / post etc. (pywikibot ist mächtig und gut, nur bin ich mit dem Handling nicht immer ganz so firm)

wikitextparser erlaubt bspw für den Zweck Vorlage:NWbib die Seite nach der Vorlage gezielt zu suchen;
dazu kann man auch vorher überprüfen ob eine Section „Weblinks“ vorhanden ist (bzw. sie ggf. erzeugen)

und ja, ich hab sowas schon gemacht. (im konkreten für das oesterreichwiki.org) aber mediawiki bleibt mediawiki. :slight_smile:

import sys
from SPARQLWrapper import SPARQLWrapper, JSON
import requests
import re
import wikitextparser as wtp

endpoint_url = "https://query.wikidata.org/sparql"

query = """SELECT * WHERE {
    ?item wdt:P6228 ?rwat; wdt:P8544 ?ariadne.
}
"""
normdatenType= "p"

URL = "https://regiowiki.at/api.php"
S = requests.Session()

def get_rwatPage(result,URL):
    #print(result)
    #print(URL)
    try:
        pageid = result["rwat"]["value"]
        print(pageid)
        #https://regiowiki.at/api.php?action=query&prop=info&pageids=3897&format=json
        PARAMS = {
            #api.php?action=query&prop=revisions&titles=Pet_door&rvslots=*&rvprop=content&formatversion=2
            "action": "query",
            "pageids": pageid,
            "prop": "info",
            "format": "json"

        }
        R = S.post(URL, data=PARAMS)
        DATA = R.json()
        rwatPage = DATA["query"]["pages"][pageid]["title"]
    except:
        # Excpetion 1 - Show Input Prompt
        #rwatPage = input("Regiowiki-PageTitle:") 
        # Exception 2 - Write Problmeatic WD-Items into Log File
        f = open("checkWDID.tsv", "a")
        print(result["item"]["value"],"\tP6228\t\""+result["rwat"]["value"]+"\"",file=f)
        f.close()
        rwatPage = ""
    return rwatPage


    
def get_results(endpoint_url, query):
    user_agent = "WDQS-example Python/%s.%s" % (sys.version_info[0], sys.version_info[1])
    # TODO adjust user agent; see https://w.wiki/CX6
    sparql = SPARQLWrapper(endpoint_url, agent=user_agent)
    sparql.setQuery(query)
    sparql.setReturnFormat(JSON)
    return sparql.query().convert()


results = get_results(endpoint_url, query)


for result in results["results"]["bindings"]:
    
    rwatPage = get_rwatPage(result,URL)
    print(rwatPage)
    if rwatPage == "":
        continue

    # Step 1: GET request to fetch login token
    PARAMS_0 = {
        "action": "query",
        "meta": "tokens",
        "type": "login",
        "format": "json"
    }

    R = S.get(url=URL, params=PARAMS_0)
    DATA = R.json()

    LOGIN_TOKEN = DATA['query']['tokens']['logintoken']

    # Step 2: POST request to log in. Use of main account for login is not
    # supported. Obtain credentials via Special:BotPasswords
    # (https://www.mediawiki.org/wiki/Special:BotPasswords) for lgname & lgpassword
    PARAMS_1 = {
        "action": "login",
        "lgname": "YOUR-WIKI-USER",
        "lgpassword": "YOUR-WIKI-USER-BOTPASSWD",
        "lgtoken": LOGIN_TOKEN,
        "format": "json"
    }

    R = S.post(URL, data=PARAMS_1)

    # Step 3: GET request to fetch CSRF token
    PARAMS_2 = {
        "action": "query",
        "meta": "tokens",
        "format": "json"
    }

    R = S.get(url=URL, params=PARAMS_2)
    DATA = R.json()

    CSRF_TOKEN = DATA['query']['tokens']['csrftoken']

    #Step 4a: Get current text of wiki page
    PARAMS_3 = {
        #api.php?action=query&prop=revisions&titles=Pet_door&rvslots=*&rvprop=content&formatversion=2
        "action": "query",
        "titles": rwatPage,
        "prop": "revisions",
        "rvprop": "content",
        "formatversion": "2",
        "format": "json"

    }
    R = S.post(URL, data=PARAMS_3)
    DATA = R.json()
    currentText = DATA["query"]["pages"][0]["revisions"][0]["content"]


    # Step 4a: Prepare text for upload

    #Check if Vorlage:Normdaten is already set
    postWikiText = "" 
    parsed = wtp.parse(currentText)
    ariaString = "* {{ARIADNE|"+result["ariadne"]["value"]+"}}"
    
    templateSet = False
    for templ in parsed.templates:
        if templ.name == "ARIADNE":
            templateSet = True
            print("Vorlage:ARIADNE bereits gesetzt")
        if templ.name == "Commonscat":
            commonsCat = str(templ)

            
    
    webLinksSet = False
    if templateSet == False:
        for section in parsed.sections:
            #print(section.id)
            #print(section.title)
            if(str(section.title).strip() == 'Weblinks'):
                webLinksSet = True
                try:
                    #Link nach etwaigem commonscat-Link
                    #print(commonsCat)
                    postWikiText= re.sub(r"\{\{(C|c)ommonscat\|.*\}\}",commonsCat+"\n"+ariaString,currentText)
                except:
                    postWikiText= re.sub(r"(\=\=Weblinks\=\=)|(\=\= Weblinks =\=)","== Weblinks ==\n"+ariaString,currentText)
                    
                # Step 5: POST request to edit a page
                PARAMS_5 = {
                    "action": "edit",
                    "title": rwatPage,
                    "token": CSRF_TOKEN,
                    "bot":"true",
                    "format": "json",
                    "text": postWikiText,
                    "summary" : "Vorlage:Ariadne basierend auf Wikidata-Matching (RWAT-ID + GND-ID, mit P31 -> Q5) ergänzt"
                }
        
        if webLinksSet == False:
            # Noch keine Weblinks-Section vorhanden
            postWikiText = re.sub(r"(\=\=Einzelnachweise\=\=)|(\=\= Einzelnachweise =\=)","== Weblinks ==\n"+ariaString+"\n\n== Einzelnachweise ==",currentText)
            
            PARAMS_5 = {
                    "action": "edit",
                    "title": rwatPage,
                    "token": CSRF_TOKEN,
                    "bot":"true",
                    "format": "json",
                    "text": postWikiText,
                    "summary" : "Vorlage:Ariadne basierend auf Wikidata-Matching (RWAT-ID + GND-ID, mit P31 -> Q5) hinzugefügt"
            }
            
     
        print(webLinksSet)            
        #print(postWikiText)



    #if templateSet == False:
        R = S.post(URL, data=PARAMS_5)
        #DATA = R.json()
        #print(DATA)
1 Like