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/except pour gerer ces cas gracieusement.


1. Installation et Imports

SPARQLWrapper est la bibliotheque de reference pour interroger des endpoints SPARQL distants depuis Python.

# Dependances pre-provisionnees (SPARQLWrapper) : voir SemanticWeb/requirements.txt ; imports dans les cellules suivantes.

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:label est 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

Retour au sommet