Sunday Query : utiliser SPARQL et Python pour corriger des coquilles sur Wikidata

À mon tour de faire une #SundayQuery! Comme Harmonia Amanda l’a dit dans son propre billet, j’envisageais de faire un article expliquant comment créer un script Python permettant de corriger les résultats de sa requête.. Finalement, j’ai préféré en faire un autre, au fonctionnement similaire mais plus court et plus simple à comprendre. Le script pour Harmonia est cependant disponible en ligne ici.

Jeudi, j’ai publié un article au sujet des  batailles du Moyen Âge, et depuis, j’ai commencé à corriger les éléments correspondants sur Wikidata

L’une des corrections les plus répétitives était la capitalisation des libellés en français : comme ils ont été importés de Wikipédia, ils ont une majuscule inutile au début ( « Bataille de Saint-Pouilleux en Binouze » instead of « bataille de Saint-Pouilleux en Binouze »…)

La requête

Commençons par trouver tous les éléments présentant cette coquille.

http://tinyurl.com/jljf6xr

Quelques explications de base :

  • ?item wdt:P31/wdt:P279* wd:Q178561 .  cherche les éléments qui sont des batailles ou des sous-classes de batailles, pour être bien sûr que je ne vais pas virer sa majucule à un bouquin intitulé  « Bataille de Perpète-les-Olivettes »…
  • Sue la ligne suivante, je demande les libellés pour les éléments en question  ?item rdfs:label ?label .  et les filtre pour ne garder que ceux en français FILTER(LANG(?label) = "fr") . . Comme j’ai besoin d’utiliser le libellé dans la requête et pas juste de l’afficher (et comme Harmonia Amanda l’a expliqué dans son billet de dimanche), je ne peux pas utiliser le servicce wikibase:label, et je me rabats donc sur le standard du web sémantique rdfs:label.
  • La dernière ligne est un FILTER  (filtre), qui ne garde que les résultats qui répondent à la fonction à l’intérieur. Ici, STRSTARTS  vérifie si ?label  commence avec "Bataille " .

Au moment où j’ai écrit la version anglaise de ce texte, la requête renvoyait 3521 résultats. Beaucoup trop pour les corriger à la main, et je ne connais aucun outil déjà existant qui pourrait faire ça pour moi… Je suppose qu’il est temps de dégainer Python, du coup !

Le script Python

J’aime Python. J’adore carrément Python, même. Ce langage est génial pour créer une une application utile en une poignée de minutes, facile à lire (pour peu qu’on lise l’anglais) et pas constellé de séries d’accolades ou de points-virgules, et a des libs géniales pour les choses que je fais le plus avec : récupérer le contenus de pages web, trier des données, vérifier des ISBNs [1]J’espère pouvoir bientôt quelque chose ici sur ce sujet. et faire des sites web. Oh, et pour faire des requêtes SPARQL et traiter les résultats [2]En plus, les exemples dans la doc officielle sont basés sur Firefly. Yes sir, Captain Tightpants..

Deux charmeurs de serpent avec un python et deux cobras.
Et puis le nom du langage a un petit côté « charmeur de serpents » 😉

Premières remarques

Si vous ne connaissez pas du tout le Python, cet article n’est pas le bon endroit pour ça, mais il y a de nombreuses ressources disponibles en ligne [3]Par exemple, https://www.codecademy.com/learn/python ou https://docs.python.org/3.5/tutorial/.. Assurez-vous juste qu’elles sont à jour et pensées pour Python 3. La suite de cet article part du principe que vous avez une connaissance basique de Python (indentation, variables, chaînes de caractères, listes, dictionnaires, imports et boucles for.), et que Python 3 et pip sont installés sur votre machine.

Pourquoi Python 3 ? Parce que nous allons manipuler des chaînes qui viennent de Wikidata et sont donc encodées en UTF-8 et que Python 2 n’est pas hyper pratique pour ça. Et puis mince, on est en 2016, par Belenos !

Pourquoi pip ? Parce qu’on a besoin d’une libraire non-standard pour faire des requêtes SPARQL, appelée SPARQLwrapper, et que cette commande est le moyen le plus simple de l’installer :

Allez, on commence à scripter !

Pour commencer, un script qui fait une requête Sparql retournant la liste des sièges à corriger [4]Oui, les sièges, j’ai déjà corrigé les batailles avant d’écrire le billet 😉 :

