# Dependances pre-provisionnees (SPARQLWrapper) : voir SemanticWeb/requirements.txt ; imports dans les cellules suivantes.SW-5b-Python-LinkedData
Navigation : Index | << SW-5 C# | SW-6 C# >>
Données Liees du Web en Python avec SPARQLWrapper
Ce notebook est un sidetrack optionnel qui presente l’equivalent Python des concepts de données liees du notebook SW-5 (dotNetRDF). Vous y decouvrirez SPARQLWrapper pour interroger des endpoints publics comme DBpedia et Wikidata.
Objectifs d’apprentissage
A la fin de ce notebook, vous saurez : 1. Interroger des endpoints SPARQL distants avec SPARQLWrapper 2. Explorer DBpedia (données structurees de Wikipedia) 3. Interroger Wikidata (base de connaissances collaborative) 4. Faire la correspondance entre dotNetRDF et SPARQLWrapper
Prerequis
- SW-5-CSharp-LinkedData recommande (pour la comprehension conceptuelle)
- Python 3.10+
- Connexion internet (pour acceder aux endpoints publics)
Duree estimee : 25 minutes
Attention : Les endpoints publics peuvent etre temporairement indisponibles. Toutes les requêtes sont enveloppees dans des
try/exceptpour gerer ces cas gracieusement.
1. Installation et Imports
SPARQLWrapper est la bibliotheque de reference pour interroger des endpoints SPARQL distants depuis Python.
Importation de SPARQLWrapper et des constantes de format de retour pour les requêtes SPARQL distantes.
try:
from SPARQLWrapper import SPARQLWrapper, JSON, XML
import json
SPARQLWRAPPER_AVAILABLE = True
print("SPARQLWrapper importe.")
except ImportError:
SPARQLWRAPPER_AVAILABLE = False
print("SPARQLWrapper non disponible. Installez avec : pip install SPARQLWrapper")SPARQLWrapper importe.
Lecture : le contrat de disponibilité
La sortie SPARQLWrapper importe. est le seul texte imprimé en cas de succès — mais le geste important est ailleurs : l’import est enveloppé dans un try/except qui positionne le drapeau SPARQLWRAPPER_AVAILABLE. Chaque cellule suivante du notebook commence par if SPARQLWRAPPER_AVAILABLE: : si la bibliothèque manque, la requête DBpedia la plus spectaculaire se réduit à un message au lieu d’une exception qui arrête l’exécution. C’est le même motif de dégradation progressive que RDFLIB_AVAILABLE dans les notebooks SW-3b et SW-4b : un notebook pédagogique doit rester lisible et parcourable même quand l’environnement est incomplet — et l’enseignant voit immédiatement QUOI installer pour retrouver le comportement complet.
2. Interroger DBpedia
DBpedia extrait des données structurees de Wikipedia et les expose sous forme de triples RDF. L’endpoint SPARQL est accessible a http://dbpedia.org/sparql.
if SPARQLWRAPPER_AVAILABLE:
# Query DBpedia for French cities
sparql = SPARQLWrapper("http://dbpedia.org/sparql")
sparql.setReturnFormat(JSON)
sparql.setQuery("""
PREFIX dbo: <http://dbpedia.org/ontology/>
PREFIX dbr: <http://dbpedia.org/resource/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?city ?name ?population
WHERE {
?city a dbo:City .
?city dbo:country dbr:France .
?city rdfs:label ?name .
?city dbo:populationTotal ?population .
FILTER (lang(?name) = "fr")
}
ORDER BY DESC(?population)
LIMIT 5
""")
try:
results = sparql.query().convert()
bindings = results["results"]["bindings"]
print(f"=== Top 5 villes francaises (DBpedia) ===")
print(f"{'Ville':<25s} {'Population':>12s}")
print("-" * 37)
for r in bindings:
name = r["name"]["value"]
pop = int(r["population"]["value"])
print(f"{name:<25s} {pop:>12,d}")
except Exception as e:
print(f"Erreur lors de la requete DBpedia : {type(e).__name__}: {e}")
print("L'endpoint DBpedia est peut-etre temporairement indisponible.")
print("Cela n'empeche pas de continuer le notebook.")
else:
print("SPARQLWrapper non disponible : requete DBpedia ignoree.")=== Top 5 villes francaises (DBpedia) ===
Ville Population
-------------------------------------
Papeete 26,654
Quartier Saint-Germain-des-Prés 5,154
Gustavia 2,300
Interpretation : Requête DBpedia
Structure de la reponse : SPARQLWrapper retourne un dictionnaire JSON contenant : - results.bindings : liste de dictionnaires, un par ligne de résultat - Chaque variable (?city, ?name, ?population) est une cle avec value et type
Points cles : 1. FILTER (lang(?name) = "fr") limite les labels a la langue francaise 2. LIMIT 5 restreint le nombre de résultats (important pour les endpoints publics) 3. Le try/except est essentiel : les services publics peuvent avoir des timeouts
| Opération | SPARQLWrapper | dotNetRDF |
|---|---|---|
| Créer endpoint | SPARQLWrapper("http://...") |
new SparqlRemoteEndpoint(uri) |
| Format retour | setReturnFormat(JSON) |
Constructeur paramètre |
| Définir requête | setQuery("...") |
Query = "..." |
| Executer | query().convert() |
QueryWithResultSet() |
Exercice (a completer) : Fleuves de France via DBpedia
En vous inspirant de la requête DBpedia de la section 2, modifiez-la pour lister les fleuves de France tries par longueur decroissante. Affichez le nom et la longueur en kilometres.
Indices : - Classe fleuve : dbo:River (a la place de dbo:City) - Propriete longueur : dbo:length ou dbo:riverLength - Gardez le filtre dbo:country dbr:France et FILTER (lang(?name) = "fr") - Endpoint : http://dbpedia.org/sparql avec format JSON - Limitez a LIMIT 10
if SPARQLWRAPPER_AVAILABLE:
# Exercice : Fleuves de France via DBpedia
# TODO etudiant : ecrire une requete SPARQL sur DBpedia
#
# Etape 1 : configurer SPARQLWrapper avec l'endpoint DBpedia
# sparql = SPARQLWrapper("http://dbpedia.org/sparql")
# sparql.setReturnFormat(JSON)
# Etape 2 : ecrire SELECT ?river ?name ?length WHERE { ... }
# ?river a dbo:River .
# ?river dbo:country dbr:France .
# ?river rdfs:label ?name .
# ?river dbo:length ?length . (ou dbo:riverLength)
# FILTER (lang(?name) = "fr")
# ORDER BY DESC(?length) LIMIT 10
# Etape 3 : executer et afficher nom + longueur en km
print("Exercice a completer")
else:
print("SPARQLWrapper non disponible")Exercice a completer
3. Interroger Wikidata
Wikidata est la base de connaissances structuree du projet Wikimedia. Son endpoint SPARQL est a https://query.wikidata.org/sparql.
Différence importante : Wikidata utilise des identifiants numériques (Q-items, P-properties) plutot que des URI lisibles.
if SPARQLWRAPPER_AVAILABLE:
# Query Wikidata for programming languages created after 2010
sparql_wd = SPARQLWrapper("https://query.wikidata.org/sparql")
sparql_wd.setReturnFormat(JSON)
# Add a User-Agent header (required by Wikidata policy)
sparql_wd.agent = "CoursIA-SemanticWeb-Notebook/1.0 (educational)"
sparql_wd.setQuery("""
SELECT ?lang ?langLabel ?inception
WHERE {
?lang wdt:P31 wd:Q9143 . # instance of: programming language
?lang wdt:P571 ?inception . # inception date
FILTER (YEAR(?inception) >= 2010)
SERVICE wikibase:label { bd:serviceParam wikibase:language "fr,en" . }
}
ORDER BY DESC(?inception)
LIMIT 10
""")
try:
results_wd = sparql_wd.query().convert()
bindings_wd = results_wd["results"]["bindings"]
print(f"=== Langages de programmation recents (Wikidata) ===")
print(f"{'Langage':<25s} {'Annee de creation':>20s}")
print("-" * 45)
for r in bindings_wd:
name = r["langLabel"]["value"]
year = r["inception"]["value"][:4] # extract year from date
print(f"{name:<25s} {year:>20s}")
except Exception as e:
print(f"Erreur lors de la requete Wikidata : {type(e).__name__}: {e}")
print("L'endpoint Wikidata est peut-etre temporairement indisponible.")
print("Cela n'empeche pas de continuer le notebook.")
else:
print("SPARQLWrapper non disponible : requete Wikidata ignoree.")=== Langages de programmation recents (Wikidata) ===
Langage Annee de creation
---------------------------------------------
DJPROCODE 2026
Varphi Language 2025
Fremio 2025
Pkl 2024
Jakt 2022
Delegua 2022
Mavka 2022
Carbon 2020
BQN 2020
V 2019
Interpretation : Requête Wikidata
Différences DBpedia vs Wikidata :
| Aspect | DBpedia | Wikidata |
|---|---|---|
| Identifiants | URIs lisibles (dbr:Paris) |
Q-items (wd:Q90 = Paris) |
| Proprietes | Ontologie (dbo:populationTotal) |
P-properties (wdt:P1082) |
| Labels | rdfs:label + FILTER(lang()) |
SERVICE wikibase:label |
| User-Agent | Optionnel | Obligatoire (politique Wikimedia) |
Identifiants Wikidata courants : - wd:Q5 = human - wd:Q9143 = programming language - wd:Q515 = city - wd:Q142 = France - wdt:P31 = instance of - wdt:P571 = inception - wdt:P1082 = population
Note :
SERVICE wikibase:labelest un mécanisme spécifique a Wikidata qui resout automatiquement les labels dans la langue demandee.
Exercice (a completer) : Capitales europeennes et leur pays (Wikidata)
En vous inspirant de la requête Wikidata de la section 3, interrogez Wikidata pour lister les capitales europeennes avec le nom du pays associe. Limitez le résultat a 15 lignes.
Indices : - Classe capitale : wd:Q5119 (capital city) - Continent Europe : wd:Q46 - Propriete “pays” : wdt:P17 - Propriete “continent du pays” : via ?country wdt:P30 wd:Q46 (pays situe en Europe) - Utilisez SERVICE wikibase:label { bd:serviceParam wikibase:language "fr,en" . } pour les labels - Endpoint : https://query.wikidata.org/sparql avec .agent = "CoursIA-SemanticWeb-Notebook/1.0 (educational)"
if SPARQLWRAPPER_AVAILABLE:
# Exercice : Capitales europeennes et leur pays (Wikidata)
# TODO etudiant : ecrire une requete SPARQL sur Wikidata
#
# Etape 1 : configurer SPARQLWrapper avec l'endpoint Wikidata
# sparql = SPARQLWrapper("https://query.wikidata.org/sparql")
# sparql.setReturnFormat(JSON)
# sparql.agent = "CoursIA-SemanticWeb-Notebook/1.0 (educational)"
# Etape 2 : ecrire SELECT ?capitalLabel ?countryLabel WHERE { ... }
# ?capital wdt:P31 wd:Q5119 . # instance de capitale
# ?capital wdt:P17 ?country . # pays de la capitale
# ?country wdt:P30 wd:Q46 . # pays en Europe
# SERVICE wikibase:label pour les labels (langue 'fr,en')
# Etape 3 : executer et afficher capitale + pays
print("Exercice a completer")
else:
print("SPARQLWrapper non disponible")Exercice a completer
4. Requêtes Federees avec SERVICE
SPARQL permet de combiner plusieurs endpoints dans une même requête avec le mot-cle SERVICE.
if SPARQLWRAPPER_AVAILABLE:
# Federated query: combine local graph with remote endpoint
try:
from rdflib import Graph, Namespace
RDFLIB_AVAILABLE = True
except ImportError:
RDFLIB_AVAILABLE = False
if RDFLIB_AVAILABLE:
# Create a small local graph with French city URIs
g_local = Graph()
DBR = Namespace("http://dbpedia.org/resource/")
g_local.bind("dbr", DBR)
# Add some city URIs (we don't have data about them locally)
cities = [DBR.Paris, DBR.Lyon, DBR.Marseille]
for city in cities:
# We'll query DBpedia for their populations
pass
# Note : DBpedia (Virtuoso) bloque les requetes SERVICE auto-federees
# (erreur Virtuoso SQ070:SECURITY). On interroge donc directement DBpedia
# pour recuperer la population des villes de notre graphe local.
# Pour une vraie federation cross-endpoint, utiliser l'evaluation SERVICE
# de rdflib (g_local.query()) plutot que l'endpoint distant.
query_federated = """
PREFIX dbo: <http://dbpedia.org/ontology/>
PREFIX dbr: <http://dbpedia.org/resource/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?city ?name ?population
WHERE {
VALUES ?city { dbr:Paris dbr:Lyon dbr:Marseille }
?city rdfs:label ?name .
?city dbo:populationTotal ?population .
FILTER (lang(?name) = "fr")
}
ORDER BY DESC(?population)
"""
sparql_fed = SPARQLWrapper("http://dbpedia.org/sparql")
sparql_fed.setReturnFormat(JSON)
sparql_fed.setQuery(query_federated)
try:
results_fed = sparql_fed.query().convert()
print("=== Villes francaises (requete federée) ===")
for r in results_fed["results"]["bindings"]:
name = r["name"]["value"]
pop = int(r["population"]["value"])
print(f" {name} : {pop:,d} habitants")
except Exception as e:
print(f"Erreur : {e}")
else:
print("rdflib non disponible : requete federée ignoree.")
else:
print("SPARQLWrapper non disponible : requete federée ignoree.")=== Villes francaises (requete federée) ===
Paris : 2,048,472 habitants
Interpretation : Requêtes federées
Le mot-cle SERVICE permet d’interroger un endpoint distant depuis une requête SPARQL.
SERVICE <http://dbpedia.org/sparql> {
# patterns a evaluer sur DBpedia
}
C’est puissant pour combiner des données locales avec des données distantes, ou pour joindre plusieurs endpoints entre eux.
Equivalent dotNetRDF : Même syntaxe SPARQL, le federated query est gere par le moteur SPARQL.
5. Formats de Retour
SPARQLWrapper supporte différents formats de retour pour les requêtes SPARQL.
if SPARQLWRAPPER_AVAILABLE:
# Compare JSON vs XML return formats
query_simple = """
PREFIX dbo: <http://dbpedia.org/ontology/>
PREFIX dbr: <http://dbpedia.org/resource/>
ASK { dbr:Paris dbo:country dbr:France }
"""
# JSON format
sparql_json = SPARQLWrapper("http://dbpedia.org/sparql")
sparql_json.setReturnFormat(JSON)
sparql_json.setQuery(query_simple)
# XML format
sparql_xml = SPARQLWrapper("http://dbpedia.org/sparql")
sparql_xml.setReturnFormat(XML)
sparql_xml.setQuery(query_simple)
try:
result_json = sparql_json.query().convert()
print(f"Format JSON : {result_json}")
result_xml = sparql_xml.query().convert()
print(f"\nFormat XML (first 200 chars) : {result_xml.toxml()[:200]}...")
except Exception as e:
print(f"Erreur : {e}")
else:
print("SPARQLWrapper non disponible : comparaison formats ignoree.")Format JSON : {'head': {'link': []}, 'boolean': True}
Format XML (first 200 chars) : <?xml version="1.0" ?><sparql xmlns="http://www.w3.org/2005/sparql-results#" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.w3.org/2001/sw/DataAccess/rf1/result2....
Interpretation : Formats de retour
| Format | Constante SPARQLWrapper | Usage |
|---|---|---|
| JSON | JSON |
Format le plus simple a parser en Python |
| XML | XML |
Format standard W3C |
| CSV/TSV | Necessite conversion | Pour l’export vers Excel |
Le format JSON est généralement recommande pour Python car il se convertit directement en dictionnaires.
6. Tableau de Correspondance dotNetRDF / SPARQLWrapper
Ce tableau recapitule les equivalences pour les requêtes vers des endpoints distants.
| Opération | dotNetRDF (C#) | SPARQLWrapper (Python) |
|---|---|---|
| Créer endpoint | new SparqlRemoteEndpoint(uri) |
SPARQLWrapper(uri) |
| Format retour | Constructeur (defaut XML) | setReturnFormat(JSON/XML) |
| Définir requête | Query = "SELECT..." ou SetQuery() |
setQuery("SELECT...") |
| Executer SELECT | QueryWithResultSet() |
query().convert() |
| Executer ASK | AskQuery() |
query().convert() |
| User-Agent | HttpClientHeaders |
.agent = "..." |
| Timeout | Timeout propriete |
setTimeout(seconds) |
Philosophies différentes
| Aspect | dotNetRDF | SPARQLWrapper |
|---|---|---|
| Résultat SELECT | SparqlResultSet (type .NET) |
Dictionnaire Python brut |
| Résultat ASK | bool |
Dictionnaire avec cle boolean |
| Type de retour | Objets fortement types | Structures natives Python |
Exemples et Exercices
Mettez en pratique les SPARQLWrapper avec ces exemples guides et exercices.
Exemple guide 1 : Top 10 villes francaises via Wikidata
Solution proposee par Lilou Mayot (promo 2026)
Voici un exemple de requête Wikidata pour obtenir les 10 villes de France les plus peuplees avec SPARQLWrapper. Notez l’utilisation des Q-items Wikidata et du service de labels.
Éléments cles : - Classe ville : wd:Q515 (city) - Pays : wdt:P17 (country), France = wd:Q142 - Population : wdt:P1082 - SERVICE wikibase:label pour les labels en francais - time.sleep(60) entre les requêtes Wikidata (politique de rate-limiting)
if SPARQLWRAPPER_AVAILABLE:
# Exemple guide 1 : Top 10 villes francaises via Wikidata
import time
sparql_ex1 = SPARQLWrapper("https://query.wikidata.org/sparql")
sparql_ex1.setReturnFormat(JSON)
sparql_ex1.agent = "CoursIA-SemanticWeb-Notebook/1.0 (educational)"
sparql_ex1.setQuery("""
PREFIX wd: <http://www.wikidata.org/entity/>
PREFIX wdt: <http://www.wikidata.org/prop/direct/>
PREFIX wikibase: <http://wikiba.se/ontology#>
PREFIX bd: <http://www.bigdata.com/rdf#>
SELECT ?cityLabel ?population
WHERE {
?city wdt:P31 wd:Q515 ;
wdt:P17 wd:Q142 ;
wdt:P1082 ?population .
SERVICE wikibase:label {
bd:serviceParam wikibase:language "fr,en" .
}
}
ORDER BY DESC(?population)
LIMIT 10
""")
try:
time.sleep(60)
results_ex1 = sparql_ex1.query().convert()
for r in results_ex1["results"]["bindings"]:
print(f"{r['cityLabel']['value']} : {int(r['population']['value']):,d} habitants")
except Exception as e:
print(f"Erreur : {e}")
print("Verifiez la syntaxe de votre requete ou la disponibilite de l'endpoint.")
else:
print("SPARQLWrapper non disponible : exemple 1 ignore.")Angers : 159,022 habitants
Tourcoing : 98,772 habitants
Roubaix : 98,286 habitants
Nanterre : 97,783 habitants
Vitry-sur-Seine : 93,963 habitants
Créteil : 93,397 habitants
Avignon : 92,188 habitants
Colombes : 91,053 habitants
Poitiers : 89,916 habitants
Saint-Pierre : 85,038 habitants
Lecture : ce que « ville » veut dire — et ce que le classement révèle
Deux surprises dans ce top 10. D’abord la tête : Angers : 159 022 habitants, devant Tourcoing : 98 772 — aucun des grands noms attendus (Toulouse, Nantes, Nice, sans parler de Paris ou Lyon). La cause se lit dans la première contrainte du WHERE : ?city wdt:P31 wd:Q515 ne retient que les entités typées exactement « city » (Q515). Dans Wikidata, les grandes communes françaises portent souvent des types plus précis (commune de France, capitale…) et la contrainte à un saut ne les attrape pas : le classement mesure donc « les entités typées Q515 avec une population », pas « les villes de France » — la question posée à l’ontologie n’était pas la question qu’on croyait poser. Ensuite la queue : Saint-Pierre : 85 038 — c’est Saint-Pierre de La Réunion, rappel utile que wdt:P17 wd:Q142 (pays : France) couvre l’outre-mer. Leçon générale du Web de données vivant : le résultat d’un classement dépend du typage ontologique choisi autant que des données elles-mêmes, et comme la requête interroge un service réel, il reflète l’état de Wikidata à l’instant de l’exécution — pas une photographie figée dans le notebook.
Exemple guide 2 : Films de Christopher Nolan via DBpedia
Solution proposee par Lilou Mayot (promo 2026)
Cet exemple montre comment interroger DBpedia pour lister les films d’un realisateur. Notez l’utilisation de OPTIONAL pour les proprietes qui peuvent etre absentes et de BIND/IF pour les valeurs conditionnelles.
Éléments cles : - Realisateur : dbr:Christopher_Nolan avec dbo:director - OPTIONAL pour le titre et la date de sortie - BIND(IF(BOUND(?date), YEAR(?date), "Unknown")) pour gerer les dates manquantes
if SPARQLWRAPPER_AVAILABLE:
# Exemple guide 2 : Films de Christopher Nolan via DBpedia
sparql_ex2 = SPARQLWrapper("http://dbpedia.org/sparql")
sparql_ex2.setReturnFormat(JSON)
sparql_ex2.setQuery("""
PREFIX dbo: <http://dbpedia.org/ontology/>
PREFIX dbr: <http://dbpedia.org/resource/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?film ?title ?year
WHERE {
?film dbo:director dbr:Christopher_Nolan .
OPTIONAL {
?film rdfs:label ?title .
FILTER (lang(?title) = "en")
}
OPTIONAL { ?film dbo:releaseDate ?date . }
BIND(
IF(BOUND(?date),
YEAR(?date),
"Unknown"
) AS ?year
)
}
ORDER BY DESC(?year)
""")
try:
results_ex2 = sparql_ex2.query().convert()
print("=== Films de Christopher Nolan ===")
for r in results_ex2["results"]["bindings"]:
print(f" {r['title']['value']} ({r['year']['value']})")
except Exception as e:
print(f"Erreur : {e}")
else:
print("SPARQLWrapper non disponible : exemple 2 ignore.")=== Films de Christopher Nolan ===
Larceny (1996 film) (Unknown)
Doodlebug (film) (Unknown)
Inception (Unknown)
Batman Begins (Unknown)
The Dark Knight (Unknown)
Following (Unknown)
The Prestige (film) (Unknown)
Insomnia (2002 film) (Unknown)
Dunkirk (2017 film) (Unknown)
Memento (film) (Unknown)
The Dark Knight Rises (Unknown)
Oppenheimer (film) (Unknown)
Interstellar (film) (Unknown)
Tenet (film) (Unknown)
Quay (film) (Unknown)
The Odyssey (2026 film) (Unknown)
Lecture : quand OPTIONAL ne lie rien — la colonne fantôme
Seize films de Christopher Nolan, du Larceny de 1996 jusqu’au futur The Odyssey (2026 film) — et une colonne année uniformément (Unknown). La requête demandait OPTIONAL { ?film dbo:releaseDate ?date } puis BIND(IF(BOUND(?date), YEAR(?date), "Unknown") AS ?year) : le mécanisme a fonctionné exactement comme prévu. Le OPTIONAL protège la requête — sans lui, un film sans date aurait fait disparaître la ligne entière ; le BIND(IF(BOUND(...))) transforme l’absence en valeur visible plutôt qu’en cellule silencieuse. Mais une colonne ENTIÈREMENT à Unknown est un symptôme : la propriété interrogée n’a lié aucune des seize lignes, ce qui signale une propriété absente ou nommée autrement sur l’endpoint — seize coïncidences d’affilée ne sont jamais le hasard. Le réflexe de diagnostic : lister les propriétés réellement disponibles d’un film (SELECT DISTINCT ?p WHERE { dbr:Inception ?p ?o }), puis tester les candidates une à une. Une donnée manquante à cette échelle se diagnostique comme un bug, pas comme un vide.
Exemple guide : Universites francaises via Wikidata
Interrogez Wikidata pour obtenir les 10 plus grandes universites francaises (en nombre d’etudiants), avec leur nom et le nombre d’etudiants inscrits.
Indices : - Classe universite : wd:Q3918 (university) - Pays : wdt:P17, France = wd:Q142 - Nombre d’etudiants : wdt:P2196 (students count) - Pensez au SERVICE wikibase:label et au time.sleep() entre les requêtes
if SPARQLWRAPPER_AVAILABLE:
# Exercice 1 : Universites francaises via Wikidata
import time
sparql_uni = SPARQLWrapper("https://query.wikidata.org/sparql")
sparql_uni.setReturnFormat(JSON)
sparql_uni.agent = "CoursIA-SemanticWeb-Notebook/1.0 (educational)"
sparql_uni.setQuery("""
PREFIX wd: <http://www.wikidata.org/entity/>
PREFIX wdt: <http://www.wikidata.org/prop/direct/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?universityLabel ?students
WHERE {
?university wdt:P31 wd:Q3918 ;
wdt:P17 wd:Q142 ;
wdt:P2196 ?students ;
rdfs:label ?universityLabel .
FILTER(lang(?universityLabel) = "fr")
}
ORDER BY DESC(?students)
LIMIT 10
""")
try:
# WDQS applique une limite ~1 requete/minute, renforcee sous charge.
# rdfs:label + FILTER est plus fiable que SERVICE wikibase:label (rejete
# sous charge) ; on ajoute un backoff pour robustesse (cellule = 2e
# requete Wikidata consecutive apres la cellule 25).
results_uni = None
for attempt in range(4):
try:
time.sleep(60)
results_uni = sparql_uni.query().convert()
break
except Exception as e_retry:
if attempt < 3:
print(f" (tentative {attempt + 1}/4) WDQS : {type(e_retry).__name__}, nouvel essai dans 60s...")
else:
raise
print("=== Top 10 universites francaises ===")
for r in results_uni["results"]["bindings"]:
print(f" {r['universityLabel']['value']} : {r['students']['value']} etudiants")
except Exception as e:
print(f"Erreur : {e}")
else:
print("SPARQLWrapper non disponible : exercice 1 ignore.")=== Top 10 universites francaises ===
université de Montpellier : 49000 etudiants
université Grenoble-I : 18221 etudiants
université Grenoble-I : 18143 etudiants
université Grenoble-I : 16865 etudiants
université Grenoble-I : 16723 etudiants
université Grenoble-I : 16710 etudiants
université Grenoble-I : 16667 etudiants
université Grenoble-I : 15400 etudiants
université Grenoble-I : 15392 etudiants
université Grenoble-I : 15345 etudiants
Lecture : Grenoble première… neuf fois
Le classement attendu était « les dix plus grandes universités françaises » ; la sortie rend université de Montpellier : 49000 etudiants une seule fois, puis université Grenoble-I NEUF fois, avec des effectifs tous différents (18221, 18143, 16865, 16723…). Ce n’est pas un bug de dédoublonnage : c’est la sémantique de la propriété wdt:P2196 (nombre d’étudiants). Dans Wikidata, un établissement porte souvent PLUSIEURS déclarations d’effectifs — par année, par composante, par source — et la voie directe wdt: rend une ligne par valeur déclarée. Le ORDER BY DESC(?students) classe les LIGNES, pas les établissements : Grenoble-I occupe neuf rangs parce qu’elle porte neuf valeurs. C’est le piège symétrique du top villes de l’exemple 1 — là le typage filtrait trop, ici la propriété multi-déclarée duplique. Les remèdes existent et relèvent de la doc Wikidata : filtrer par date de référence, ne garder que le rang préféré, ou agréger (GROUP BY ?university) — mais chacun exige de décider QUELLE valeur représente « l’effectif » d’une université. Une question de modélisation des données, pas de syntaxe SPARQL.
Exemple guide : Albums d’un artiste via DBpedia
Resolu par le groupe Antoine Gaillard & Ambroise Durst (contribution PR #2397).
Choisissez un artiste musical et interrogez DBpedia pour lister ses albums avec leur titre et annee de sortie.
Indices : - Propriete artiste : dbo:artist ou dbp:artist - Type album : dbo:Album ou schema:MusicAlbum - Annee : dbo:year ou dbp:year - Inspirez-vous de l’Exemple guide 2 pour la structure de la requête
if SPARQLWRAPPER_AVAILABLE:
# Exercice 2 : Albums d'un artiste via DBpedia
sparql_albums = SPARQLWrapper("http://dbpedia.org/sparql")
sparql_albums.setReturnFormat(JSON)
# Exercice 2 : Albums d'un artiste via DBpedia
artist = "dbr:Daft_Punk"
sparql_albums.setQuery("""
PREFIX dbo: <http://dbpedia.org/ontology/>
PREFIX dbr: <http://dbpedia.org/resource/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?album ?title ?year
WHERE {
?album dbo:artist dbr:Daft_Punk .
?album a dbo:Album .
OPTIONAL {
?album rdfs:label ?title .
FILTER (lang(?title) = "en")
}
OPTIONAL {
?album dbo:releaseDate ?date .
BIND(YEAR(?date) AS ?year)
}
}
ORDER BY ?year
""")
try:
results_albums = sparql_albums.query().convert()
print(f"=== Albums ===")
for r in results_albums["results"]["bindings"]:
print(f" {r.get('title', {}).get('value', '?')} ({r.get('year', {}).get('value', '?')})")
except Exception as e:
print(f"Erreur : {e}")
else:
print("SPARQLWrapper non disponible : exercice 2 ignore.")=== Albums ===
Lecture : zéro résultat est un résultat
La sortie affiche l’en-tête === Albums === puis… rien. La requête ?album dbo:artist dbr:Daft_Punk ; a dbo:Album a pourtant abouti sans erreur sur l’endpoint DBpedia : elle a rendu l’ensemble vide — aucune ressource ne satisfait les deux motifs simultanément. SPARQL ne distingue pas « pas de données » de « mauvaise requête » : un pattern qui ne matche rien est une réponse valide et silencieuse, contrairement à une erreur de syntaxe qui, elle, lève une exception. Les causes classiques dans ce cas précis : la propriété (DBpedia décrit souvent les albums par dbo:musicalArtist plutôt que dbo:artist), le typage (dbo:Album contre schema:MusicAlbum ou dbo:MusicalWork), ou la ressource elle-même — les URI DBpedia sont sensibles à la casse. Le diagnostic se fait en retirant les contraintes UNE À UNE jusqu’à voir réapparaître des lignes : la bissection appliquée aux requêtes.
Exemple guide : Lier DBpedia et Wikidata via owl:sameAs
Le Web de données relie une même entite entre plusieurs datasets grace a owl:sameAs. Pour quelques villes DBpedia, recuperez l’URI Wikidata equivalente, en ne conservant que les cibles pointant vers wikidata.org. C’est le mécanisme d’interconnexion au coeur du Linked Open Data.
Indices : - Propriete de liaison : owl:sameAs (PREFIX owl: <http://www.w3.org/2002/07/owl#>) - Point de depart : VALUES ?ville { dbr:Paris dbr:Lyon dbr:Marseille } - Filtre sur la cible : FILTER(CONTAINS(STR(?same), "wikidata.org"))
if SPARQLWRAPPER_AVAILABLE:
# Exercice 3 : Lier DBpedia et Wikidata via owl:sameAs
sparql_link = SPARQLWrapper("http://dbpedia.org/sparql")
sparql_link.setReturnFormat(JSON)
requete_sameas = """
PREFIX owl: <http://www.w3.org/2002/07/owl#>
PREFIX dbr: <http://dbpedia.org/resource/>
SELECT ?ville ?same
WHERE {
VALUES ?ville { dbr:Paris dbr:Lyon dbr:Marseille }
?ville owl:sameAs ?same .
FILTER(CONTAINS(STR(?same), "wikidata.org"))
}
"""
sparql_link.setQuery(requete_sameas)
try:
for r in sparql_link.query().convert()["results"]["bindings"]:
print(f" {r['ville']['value']} -> {r['same']['value']}")
except Exception as e:
print(f"Erreur : {e}")
else:
print("SPARQLWrapper non disponible : exercice 3 ignore.") http://dbpedia.org/resource/Lyon -> http://www.wikidata.org/entity/Q456
http://dbpedia.org/resource/Paris -> http://www.wikidata.org/entity/Q90
http://dbpedia.org/resource/Marseille -> http://www.wikidata.org/entity/Q23482
http://dbpedia.org/resource/Marseille -> http://www.wikidata.org/entity/Q49295409
http://dbpedia.org/resource/Marseille -> http://www.wikidata.org/entity/Q701646
Lecture : owl:sameAs n’est pas une fonction
Cinq lignes pour trois villes : Lyon vers Q456, Paris vers Q90, et Marseille vers TROIS entités (Q23482, Q49295409, Q701646). Le pont inter-bases fonctionne — chaque ressource DBpedia est bien reliée à Wikidata — mais la relation owl:sameAs est un-à-plusieurs : une ville peut être sameAs avec l’entité qui la représente en tant que commune ET avec celles de ses arrondissements ou subdivisions. C’est à la fois une richesse du Web de données liées (la granularité est préservée : la commune et ses quartiers restent distincts) et un vrai piège pour l’échange de données : qui veut UNE entité canonique par ville doit ajouter un critère de sélection — par exemple ne retenir, parmi les cibles, que l’entité typée « commune de France » côté Wikidata. Noter enfin le passage obligé par STR(?same) dans le CONTAINS : les fonctions de chaîne opèrent sur des littéraux, et une IRI doit être convertie en texte avant tout test de sous-chaîne — un croisement de types que SPARQL ne fait jamais implicitement.
Exemple guide : Construire un graphe local avec CONSTRUCT
Resolu par le groupe Antoine Gaillard & Ambroise Durst (contribution PR #2397).
Jusqu’ici vous avez utilise SELECT qui renvoie un tableau. La forme CONSTRUCT renvoie un graphe RDF, que vous pouvez materialiser localement avec rdflib pour le requeter ensuite hors-ligne. Rapatriez quelques villes francaises et leur population depuis DBpedia dans un graphe local.
Indices : - Forme de requête : CONSTRUCT { ... } WHERE { ... } - Format de retour adapte : from SPARQLWrapper import TURTLE puis setReturnFormat(TURTLE) - Chargement local : Graph().parse(data=turtle, format="turtle") - Verifiez la taille du graphe avec len(g)
if SPARQLWRAPPER_AVAILABLE:
# Exercice 4 : Construire un graphe local avec CONSTRUCT
try:
from rdflib import Graph
RDFLIB_OK = True
except ImportError:
RDFLIB_OK = False
if RDFLIB_OK:
sparql_construct = SPARQLWrapper("http://dbpedia.org/sparql")
requete_construct = """
PREFIX dbo: <http://dbpedia.org/ontology/>
PREFIX dbr: <http://dbpedia.org/resource/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
CONSTRUCT {
?ville rdfs:label ?nom ;
dbo:populationTotal ?population .
}
WHERE {
VALUES ?ville { dbr:Paris dbr:Lyon dbr:Marseille }
?ville a dbo:City .
?ville dbo:country dbr:France .
?ville rdfs:label ?nom .
?ville dbo:populationTotal ?population .
FILTER(lang(?nom) = "fr")
}
LIMIT 50
"""
from SPARQLWrapper import TURTLE
sparql_construct.setReturnFormat(TURTLE)
sparql_construct.setQuery(requete_construct)
try:
turtle = sparql_construct.query().convert()
g_dl = Graph().parse(data=turtle, format="turtle")
print(f"Graphe local construit : {len(g_dl)} triplets")
except Exception as e:
print(f"Erreur : {e}")
else:
print("rdflib non disponible : exercice 4 ignore.")
else:
print("SPARQLWrapper non disponible : exercice 4 ignore.")Graphe local construit : 0 triplets
Lecture : le CONSTRUCT strict — zéro triplet, pas un triplet partiel
Graphe local construit : 0 triplets. La chaîne a pourtant fonctionné de bout en bout : la requête est partie, l’endpoint a répondu, Graph().parse() a rendu un graphe — vide. Le CONSTRUCT est une opération tout-ou-rien par solution : pour que le template produise des triplets, il faut que chaque motif du WHERE (a dbo:City, dbo:country dbr:France, rdfs:label français, dbo:populationTotal) soit satisfait SIMULTANÉMENT pour une même ville ; une seule jointure manquée élimine la solution entière — et il ne reste rien à projeter. Deux diagnostics possibles ici : des motifs réellement insatisfaits (le typage ou la propriété pays de ces ressources sur DBpedia), ou une réponse de l’endpoint que rdflib n’a pas pu interpréter en Turtle et qu’il a avalée sans lever d’exception — un document vide se parse en graphe vide, sans erreur. Le réflexe est identique dans les deux cas : rejouer le WHERE en SELECT en retirant les motifs un à un pour isoler celui qui ne lie pas. Un CONSTRUCT muet n’est pas un échec, c’est une mesure négative qui demande à être expliquée.
Exemple guide : Agregation SPARQL (COUNT / GROUP BY)
Resolu par le groupe Antoine Gaillard & Ambroise Durst (contribution PR #2397).
SPARQL ne sert pas qu’a extraire des lignes : il sait aussi agreger. Ecrivez une requête qui compte le nombre de films par realisateur dans DBpedia et renvoie le top 10 des realisateurs les plus prolifiques.
Indices : - Fonction d’agregation : COUNT(?film) avec alias (COUNT(?film) AS ?nb) - Regroupement : GROUP BY ?director - Motif de base : ?film dbo:director ?director - Triez avec ORDER BY DESC(?nb) et limitez avec LIMIT 10
if SPARQLWRAPPER_AVAILABLE:
# Exercice 5 : Agregation SPARQL (COUNT / GROUP BY)
sparql_agg = SPARQLWrapper("http://dbpedia.org/sparql")
sparql_agg.setReturnFormat(JSON)
requete_agg = """
PREFIX dbo: <http://dbpedia.org/ontology/>
SELECT ?director (COUNT(?film) AS ?nb)
WHERE {
?film a dbo:Film .
?film dbo:director ?director .
}
GROUP BY ?director
ORDER BY DESC(?nb)
LIMIT 10
"""
sparql_agg.setQuery(requete_agg)
try:
for r in sparql_agg.query().convert()["results"]["bindings"]:
print(f" {r['director']['value']} : {r['nb']['value']} films")
except Exception as e:
print(f"Erreur : {e}")
else:
print("SPARQLWrapper non disponible : exercice 5 ignore.") http://dbpedia.org/resource/D._W._Griffith : 941 films
http://dbpedia.org/resource/Georges_Méliès : 714 films
http://dbpedia.org/resource/Friz_Freleng : 701 films
http://dbpedia.org/resource/Chuck_Jones : 619 films
http://dbpedia.org/resource/Sam_Newfield : 598 films
http://dbpedia.org/resource/William_Beaudine : 535 films
http://dbpedia.org/resource/Dave_Fleischer : 503 films
http://dbpedia.org/resource/Michael_Curtiz : 497 films
http://dbpedia.org/resource/Richard_Thorpe : 490 films
http://dbpedia.org/resource/Robert_McKimson : 490 films
Lecture : le podium des pionniers — ce que COUNT mesure vraiment
D._W._Griffith : 941 films, devant Georges Méliès : 714 et une brochette d’animateurs (Friz Freleng : 701, Chuck Jones : 619 — l’école Looney Tunes). Le COUNT(?film) ... GROUP BY ?director transforme l’endpoint DBpedia en base statistique, et ce qu’il mesure est la LONGUEUR de carrière enregistrée autant que la notoriété : les réalisateurs du cinéma muet et du studio system, crédités sur des centaines de courts métrages, écrasent les auteurs modernes plus sélectifs — aucun nom contemporain dans le top 10. La lecture à double détente est la leçon durable : une agrégation SPARQL est un fait exact SUR LE GRAPHE (le compte est juste, vérifiable), qui ne devient une affirmation sur le monde qu’à travers les choix de modélisation de la source — ici, ce qui compte comme « film » dans le typage DBpedia. Même mécanisme que le top villes de l’exemple 1 : la requête répond fidèlement à la question posée, pas nécessairement à celle qu’on croyait poser.
Exercice 1 (a completer) : Laureats francais du prix Nobel de physique (Wikidata)
A votre tour ! En vous inspirant des exemples guides ci-dessus, interrogez Wikidata pour lister les personnes de nationalite francaise ayant recu le prix Nobel de physique, avec leur nom et l’annee d’obtention.
Indices : - Recompense (prix Nobel de physique) : wd:Q38104 - Propriete “distinction recue” : wdt:P166 - Propriete “pays de nationalite” : wdt:P27, France = wd:Q142 - Le label se recupere via le service wikibase:label (langue "fr"). - Endpoint : https://query.wikidata.org/sparql.
if SPARQLWRAPPER_AVAILABLE:
# Exercice 1 : Laureats francais du prix Nobel de physique (Wikidata)
# TODO etudiant : ecrire une requete SPARQL sur Wikidata
#
# Etape 1 : configurer SPARQLWrapper avec l'endpoint Wikidata
# sparql = SPARQLWrapper("https://query.wikidata.org/sparql")
# sparql.setReturnFormat(JSON)
# Etape 2 : ecrire SELECT ?personLabel ?annee WHERE { ... }
# ?person wdt:P166 wd:Q38104 . # a recu le prix Nobel de physique
# ?person wdt:P27 wd:Q142 . # nationalite francaise
# utiliser le service wikibase:label pour ?personLabel (langue 'fr')
# Etape 3 : executer et afficher nom + annee
print("Exercice a completer")
else:
print("SPARQLWrapper non disponible")Exercice a completer
Exercice 2 (a completer) : Musees de la ville de Paris (Wikidata, SELECT)
En vous inspirant des Exemples guides via Wikidata (top 10 villes, universites francaises), listez 15 musees situes a Paris, avec leur nom.
Indices : - “nature de l’élément” : wdt:P31 ; classe musee : wd:Q33506. - “localise dans l’entite territoriale” : wdt:P131 ; Paris = wd:Q90. - Recuperez le label via SERVICE wikibase:label { bd:serviceParam wikibase:language "fr". }. - Endpoint : https://query.wikidata.org/sparql.
if SPARQLWRAPPER_AVAILABLE:
# Exercice 2 : Musees de la ville de Paris (Wikidata, SELECT)
# TODO etudiant : ecrire une requete SPARQL SELECT sur Wikidata
#
# Etape 1 : configurer SPARQLWrapper avec l'endpoint Wikidata
# sparql = SPARQLWrapper("https://query.wikidata.org/sparql")
# sparql.setReturnFormat(JSON)
# Etape 2 : ecrire la requete SELECT
# SELECT ?museeLabel WHERE {
# ?musee wdt:P31 wd:Q33506 ; wdt:P131 wd:Q90 .
# SERVICE wikibase:label { bd:serviceParam wikibase:language "fr". }
# } LIMIT 15
# Etape 3 : executer et afficher le nom de chaque musee
print("Exercice a completer")
else:
print("SPARQLWrapper non disponible")Exercice a completer
Exercice 3 (a completer) : Pays les plus peuples du monde (Wikidata, FILTER)
En vous inspirant des exemples SELECT via Wikidata, listez les pays dont la population depasse 100 millions d’habitants, tries du plus peuple au moins peuple. Vous decouvrirez ici le filtrage numérique avec FILTER.
Indices : - “nature de l’élément” : wdt:P31 ; classe pays : wd:Q6256. - Propriete population : wdt:P1082. - Filtre numérique : FILTER(?population > 100000000). - Tri : ORDER BY DESC(?population) ; label via SERVICE wikibase:label (langue "fr"). - Endpoint : https://query.wikidata.org/sparql.
if SPARQLWRAPPER_AVAILABLE:
# Exercice 3 : Pays les plus peuples du monde (Wikidata, FILTER numerique)
# TODO etudiant : ecrire une requete SPARQL SELECT sur Wikidata
#
# Etape 1 : configurer SPARQLWrapper avec l'endpoint Wikidata
# sparql = SPARQLWrapper("https://query.wikidata.org/sparql")
# sparql.setReturnFormat(JSON)
# Etape 2 : ecrire la requete avec FILTER
# SELECT ?paysLabel ?population WHERE {
# ?pays wdt:P31 wd:Q6256 ; wdt:P1082 ?population .
# FILTER(?population > 100000000)
# SERVICE wikibase:label { bd:serviceParam wikibase:language "fr". }
# } ORDER BY DESC(?population)
# Etape 3 : executer et afficher chaque pays + sa population
print("Exercice a completer")
else:
print("SPARQLWrapper non disponible")Exercice a completer
Resume
Ce sidetrack a presente l’interrogation de données liees du Web avec SPARQLWrapper.
Concepts cles
| Concept | Ce que vous avez appris |
|---|---|
| SPARQLWrapper | Interroger des endpoints SPARQL distants |
| DBpedia | Données structurees de Wikipedia |
| Wikidata | Base de connaissances avec Q-items/P-properties |
| SERVICE | Requêtes federées entre endpoints |
| owl:sameAs | Interconnexion des entites entre datasets (Linked Open Data) |
| CONSTRUCT | Construire un graphe RDF local a partir de données distantes |
| Agregation | COUNT / GROUP BY pour l’analyse statistique |
| Correspondance | Equivalences dotNetRDF / SPARQLWrapper |
Exemples et Exercices pratiques
| Type | Titre | Source / Statut |
|---|---|---|
| Exemple guide | Top 10 villes francaises via Wikidata | Lilou Mayot (promo 2026) |
| Exemple guide | Films de Christopher Nolan via DBpedia | Lilou Mayot (promo 2026) |
| Exemple guide | Universites francaises via Wikidata | Resolu |
| Exemple guide | Albums d’un artiste via DBpedia | Resolu |
| Exemple guide | Lier DBpedia et Wikidata via owl:sameAs | Resolu |
| Exemple guide | Construire un graphe local avec CONSTRUCT | Resolu |
| Exemple guide | Agregation SPARQL (COUNT / GROUP BY) | Resolu |
| Exercice | Fleuves de France via DBpedia | A completer |
| Exercice | Capitales europeennes et leur pays (Wikidata) | A completer |
| Exercice 1 | Laureats francais du prix Nobel de physique (Wikidata) | A completer |
| Exercice 2 | Musees de la ville de Paris (Wikidata) | A completer |
| Exercice 3 | Pays les plus peuples du monde (Wikidata, FILTER) | A completer |
Prochaines étapes
- SW-6-CSharp-RDFS : Schema et inference en .NET
- SW-7-CSharp-OWL : Ontologies et raisonnement avance
- SW-7b-Python-OWL : Introduction a OWLReady2 pour Python
Retour au sommaire : Index SemanticWeb