Hoe werken bots voor wikibase?
Een bot is eigenlijk gewoon een account die gelijkaardige zaken kan doen als een gebruiker, maar in plaats van dat een mens het nodige denk- en klikwerk uitvoert, is het een programma of script dat achter het stuur zit.
Concreet betekent dit dat je een programmeertaal kiest en een manier van praten met wikibase (wikibase heeft meerdere API's beschikbaar).
Ik heb gekozen voor python, omdat ik hier vrij vertrouwd mee ben en dit in mijn ogen ook een zeer gebruiksvriendelijke en toegankelijke taal is voor zowel ervaren als nieuwe programmeurs.
Als API heb ik gekozen voor de python module Wikibase Integrator. Deze is gebouwd op de MediaWiki Wikibase API en de Wikibase SPARQL endpoint en maakt de interacties nog eenvoudiger dan rechtstreeks de API te proberen aanspreken. De module is populair en wordt actief ontwikkeld, dus er is weinig risico dat deze plots niet meer zal compatibel zijn met de API.
Bot paswoord
Er zijn verschillende manieren om je als bot in te loggen via Wikibase Integrator, geïmplementeerd in wbi_login (zie documentatie).
Ik vond niet meteen of OAuth ingesteld was op de instantie van Kunstenpunt, maar vond wel vrij snel mijn weg naar de Bot passwords pagina. Hoe dit werkt is dat je je inlogt met een gebruikersaccount, en dan voor die gebruikersaccount bots kan registreren met een automatisch gegenereerd "bot password". Je kan dan via wbi_login inloggen met die informatie en op die manier verbinden met de wikibase.
Het voordeel van bot passwords is dat je meerdere bots kan hebben per gebruikersaccount, en dat je per bot kan aanduiden welke rechten die bot mag hebben:
Let wel: de bots die op deze manier aangemaakt worden op een account kunnen nooit meer rechten hebben dan de originele gebruikersaccount waaronder ze aangemaakt worden. Je kan dus als gewone gebruiker geen bots maken die plots meer kunnen dan je zelf kan.
Als je wilt dat een gebruiker bots kan maken die snel werken, voeg je deze best ook toe aan de bot rol. Dit zorgt ervoor dat de account sneller wijzigingen dan een mens mag aanbrengen en dat er hogere limieten worden gezet op het aantal aanpassingen per tijdseenheid. (Dit komt dan overeen met het tweede vinkje in de afbeelding hierboven).
Python omgeving
Voor de python omgeving heb ik gebruik gemaakt van Python 13.3 en pipenv. Dat laatste is een tool om eenvoudig python projecten en hun dependencies te beheren. De snelle uitleg is te vinden op de website. Aangezien ik het project al heb aangemaakt, moet je gewoon zorgen dat Python en pipenv geïnstalleerd zijn en dan met de console naar de juiste map gaan en daar pipenv run python naam_van_script.py invoeren.
Een volledig voorbeeld: import clutter
Nu de basis uitgelegd is, kunnen we misschien het snelst leren door gewoon naar een eerste (werkend) voorbeeld te kijken. Het volgende script (delete_query_results.py ) was nodig om import clutter te verwijderen:
from wikibaseintegrator import WikibaseIntegrator from wikibaseintegrator.wbi_config import config from wikibaseintegrator.wbi_helpers import execute_sparql_query from wikibaseintegrator.wbi_login import Login from utils import query_result_to_entity_ids # De nodige configuratie voor onze wikibase instantie config["DEFAULT_LANGUAGE"] = "nl" config["WIKIBASE_URL"] = "https://kg.kunsten.be" config["MEDIAWIKI_API_URL"] = "https://kg.kunsten.be/w/api.php" config["MEDIAWIKI_INDEX_URL"] = "https://kg.kunsten.be/w/index.php" config["MEDIAWIKI_REST_URL"] = "https://kg.kunsten.be/w/rest.php" config["SPARQL_ENDPOINT_URL"] = ( "https://kg.kunsten.be/query/proxy/wdqs/bigdata/namespace/wdq/sparql" ) # De login van de bot account (niet zomaar online zetten / publiek delen!) login = Login(user="WALL-E", password="DELETER@GEHEIMWACHTWOORD") wbi = WikibaseIntegrator(login=login) # Je mag hier elke query schrijven die als resultaat een lijst van entiteiten geeft # die je wilt verwijderen (geen andere velden in de reusultaten, de naam van de # variabele, hier "subject", maakt niet uit) query = """ SELECT ?subject WHERE { ?subject rdfs:label ?label . FILTER REGEX (?label, "already has label", "i") SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],nl,en" . } } """ # Reden van verwijderen reason = "This was clutter left by an earlier merge." query_result = execute_sparql_query(query) entity_ids = query_result_to_entity_ids(query_result) for index, entity_id in enumerate(entity_ids): print(f"deleting {entity_id} ({index + 1} of {len(entity_ids)})") entity = wbi.item.get(entity_id) entity.delete(is_bot=True, reason=reason)
We zullen deze nu stap per stap overlopen.
from wikibaseintegrator import WikibaseIntegrator from wikibaseintegrator.wbi_config import config from wikibaseintegrator.wbi_helpers import execute_sparql_query from wikibaseintegrator.wbi_login import Login from utils import query_result_to_entity_ids
Eerst importeren we de nodige zaken uit de wikibaseintegrator module.
WikibaseIntergrator is het basisobject voor interacties met de database.
config dient voor de configuratie specifiek voor onze wikibase instantie
execute_sparql_query is een helper functie om een sparql query uit te voeren op onze database
Login is het object dat ons toestaat om in te loggen met het reeds aangemaakte bot password
query_result_to_entity_ids is een zelfgeschreven functie, te vinden in een andere utils.py file. execute_sparql_query geeft een grote json file waar de resultaten nogal omslachtig in vervat zitten. Die functie overloopt die file en haalt er de entity ids uit (Q nummers), ervan uitgaande dat het resultaat gewoon een lijst van entiteiten is.
# De nodige configuratie voor onze wikibase instantie config["DEFAULT_LANGUAGE"] = "nl" config["WIKIBASE_URL"] = "https://kg.kunsten.be" config["MEDIAWIKI_API_URL"] = "https://kg.kunsten.be/w/api.php" config["MEDIAWIKI_INDEX_URL"] = "https://kg.kunsten.be/w/index.php" config["MEDIAWIKI_REST_URL"] = "https://kg.kunsten.be/w/rest.php" config["SPARQL_ENDPOINT_URL"] = ( "https://kg.kunsten.be/query/proxy/wdqs/bigdata/namespace/wdq/sparql" ) # De login van de bot account (niet zomaar online zetten / publiek delen!) login = Login(user="WALL-E", password="DELETER@GEHEIMWACHTWOORD") wbi = WikibaseIntegrator(login=login)
Dit spreekt redelijk voor zich: hier geven we de module de nodige informatie om correct te kunnen verbinden met onze instantie. De login informatie wordt meegegeven ("WALL-E" is de hoofdgebruiker, de bot onder die gebruiker die we nu gaan nemen is "DELETER" en het bot password is "GEHEIMWACHTWOORD"). We hebben vooraf al de juiste rechten toegekend aan "WALL-E" (bot, administrator) en aan DELETER op de bot password pagina. Tot slot maken we met deze informatie ons WikibaseIntegrator object aan.
# Je mag hier elke query schrijven die als resultaat een lijst van entiteiten geeft # die je wilt verwijderen (geen andere velden in de reusultaten, de naam van de # variabele, hier "subject", maakt niet uit) query = """ SELECT ?subject WHERE { ?subject rdfs:label ?label . FILTER REGEX (?label, "already has label", "i") SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],nl,en" . } } """ # Reden van verwijderen reason = "This was clutter left by an earlier merge."
Dit stukje code omvat de query waarvan we de resultaten wensen te verwijderen. We maken ook een variabele aan die de reden van verwijdering bevat (dit zullen we later meegeven met de API). Als je het script wilt hergebruiken voor andere zaken, kan je dit deel dus aanpassen zodat de query de entiteiten geeft die je wilt verwijderen en met de reden die jij wilt meegeven.
query_result = execute_sparql_query(query) entity_ids = query_result_to_entity_ids(query_result)
Hier voeren we de query uit en gebruiken we de extra zelfgeschreven functie om de grote en ietwat omslachtige JSON om te zetten naar een simpele lijst van entiteit ids (Q nummers).
for index, entity_id in enumerate(entity_ids): print(f"deleting {entity_id} ({index + 1} of {len(entity_ids)})") entity = wbi.item.get(entity_id) entity.delete(is_bot=True, reason=reason)
Hier gebeurt het eigenlijke verwijderen. Per entiteit in de lijst printen we eerst een lijntje informatie naar de console waar het scriptje in draait. Zo kan diegene die het script uitvoert duidelijk zien hoever het is met het verwijderen van de entiteiten. Dat berichtje is van de vorm deleting Q12345 (7 of 42) . Daarna halen we het entiteit object op uit de database op basis van het id, en vervolgens roepen we de delete methode op met de vooraf opgestelde reden. is_bot=True zorgt ervoor dat we de botrechten benutten en hogere limieten hebben dan een gewone gebruiker. Als de juiste rechten niet zijn ingesteld voor de gebruiker of de bot, zal deze functie niet slagen en krijg je daar een foutmelding over.
Om het script uit te voeren gebruik je het volgende commando vanuit de projectmap:
pipenv run python delete_query_results.py
Als alles goed gaat, voert dit script de query uit, waarna je vervolgens 1 voor 1 de entiteiten ziet verwijderd worden. Dit gaat relatief langzaam (slechts enkele entiteiten per seconde), omdat er per entiteit een API call voor verwijdering wordt gedaan. Ik heb even gezocht naar een "bulk delete" call, maar vond niet meteen een vlot antwoord en vooral andere gebruikers met dezelfde vraag, dus ik denk dat dit mogelijks in de toekomst eenvoudiger wordt. Maar voor de ~250 resultaten van de merge clutter was het uiteindelijk al binnen een minuutje klaar. Dit gaat natuurlijk nu niet meer werken, aangezien de resultaten al verwijderd zijn. Om achteraf nog wat dingen te testen, heb ik tijdelijk wat dummy pagina's terug aangemaakt met een ander test scriptje (create_10_pages.py ) om die dan weer te kunnen verwijderen met deze.