Ça fait un bon petit paquet de lignes, mais que font-elles ? Comme on va le voir, la plupart vont en fait être incluses à l’identique dans tout script qui fait une requête  SPARQL.

  • Pour commencer, on importe deux choses du module  SPARQLWrapper : la classe SPARQLWrapper elle-même et la constante « JSON » qu’elle va utiliser plus tard (pas d’inquiétude, on n’aura pas à manipuler du json directement.)
  • On import aussi le module « Pretty printer » pour afficher les résultats de manière plus lisible.
  • Ensuite, on crée une variable qu’on nomme « endpoint », qui contient l’URL complète vers le point d’accès SPARQL de Wikidata [5]Et non son accès web qui est simplement « https://query.wikidata.org/ ».
  • Ensuite, on crée une instance de la classe SPARQLWrapper qui utilisera ce point d’accès pour faire des requêtes, et on les met dans une variable simplement appelée  « sparql ».
  • On applique à cette variable la fonction setQuery, qui est l’endroit où l’on rentre la requête de tout à l’heure. Attention, il faut doublonner les accolades (remplacer { et } par {{ et }}, car elles sont des caractères réservés dans les chaînes Python.
  • sparql.setReturnFormat(JSON)  dit au script que le résultat sera retourné en json.
  • results = sparql.query().convert() , enfin, fait la requête elle-même et convertit la réponse dans un dictionnaire Python appelé  « results ».
  • Et pour l’instant, on va juste afficher le résultat à l’écran pour voir ce qu’on obtient.

Ouvrons un terminal et lançons le script :

C’est un gros paquet de résultats mais on peut voir que c’est un dictionnaire qui contient deux entrées :

  • « head », qui contient les noms des deux variables renvoyées par la requête,
  • et « results », qui contient lui-même un autre dictionnaire avec la clef « bindings », associée avec la liste des résultats eux-mêmes, chacun d’entre eux étant lui-même un dictionnaire  Python. Pfiou…

Examinons un desdits résultats :

C’est un dictionnaire avec deux clefs (label et item), chacune ayant pour valeur un autre dictionnaire qui à son tour a une clef « value » associée avec, cette fois, la valeur qu’on veut au final. Enfin !

Parcourir les résultats

Parcourons la liste « bindings » avec une boucle « for » de Python, pour pouvoir en extraire les résultats.

Rapide explication sur la ligne  qid = result['item']['value'].split('/')[-1]  : comme l’identifiant de l’élément est en fait stocké sous la forme d’une URL complète (« https://www.wikidata.org/entity/Q17627724 » et pas juste « Q17627724 »), il nous faut séparer cette chaîne à chaque caractère ‘/’, ce qu’on fait à l’aide de la fonction « split()« , qui transforme la chaîne en une liste Python contenant ceci :

Nous ne voulons que le dernier élément de cette liste. En Python, c’est celui avec l’index -1, d’où le [-1] à la fin de la ligne. Enfin, nous stockons cette valeur dans la variable qid.

Lançons le script ainsi modifié :

Corriger le problème

On y est presque ! Maintenant, il reste à remplacer cet orgueilleux « S » majuscule par un plus modeste « s » minuscule :

Que se passe-t-il ici ? Une chaîne Python fonctionne comme une liste, on peut donc lui demander de prendre la partie située entre le début de la chaîne « label » et la position qui suit le premier caractère (« label[:1] ») et forcer celui-ci en bas-de-casse (« .lower() »). Ensuite, on y concatène le reste de la chaîne (de la position 1 à la fin, donc « label[1:] ») et on réassigne ce résultat à la variable « label ».

Dernière chose, formater le résultat de manière compatible à QuickStatements:

Cette ligne semble barbare ? Elle est en fait assez simple : "{}\tLfr\t{}"  est une chaîne qui contient un premier emplacement pour le résultat d’une variable (« {} »), puis une tabulation, (« \t »), puis le mot-clef Quickstatements pour le libellé français (« Lfr »), une autre tabulation et enfin le second emplacement pour une variable. Ensuite, la fonction « format() » se charge de mettre le contenu des variables « qid » et « label » dedans. Le script final devrait ressembler à ça :

C’est parti :

On est bons ! Il ne reste plus qu’à copier-coller le résultat dans QuickStatements et attendre qu’il fasse le boulot tout seul.

Image à la une : Photographie de fontes de caractères par Andreas Praefcke (domaine public)

Enregistrer

Enregistrer

Notes   [ + ]

1. J’espère pouvoir bientôt quelque chose ici sur ce sujet.
2. En plus, les exemples dans la doc officielle sont basés sur Firefly. Yes sir, Captain Tightpants.
3. Par exemple, https://www.codecademy.com/learn/python ou https://docs.python.org/3.5/tutorial/.
4. Oui, les sièges, j’ai déjà corrigé les batailles avant d’écrire le billet 😉
5. Et non son accès web qui est simplement « https://query.wikidata.org/ »

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *