Inhoud
Redirects bij properties op entiteiten

Exploratie

Iets uitdagender is het volgende project: verwijzingen na merging. Dit zijn entiteiten die via owl:sameAs doorverwijzen naar een andere entiteit. Het is dus de bedoeling om deze oude entiteiten, die fungeren als overstap, te verwijderen, en bij andere entiteiten alle verwijzingen naar deze oude entiteiten te vervangen door verwijzingen naar de nieuwe doelentiteit.
Een eenvoudige query die ons al deze doelen oplevert is de volgende:
SELECT DISTINCT ?old ?new WHERE { ?old owl:sameAs ?new . }
Dit geeft ons 1898 resultaten. Voor elk resultaat, kunnen er dus tal van entiteiten zijn met statements die verwijzen naar de oude entiteit in plaats van naar de nieuwe. Deze dienen allemaal aangepast te worden, alvorens de oude entiteit verwijderd kan worden. Maar eerst een fijn extraatje…

Dubbele redirects

Wijzen deze redirects wel allemaal naar entiteiten? Een snelle uitbreiding van de vorige query toont ons van niet:
SELECT DISTINCT ?old ?new WHERE { ?old owl:sameAs ?new . ?new wikibase:sitelinks [] . }
Dit geeft ons slechts 1858 resultaten. Let op de vreemde nieuwe lijn: ?subject wikibase:sitelinks [] . Wat ik daar eigenlijk wil doen is filteren op entiteiten (en dus geen statements en zo in de resultaten zien). Wat je daarvoor logischerwijs zou doen is iets van de vorm ?subject rdf:type wikibase:Item . Dit geeft echter geen resultaten, omdat rdf:type niet bijgehouden wordt voor onder andere entiteiten voor performantie redenen.
Willen we de 40 andere eens van naderbij bekijken, gebruiken we de volgende query:
SELECT DISTINCT ?old ?new WHERE { ?old owl:sameAs ?new . FILTER NOT EXISTS { ?new wikibase:sitelinks [] } }
Eens rondneuzen in die resultaten toont ons al snel dat het hier gaat om dubbele redirects: redirects die op hun beurt weer wijzen naar een redirect! Stopt deze trein ooit of blijven we gaan, vragen we ons af met deze query:
SELECT DISTINCT ?old ?new ?newer WHERE { ?old owl:sameAs ?new . FILTER NOT EXISTS { ?new wikibase:sitelinks [] } ?new owl:sameAs ?newer . ?newer wikibase:sitelinks [] . }
Deze geeft gelukkig alle 40 resultaten. Wat dit betekent, is dat 40 redirects op hun beurt zelf weer verwijzen naar een andere redirect, maar dat die op hun beurt dan wel allemaal wijzen naar een gewone entiteit.
We kunnen dus concluderen dat we 1858 redirects hebben naar entiteiten, en 40 redirects die via die voorgaande redirects op hun beurt naar een doelentiteit wijzen.

Entity aanpassingen query

Hoeveel aanpassingen gaan we dan zo in totaal moeten doorvoeren? We zoeken specifiek de entiteiten waar we statements voor moeten aanpassen omdat de Wikibase Intergrator Python API steeds vertrekt vanuit het aanpassen van entiteiten. Rechtstreekse aanpassing van claims via wbsetclaim is momenteel in geen enkele actieve python API ondersteund. Op zich helpt dit ook wel om een logisch overzicht te houden van wat we precies aanpassen. De volgende query geeft ons een antwoord:
SELECT (COUNT(distinct *) as ?modifications) WHERE { ?old owl:sameAs ?new . ?subject ?predicate ?old . ?subject wikibase:sitelinks [] . # Needed to filter on entities }
Dit geeft ons een resultaat van 106 048.
Willen we nu de exacte aanpassingen die we moeten doorvoeren, gebruiken we de volgende query:
SELECT ?subject ?predicate ?old ?new WHERE { ?old owl:sameAs ?new . ?subject ?predicate ?old . ?subject wikibase:sitelinks [] . # Needed to filter on entities }
Dit geeft ons dus wel ook alle dubbele redirects.
We kunnen dit opsplitsen in twee versies:
Ten eerste geeft de volgende query alle aanpassingen gerelateerd aan enkele redirects:
SELECT ?subject ?predicate ?old ?new WHERE { ?old owl:sameAs ?new . ?subject ?predicate ?old . ?subject wikibase:sitelinks [] . # Needed to filter on entities ?new wikibase:sitelinks [] . }
Dit geeft 105 321 resultaten.
Ten tweede geeft de volgende query de aanpassingen gerelateerd aan dubbele redirects:
SELECT ?subject ?predicate ?old ?new WHERE { ?old owl:sameAs ?middle . FILTER NOT EXISTS { ?middle wikibase:sitelinks [] } ?middle owl:sameAs ?new . ?new wikibase:sitelinks [] . ?subject ?predicate ?old . ?subject wikibase:sitelinks [] . }
Deze geeft 727 resultaten.

Uitwerking in python

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 redirect_query_result_to_edit_list # 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@h9lqbjruoclc9h3dht6p3td6rkr7t0k1') wbi = WikibaseIntegrator(login=login) single_redirect_query = """ SELECT ?subject ?predicate ?old ?new WHERE { ?old owl:sameAs ?new . ?subject ?predicate ?old . ?subject wikibase:sitelinks [] . # Needed to filter on entities ?new wikibase:sitelinks [] . } """ double_redirect_query = """ SELECT ?subject ?predicate ?old ?new WHERE { ?old owl:sameAs ?middle . FILTER NOT EXISTS { ?middle wikibase:sitelinks [] } ?middle owl:sameAs ?new . ?new wikibase:sitelinks [] . ?subject ?predicate ?old . ?subject wikibase:sitelinks [] . } """ single_redirect_query_result = execute_sparql_query(single_redirect_query) double_redirect_query_result = execute_sparql_query(double_redirect_query) # Deze hulpfunctie zet elke query result om naar een lijst met elementen # van de vorm: [subject_id, old_id, new_id, predicate_id] # Dit betekent dat bij subject_id, predicate id statement moet aangepast worden # om old_id te vervangen door new_id edit_list = redirect_query_result_to_edit_list(single_redirect_query_result) edit_list += redirect_query_result_to_edit_list(double_redirect_query_result) total = len(edit_list) for index, [subject_id, old_id, new_id, predicate_id] in enumerate(edit_list): print(f'Changing {predicate_id} on {subject_id}: {old_id} -> {new_id} ({index + 1}/{total})') subject = wbi.item.get(subject_id) claims = subject.claims.get(predicate_id) for claim in claims: if claim.mainsnak.datavalue['value']['id'] == old_id: claim.mainsnak.datavalue['value']['id'] = new_id claim.mainsnak.datavalue['value']['numeric-id'] = new_id[1:] try: subject.write(is_bot=True) except Exception as err: print(f"Error while writing: {err}, {type(err)}")
De corresponderende hulpfunctie in utils.py :
def redirect_query_result_to_edit_list(query_result): edit_list = [] for row in query_result['results']['bindings']: edit_row = [] edit_row.append(entity_id_from_uri(row['subject']['value'])) edit_row.append(entity_id_from_uri(row['old']['value'])) edit_row.append(entity_id_from_uri(row['new']['value'])) edit_row.append( entity_id_from_uri( row['predicate']['value'], prefix='https://kg.kunsten.be/prop/direct/' ) ) edit_list.append(edit_row) return edit_list
We krijgen af en toe een error bij write, indicatie dat er andere dingen mis zijn met die entiteit (zoals bijvoorbeeld andere claims die verwijzen naar onbestaande entiteiten). Momenteel negeer ik die even, maar kan daar later een lijstje van maken en dieper onderzoek naar doen.
Op basis van een snelle berekening zou dit script ongeveer 10 uur moeten draaien om alle aanpassingen door te voeren :').
Errors
Ik liet dit script gedurende de nodige tijd draaien. Om geen onnodige elektriciteit te verspillen, liet ik het alleen lopen als ik zelf ook gebruik maakte van de PC. Uiteindelijk was hij klaar en bleven er (slechts) 531 aanpassingen over.
Door het scriptje nog eens te draaien, en nu mooi weg te schrijven naar een csv bestand, kreeg ik een mooi overzicht.
In utils.py :
import csv class Logger: def __init__(self, filename='log.csv'): self.filename = filename def write_row(self, row): with open(self.filename, mode='a') as csv_file: writer = csv.writer(csv_file) writer.writerow(row)
Toevoegingen in fix_redirects.py :
logger = Logger('fix_redirects_errors.csv') logger.write_row(['subject_id', 'old_id', 'new_id', 'predicate_id', 'error_type', 'error_message']) ... except Exception as err: logger.write_row([subject_id, old_id, new_id, predicate_id, type(err), err])
Alle resultaten staan in het volgende csv bestand:
Dit maakt duidelijk dat er twee soorten errors zijn:
1.
Er zijn 403 errors van de vorm wikibaseintegrator.wbi_exceptions.ModificationFailed , met allemaal een error message van de vorm 'Q157581 not found'.
2.
Er zijn 128 errors van de vorm wikibaseintegrator.wbi_exceptions.MWApiError , met allemaal een error message van de vorm [39f180df7911d7994a460b80] Caught exception of type Wikibase\\DataModel\\Services\\Lookup\\UnresolvedEntityRedirectException
Voor geval 1 is dit te wijten aan de aanwezigheid van een onbestaande entiteit in de properties van de activiteit die we willen aanpassen. Dit staat los van onze huidige aanpassing, maar de API weigert de aanpassing omdat we de originele "fout" mee willen schrijven en eist dat we deze eerst ook oplossen. Voorbeeld is te zien hier bij P41 Q157581 (wat een entiteit is die niet bestaat).
Voor geval 2 lijkt dit te wijten aan dubbele redirects in de qualifiers. Dubbele redirects zijn blijkbaar een issue bij WikiMedia / Wikidata, waarbij deze in vele delen van de implementatie slechts 1 stap gevolgd worden. In dit geval, is de qualifier op een property een redirect naar een redirect, en zegt de API dus dat er niet naar een correcte entiteit verwezen wordt, aangezien de tweede redirect niet gevolgd wordt. Voorbeeld is te zien hier, bij P42 (brought by) Q111798, met qualifier Q69238 die zelf een dubbele redirect is via Q70059 naar Q70640.
Geval 2 kan normaal gezien vlot opgelost worden…

Redirect kettingen eruit!

De eerste query van dit document toonde dat we met een heel schappelijk aantal redirects zitten: 1898.
Daarin zitten ook de redirects die zelf terug wijzen naar een redirect. Als we een manier vinden om die redirects zelf aan te passen, kunnen we deze steeds laten wijzen naar de volgende in de rij, tot dat er geen enkele dubbele redirect meer in zit.
We kunnen onze query voor het detecteren van dubble redirects hergebruiken (redirects die wijzen naar een redirect die zelf wel wijst naar een entiteit). Als we deze dan aanpassen om te wijzen naar de entiteit, zijn deze dubbele redirects opgelost en zijn alle andere kettingen eentje korter. We kunnen dan deze 2 stappen (query + aanpassen) recursief blijven uitvoeren totdat uiteindelijk alle redirects enkelvoudig zijn. Dit wordt verduidelijkt in de onderste tekening (groen = enkelvoudig, geel = dubbel, rood = drievoudig):
Probleem is dat de API voor redirects (wbcreateredirect ) niet rechtstreeks ondersteund wordt door Wikibase Integrator. Volgende stap is dus om de hulpfunctie mediawiki_api_call_helper van Wikibase Integrator te gebruiken om manueel deze call uit te voeren:
from wikibaseintegrator import WikibaseIntegrator from wikibaseintegrator.wbi_config import config from wikibaseintegrator.wbi_helpers import execute_sparql_query, mediawiki_api_call_helper from wikibaseintegrator.wbi_login import Login from utils import double_redirects_query_result_to_edit_list # 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@h9lqbjruoclc9h3dht6p3td6rkr7t0k1') wbi = WikibaseIntegrator(login=login) query = """ SELECT DISTINCT ?old ?new ?newer WHERE { ?old owl:sameAs ?new . FILTER NOT EXISTS { ?new wikibase:sitelinks [] } ?new owl:sameAs ?newer . ?newer wikibase:sitelinks [] . } """ query_result = execute_sparql_query(query) edit_list = double_redirects_query_result_to_edit_list(query_result) total = len(edit_list) for index, [old_id, new_id, newer_id] in enumerate(edit_list): print(f'Changing redirect {old_id} to point directly to {newer_id} instead of {new_id} ({index + 1}/{total})') params = { 'action': 'wbcreateredirect', 'from': old_id, 'to': newer_id, } result = mediawiki_api_call_helper(data=params, login=login) print(result
Dit ging een pak gemakkelijker dan verwacht! De mediawiki_api_call_helper werkt echt als een droom en verbergt alle frustrerende delen van API communicatie (login, tokens…).
Eerste run geeft ons:
Changing redirect Q268503 to point directly to Q1318155 instead of Q268504 (1/40) {'success': 1, 'redirect': 'Q1318155'} ... Changing redirect Q1446479 to point directly to Q498728 instead of Q160152 (40/40) {'success': 1, 'redirect': 'Q498728'}
Volgende runs geven ons niets, want er waren enkel kettingen van maximum lengte 2. Normaal gezien moeten de errors van type 2 in het aanpassing script nu opgelost zijn. We runnen het nog eens en inderdaad, nu nog slechts 403 errors over, enkel die met een niet bestaande entiteit in de properties! Hoera!
Uitbreiding voor redirects bij qualifiers
De huidige implementatie gaat ervan uit dat de redirects aangepast moeten worden in properties op entiteiten. Het kan echter ook gaan om properties op properties: de zogeheten qualifiers. Deze komen nu niet tevoorschijn na onze query en zouden een andere techniek nodig hebben om aangepast te worden via de API.
Aangezien we in de vorige sectie succesvol rechtstreeks de wikidata API hebben gebruikt dankzij mediawiki_api_call_helper van Wikibase Integrator, kunnen we voor deze taak eens kijken of we toch ook rechtstreeks op claim / statement niveau kunnen werken. Het idee zou dan zijn:
1.
Query maken die alle statements opvraagt die een verwijzing naar een redirect bevatten.
2.
Deze statements opvragen via wb_get_claims
3.
De statements aanpassen
4.
De statements uploaden via wb_set_claim
Als dit lukt, zou dit in principe ook meteen de redirects bij entiteiten moeten doorvoeren (aangezien we nu gewoon alle statements met redirects aanpassen, of het nu gaat om properties op entities of qualifiers). De reden dat we voordien eerst enkel focusten op de entiteiten, is omdat dit deel mooi ondersteund werd door de high level API van Wikibase Integrator en ik nog niet wist hoe complex het ging zijn om rechtstreeks de Wikibase API aan te spreken. Bijleren is een reis he :). In ieder geval fijn dat Wikibase Intergrator zowel een ideale library is voor simpel high level werk als om op een eenvoudigere manier rechtstreeks de low level API aan te spreken!

Statement aanpassingen query

De volgende query geeft ons alle statements die een verwijzing naar een redirect bevatten:
SELECT DISTINCT ?subject ?old ?new ?predicate WHERE { ?old owl:sameAs ?new . ?subject ?predicate ?old . ?subject wikibase:rank [] . }
We hebben hier opnieuw een trucje moeten gebruiken om te filteren op statements: ?subject wikibase:rank [] . De reden waarom vind je hier.
Let op, want deze query is groot: in totaal zijn er 602 190 resultaten en duurt het ongeveer een volle minuut om uit te voeren!

Uitwerking in python

import json from wikibaseintegrator import WikibaseIntegrator from wikibaseintegrator.wbi_config import config from wikibaseintegrator.wbi_helpers import ( execute_sparql_query, mediawiki_api_call_helper, ) from wikibaseintegrator.wbi_login import Login from utils.csv_logger import Logger from utils.utils import statement_redirect_query_result_to_edit_list # 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) query = """ SELECT DISTINCT ?subject ?old ?new WHERE { ?old owl:sameAs ?new . ?subject ?predicate ?old . ?subject wikibase:rank [] . } """ query_result = execute_sparql_query(query) edit_list = statement_redirect_query_result_to_edit_list(query_result) logger = Logger('fix_statement_redirects_errors.csv') logger.write_row(['statement_id', 'old_id', 'new_id', 'error_type', 'error_message']) total = len(edit_list) for index, [statement_id, old_id, new_id] in enumerate(edit_list): print( f'Changing statement {statement_id} to accomodate redirect: {old_id} -> {new_id} ({index + 1}/{total})' ) params = { 'action': 'wbgetclaims', 'claim': statement_id, } result = mediawiki_api_call_helper(data=params, login=login, is_bot=True) result_json = json.dumps(list(result['claims'].values())[0][0]) # Het lijkt alsof enkel de numeric-id aanpassen ook voldoende is, # maar voor het zeker toch beide nog doen updated_json = result_json.replace(f'"{old_id}"', f'"{new_id}"') updated_json = updated_json.replace(f'"numeric-id": {old_id[1:]}', f'"numeric-id": {new_id[1:]}') params = { 'action': 'wbsetclaim', 'claim': updated_json, 'summary': 'update value to accomodate redirect', } try: mediawiki_api_call_helper(data=params, login=login, is_bot=True) except Exception as err: logger.write_row([statement_id, old_id, new_id, type(err), err]) print(err)
Het duurde even om wbgetclaims aan de praat te krijgen. Dit omdat de statement GUID die je krijgt van de SPARQL query niet exact de GUID is die deze API call wil als argument voor de claim parameter: je moet de eerste keer dat - voorkomt in de GUID van de query dit symbool vervangen door $ . Ik heb dit gevonden door trial en error en door manueel in de API te duiken, zou wel ergens duidelijker mogen gedocumenteerd worden :'). De aanpassing gebeurt in de volgende hulpfunctie:
def statement_redirect_query_result_to_edit_list(query_result): edit_list = [] for row in query_result["results"]["bindings"]: edit_row = [] # Statement GUIDs are returned by query as # Q1334257-ABD96D2C-0021-4908-915B-E6BD1D3EEA9F # but wbgetclaims / wbsetclaim expects them of the form # Q1334257$ABD96D2C-0021-4908-915B-E6BD1D3EEA9F edit_row.append( entity_id_from_uri( row["subject"]["value"], prefix="https://kg.kunsten.be/entity/statement/", ).replace('-', '$', 1) ) edit_row.append(entity_id_from_uri(row["old"]["value"])) edit_row.append(entity_id_from_uri(row["new"]["value"])) edit_list.append(edit_row) return edit_list
Ok, het plan lijkt te werken, maar zal wel even duren (~50 uur).

Dat kan sneller: multiprocessing

De API calls nemen wel wat tijd in beslag. Aangezien ik toch nog 40 uur ging moeten wachten tot het klaar was, besloot ik eens uit te zoeken of dit te parallelliseren was.
Aanvankelijk maakte ik een versie die op meerdere threads API calls deed met de Python threading module. Dit gaf echter veel API errors, vermoedelijk omdat de Wikibase Integrator niet threadsafe is, en ik die functies dus niet gelijktijdig vanaf verschillende threads mag aanroepen.
Als ik het over een andere boeg gooide, en mijn script gewoon twee keer naast elkaar liet draaien, leek dit wel goed (en snel) te werken.
Uiteindelijk maakte ik een versie met de multiprocessing python module. Het verschil met de threading module is dat elke uitvoering een afzonderlijk python process is. Als ik dan per process een aparte Login en WikibaseIntegrator aanmaakte, kreeg ik mooi snellere resultaten zonder errors! De uiteindelijke code:
import json import math import multiprocessing import time from wikibaseintegrator import WikibaseIntegrator from wikibaseintegrator.wbi_config import config from wikibaseintegrator.wbi_helpers import ( execute_sparql_query, mediawiki_api_call_helper, ) from wikibaseintegrator.wbi_login import Login from utils.csv_logger import Logger from utils.utils import statement_redirect_query_result_to_edit_list # Het aantal processen dat je wilt gebruiken (voor snellere werking) N_PROCESSES = 1 # 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) query = """ SELECT DISTINCT ?subject ?old ?new WHERE { ?old owl:sameAs ?new . ?subject ?predicate ?old . ?subject wikibase:rank [] . } """ query_result = execute_sparql_query(query) edit_list = statement_redirect_query_result_to_edit_list(query_result) def task(edit_list, process_index): # De login van de bot account (niet zomaar online zetten / publiek delen!) login = Login(user='WALL-E', password='DELETER@h9lqbjruoclc9h3dht6p3td6rkr7t0k1') wbi = WikibaseIntegrator(login=login, is_bot=True) logger = Logger(f'fix_statement_redirects_errors_process_{process_index}.csv') logger.write_row(['statement_id', 'old_id', 'new_id', 'error_type', 'error_message']) total = len(edit_list) print(f'Starting process {process_index} with edit list of length {total}') for index, [statement_id, old_id, new_id] in enumerate(edit_list): print( f'Process {process_index}: Changing statement {statement_id} to accomodate redirect: {old_id} -> {new_id} ({index + 1}/{total})' ) params = { 'action': 'wbgetclaims', 'claim': statement_id, } result = mediawiki_api_call_helper(data=params, login=login, is_bot=True) result_json = json.dumps(list(result['claims'].values())[0][0]) # Het lijkt alsof enkel de numeric-id aanpassen ook voldoende is, # maar voor het zeker toch beide nog doen updated_json = result_json.replace(f'"{old_id}"', f'"{new_id}"') updated_json = updated_json.replace(f'"numeric-id": {old_id[1:]}', f'"numeric-id": {new_id[1:]}') params = { 'action': 'wbsetclaim', 'claim': updated_json, 'summary': 'update value to accomodate redirect', } try: mediawiki_api_call_helper(data=params, login=login, is_bot=True) except Exception as err: logger.write_row([statement_id, old_id, new_id, type(err), err]) print(err) window_size = math.ceil(len(edit_list) / N_PROCESSES) processes = [] start = time.time() for i in range(N_PROCESSES): p = multiprocessing.Process(target=task, args=(edit_list[(i * window_size):(i * window_size + window_size)],i)) p.start() processes.append(p) # Nodig zodat wikibase niet klaagt dat we te snel meerdere keren aan het inloggen zijn :) time.sleep(0.2) for p in processes: p.join() print(f'Processing of {len(edit_list)} statements done in {time.time() - start} seconds over {N_PROCESSES} processes.')
Je kan gemakkelijk kiezen hoeveel processen je wilt gebruiken met de N_PROCESSES variabele. Ik heb hier voor het plezier wat statistiekjes over verzameld:
Dit maakte de taak meer dan 5 keer zo snel, en op het einde van de dag was het script klaar :).

Resultaat

Alle statements met verwijzingen werden succesvol aangepast om rechtstreeks te wijzen naar de doel entiteit. Het valt ook op dat dit script geen errors heeft gegeven, wat betekent dat alle aanpassingen gelukt zijn. Opnieuw runnen van het script geeft inderdaad 0 resultaten bij de initiële query.
Herinner u de errors die we nog kregen toen we met de API op entiteit niveau werkten:
Er zijn 403 errors van de vorm wikibaseintegrator.wbi_exceptions.ModificationFailed , met allemaal een error message van de vorm 'Q157581 not found'.
Deze zijn bij de nieuwe versie van het script wel allemaal gelukt, omdat de error in dit geval steeds te maken had met een ander statement op die entiteiten dat naar een onbestaande entiteit wees. Als we dus nu het oude script runnen, zegt deze ook dat er niets meer hoeft aangepast worden. Deze foute verwijzingen naar "spookentiteiten" zijn er wel nog steeds, dus is het wel nog steeds interessant om uit te zoeken hoe we deze allemaal tevoorschijn toveren met een query en hoe we de spookverwijzingen willen aanpakken!

Laatste check

Met de volgende query vinden we alles dat nog zou wijzen naar een redirect:
SELECT DISTINCT ?subject ?old ?new WHERE { ?old owl:sameAs ?new . ?subject ?predicate ?old . }
Deze geeft de volgende 7 resultaten:
subject
old
new
predicate
https://kg.kunsten.be/reference/516b57fb5b5806040b565eb8871e55e48f6935e5
https://kg.kunsten.be/entity/Q70575
https://kg.kunsten.be/entity/Q69252
https://kg.kunsten.be/prop/reference/P15
https://kg.kunsten.be/reference/e5234bb1a6f39aae7819036635587812f5f042e7
https://kg.kunsten.be/entity/Q59547
https://kg.kunsten.be/entity/Q59544
https://kg.kunsten.be/prop/reference/P10
https://kg.kunsten.be/reference/a48d09e002ba76a3219fd49865b5ab9fe8c358d2
https://kg.kunsten.be/entity/Q59547
https://kg.kunsten.be/entity/Q59544
https://kg.kunsten.be/prop/reference/P62
https://kg.kunsten.be/reference/64882c02442f18dd41dbee5282ea35bb30389a0d
https://kg.kunsten.be/entity/Q63182
https://kg.kunsten.be/entity/Q113660
https://kg.kunsten.be/prop/reference/P62
https://kg.kunsten.be/reference/99668cf989a53b19d2867f7efbe7e9614de5e2c7
https://kg.kunsten.be/entity/Q69774
https://kg.kunsten.be/entity/Q523406
https://kg.kunsten.be/prop/reference/P15
https://kg.kunsten.be/reference/6b6dfbab2c4295525289faaf92ad26f4812a7b07
https://kg.kunsten.be/entity/Q70623
https://kg.kunsten.be/entity/Q523406
https://kg.kunsten.be/prop/reference/P15
https://kg.kunsten.be/entity/Q268503
https://kg.kunsten.be/entity/Q268504
https://kg.kunsten.be/entity/Q1318155
http://www.w3.org/2002/07/owl#sameAs
Het gaat om 6 references en 1 redirect.
De 6 references lijken me eventueel voldoende om met de hand te doen, al zal dit waarschijnlijk ook wel weer mogelijk zijn met een aangepaste query om de bijhorende statement te vinden en de nodige API calls. Op het eerste zicht lijken wel slechts 2 van deze references echt aan een statement te hangen (zie resultaten van de gelinkte query).
Die redirect wijst zogezegd nog steeds zelf naar een redirect, maar in de praktijk lijkt dit niet het geval als ik doorklik.
Zeer vreemd, misschien een bug in de database? Ik was deze ook al eens tegengekomen toen ik de kettingen aan het oplossen was…
Update: ik had nog wat tests geschreven die tijdelijk terug wat fouten invoegden om dan terug te kunnen verwijderen, en dit lijkt nu ook het geval met 1 enkele statement aanpassing voor redirects. Dus de query geeft 1 resultaat dat nog niet in orde zou zijn, maar in de praktijk lijkt dit wel al OK. Zou dit een bug kunnen zijn in de Query service of database? Of een cache probleem of zo? De bovenste query geeft nu 9 resultaten, waarvan er twee gaan over diezelfde statement (entity niveau en de statement zelf worden apart in de query teruggegeven).
Redirect pagina's zelf verwijderen als alles aangepast is?
Dit gaan we zeker niet doen, aangezien we niet weten of er externe databases zijn die nog verwijzen naar het oude Q nummer!
Samenvatting
Redirect kettingen kunnen gemakkelijk opgelost worden door alle redirects zelf aan te passen zodat ze wijzen naar de laatste entiteit in de ketting. Dit kan gemakkelijk recursief en door gebruik te maken van wbcreateredirect . Zo wijst uiteindelijk elke redirect naar een entiteit en nooit naar een andere redirect.
Om dan de verwijzingen naar redirects te vervangen door de doelentiteit in entiteiten en qualifiers kunnen we het recentste script gebruiken (diegene die gebruik maakt van wbgetclaims en wbsetclaim ). Deze past immers redirects aan in alle statements, dus zowel statements die rechtstreeks van een entiteit naar een redirect wijzen als statements met qualifiers die naar een redirect wijzen. Indien het om grote aantallen gaat, kan je dit script uitvoeren met multiprocessing om sneller door de aanpassingen te gaan.
Er zijn nog enkele verwijzingen naar redirects in references, dit zijn er momenteel 6 en kunnen dus ofwel manueel gedaan worden, ofwel met een verdere uitbreiding van het recentste script.
Er zijn nog steeds entiteiten die verwijzen naar entiteiten die niet bestaan. Dit is een probleem dat los staat van de redirects, maar dat wel tijdens dit werk naar boven is gekomen.