SPARQL (от англ. SPARQL Protocol and RDF Query Language) – язык запросов к данным, представленным по модели RDF.
Сервисы, предоставляющие API для доступа к базам знаний
SPARQLWrapper – библиотека-обертка для работы с SPARQL на языке Python (на других языках: RDF-Query для Perl, SPARQL/Grammar для Ruby и ARQ для Java).
Далее приведены примеры автоматизации запросов к базе знаний: https://dbpedia.org/sparql
Следующий запрос возвращает ответ в формате JSON: Содержимое переменной results будет иметь вид:
{ 'head': {'vars': ['label'], 'link': []},
'results': {'distinct': False, 'bindings':
[{'label': {'value': 'Asturias', 'xml:lang': 'en', 'type': 'literal'}},
{'label': {'value': 'منطقة أستورياس', 'xml:lang': 'ar', 'type': 'literal'}},
{'label': {'value': 'Asturien', 'xml:lang': 'de', 'type': 'literal'}},
{'label': {'value': 'Asturias', 'xml:lang': 'es', 'type': 'literal'}},
{'label': {'value': 'Asturies', 'xml:lang': 'fr', 'type': 'literal'}},
{'label': {'value': 'Asturie', 'xml:lang': 'it', 'type': 'literal'}},
{'label': {'value': 'アストゥリアス州', 'xml:lang': 'ja', 'type': 'literal'}},
{'label': {'value': 'Asturië (regio)', 'xml:lang': 'nl', 'type': 'literal'}},
{'label': {'value': 'Asturia', 'xml:lang': 'pl', 'type': 'literal'}},
{'label': {'value': 'Astúrias', 'xml:lang': 'pt', 'type': 'literal'}},
{'label': {'value': 'Астурия', 'xml:lang': 'ru', 'type': 'literal'}},
{'label': {'value': '阿斯图里亚斯', 'xml:lang': 'zh', 'type': 'literal'}}],
'ordered': True}
}
# Пример с официального сайта: https://rdflib.github.io/sparqlwrapper/
from SPARQLWrapper import SPARQLWrapper, JSON
sparql = SPARQLWrapper("http://dbpedia.org/sparql")
queryString = """
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?label
WHERE { <http://dbpedia.org/resource/Asturias> rdfs:label ?label }
"""
sparql.setQuery(queryString)
sparql.setReturnFormat(JSON)
results = sparql.query().convert()
#print(results, "\n")
if (len(results["results"]["bindings"]) == 0):
print ("No results found.")
else:
for result in results["results"]["bindings"]:
print(result["label"]["value"])
Результат возвращается в формате JSON: Формат ответа, т.е. содержимое переменной results:
{'head': {'vars': ['author_name', 'title', 'pages'], 'link': []},
'results': {'distinct': False,
'bindings': [
{'title': {'value': 'David Copperfield', 'xml:lang': 'en', 'type': 'literal'},
'author_name': {'value': 'Charles Dickens', 'xml:lang': 'en', 'type': 'literal'},
'pages': {'value': '624', 'datatype': 'http://www.w3.org/2001/XMLSchema#positiveInteger', 'type': 'typed-literal'}},
{'title': {'value': 'Fifty Shades of Grey', 'xml:lang': 'en', 'type': 'literal'},
'author_name': {'value': 'E. L. James', 'xml:lang': 'en', 'type': 'literal'},
'pages': {'value': '514', 'datatype': 'http://www.w3.org/2001/XMLSchema#positiveInteger', 'type': 'typed-literal'}},
{'title': {'value': 'Roma (novel)', 'xml:lang': 'en', 'type': 'literal'},
'author_name': {'value': 'Steven Saylor', 'xml:lang': 'en', 'type': 'literal'},
'pages': {'value': '592', 'datatype': 'http://www.w3.org/2001/XMLSchema#positiveInteger', 'type': 'typed-literal'}},
{'title': {'value': 'The Alexandria Quartet', 'xml:lang': 'en', 'type': 'literal'},
'author_name': {'value': 'Lawrence Durrell', 'xml:lang': 'en', 'type': 'literal'},
'pages': {'value': '884', 'datatype': 'http://www.w3.org/2001/XMLSchema#positiveInteger', 'type': 'typed-literal'}},
{'title': {'value': 'Helter Skelter (book)', 'xml:lang': 'en', 'type': 'literal'},
'author_name': {'value': 'Vincent Bugliosi', 'xml:lang': 'en', 'type': 'literal'},
'pages': {'value': '502', 'datatype': 'http://www.w3.org/2001/XMLSchema#positiveInteger', 'type': 'typed-literal'}},
{'title': {'value': 'Inferno (Brown novel)', 'xml:lang': 'en', 'type': 'literal'},
'author_name': {'value': 'Dan Brown', 'xml:lang': 'en', 'type': 'literal'},
'pages': {'value': '609', 'datatype': 'http://www.w3.org/2001/XMLSchema#positiveInteger', 'type': 'typed-literal'}},
{'title': {'value': 'Terra Nostra (novel)', 'xml:lang': 'en', 'type': 'literal'},
'author_name': {'value': 'Carlos Fuentes', 'xml:lang': 'en', 'type': 'literal'},
'pages': {'value': '783', 'datatype': 'http://www.w3.org/2001/XMLSchema#positiveInteger', 'type': 'typed-literal'}},
{'title': {'value': 'The Dark Is Rising Sequence', 'xml:lang': 'en', 'type': 'literal'},
'author_name': {'value': 'Susan Cooper', 'xml:lang': 'en', 'type': 'literal'},
'pages': {'value': '786', 'datatype': 'http://www.w3.org/2001/XMLSchema#positiveInteger', 'type': 'typed-literal'}},
{'title': {'value': 'The Kindly Ones (Littell novel)', 'xml:lang': 'en', 'type': 'literal'},
'author_name': {'value': 'Jonathan Littell', 'xml:lang': 'en', 'type': 'literal'},
'pages': {'value': '902', 'datatype': 'http://www.w3.org/2001/XMLSchema#positiveInteger', 'type': 'typed-literal'}},
{'title': {'value': 'The Kindly Ones (Littell novel)', 'xml:lang': 'en', 'type': 'literal'},
'author_name': {'value': 'Jonathan Littell', 'xml:lang': 'en', 'type': 'literal'},
'pages': {'value': '992', 'datatype': 'http://www.w3.org/2001/XMLSchema#positiveInteger', 'type': 'typed-literal'}}], 'ordered': True}}
from SPARQLWrapper import SPARQLWrapper, JSON
sparql = SPARQLWrapper("http://dbpedia.org/sparql")
queryString = """
PREFIX : <http://dbpedia.org/resource/>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX dbo: <http://dbpedia.org/ontology/>
SELECT ?author_name ?title ?pages
WHERE {
?author rdf:type dbo:Writer .
?author rdfs:label ?author_name
FILTER (LANG(?author_name)="en").
?author dbo:notableWork ?work .
?work dbo:numberOfPages ?pages
FILTER (?pages > 500) .
?work rdfs:label ?title .
FILTER (LANG(?title)="en").
}
LIMIT 10
"""
sparql.setQuery(queryString)
sparql.setReturnFormat(JSON)
results = sparql.query().convert()
#print(results)
# проверяем количество записей, которое вернул сервис:
if (len(results["results"]["bindings"]) == 0):
print ("No results found.")
else:
for result in results["results"]["bindings"]:
print (result["author_name"]["value"], result["title"]["value"], result["pages"]["value"], sep=" : ")
Следующий скрипт из книги Learning SPARQL отправляет запрос к базе знаний LinkedMDB об актерах, которые хотя бы один раз снимались в фильмах Стивена Спилберга и Стенли Кубрика.
Результат возвращается в формате JSON:
{ 'results': {
'bindings':
[
{'actorName': {'type': 'literal', 'value': 'Wolf Kahler'}},
{'actorName': {'type': 'literal', 'value': 'Slim Pickens'}},
{'actorName': {'type': 'literal', 'value': 'Tom Cruise'}},
{'actorName': {'type': 'literal', 'value': 'Arliss Howard'}},
{'actorName': {'type': 'literal', 'value': 'Ben Johnson'}},
{'actorName': {'type': 'literal', 'value': 'Scatman Crothers'}},
{'actorName': {'type': 'literal', 'value': 'Philip Stone'}}
]
},
'head': {'vars': ['actorName']}
}
from SPARQLWrapper import SPARQLWrapper, JSON
# SPARQL endpoint
sparql = SPARQLWrapper("http://data.linkedmdb.org/sparql")
queryString = """
PREFIX m: <http://data.linkedmdb.org/resource/movie/>
SELECT DISTINCT ?actorName WHERE {
?dir1 m:director_name "Steven Spielberg" .
?dir2 m:director_name "Stanley Kubrick" .
?dir1film m:director ?dir1 ;
m:actor ?actor .
?dir2film m:director ?dir2 ;
m:actor ?actor .
?actor m:actor_name ?actorName .
}
"""
sparql.setQuery(queryString)
# по умолчанию результат возвращается в XML формате: https://www.w3.org/TR/rdf-sparql-XMLres/
# преобразуем в JSON: https://www.w3.org/TR/rdf-sparql-json-res/
sparql.setReturnFormat(JSON)
# вернется словарь (dict), основанный на JSON
results = sparql.query().convert()
# проверяем количество записей, которое вернул сервис:
if (len(results["results"]["bindings"]) == 0):
print ("No results found.")
else:
for result in results["results"]["bindings"]:
print (result["actorName"]["value"])
from SPARQLWrapper import SPARQLWrapper, JSON
# Имена режиссеров поместили в отдельные переменные
director1 = "Steven Spielberg"
director2 = "Stanley Kubrick"
sparql = SPARQLWrapper("http://data.linkedmdb.org/sparql")
queryString = """
PREFIX m: <http://data.linkedmdb.org/resource/movie/>
SELECT DISTINCT ?actorName WHERE {
?dir1 m:director_name "DIR1-NAME".
?dir2 m:director_name "DIR2-NAME".
?dir1film m:director ?dir1 ;
m:actor ?actor .
?dir2film m:director ?dir2 ;
m:actor ?actor .
?actor m:actor_name ?actorName .
}
"""
queryString = queryString.replace("DIR1-NAME",director1)
queryString = queryString.replace("DIR2-NAME",director2)
sparql.setQuery(queryString)
sparql.setReturnFormat(JSON)
results = sparql.query().convert()
if (len(results["results"]["bindings"]) == 0):
print ("No results found.")
else:
for result in results["results"]["bindings"]:
print (result["actorName"]["value"])
Добавим программе графический интерфейс на основе tkinter. Список виджетов tkinter представлен по ссылке.
Сначала рассмотрим пример простой графической программы:
from tkinter import *
master = Tk()
var = StringVar()
var2 = StringVar()
var.set("one") # initial value
label = Label (master, text = "Укажите режиссера:")
label.pack()
# http://effbot.org/tkinterbook/optionmenu.htm
option = OptionMenu(master, var, "Steven Spielberg", "Stanley Kubrick")
option.pack()
def ok():
var2.set(var.get())
button = Button(master, text="Готово!", command=ok)
button.pack()
label = Label (master, textvariable=var2)
label.pack()
mainloop()
В следующем примере подключаем возможности SPARQLWrapper:
from SPARQLWrapper import SPARQLWrapper, JSON
from tkinter import *
master = Tk()
var = StringVar()
var2 = StringVar()
var.set("Укажите режиссера:") # initial value
option = OptionMenu(master, var, "Steven Spielberg", "Stanley Kubrick")
option.pack()
def ok():
sparql = SPARQLWrapper("http://data.linkedmdb.org/sparql")
queryString = """
PREFIX m: <http://data.linkedmdb.org/resource/movie/>
SELECT DISTINCT ?actorName WHERE {
?dir1 m:director_name "DIR1-NAME".
?dir1film m:director ?dir1 ;
m:actor ?actor .
?actor m:actor_name ?actorName .
}
"""
queryString = queryString.replace("DIR1-NAME",var.get())
sparql.setQuery(queryString)
sparql.setReturnFormat(JSON)
results = sparql.query().convert()
if (len(results["results"]["bindings"]) == 0):
var2.set ("No results found.")
else:
res = ""
for result in results["results"]["bindings"]:
res = res + result["actorName"]["value"] + '\n'
var2.set (res)
button = Button(master, text="Готово!", command=ok)
button.pack()
# Получился длинный список актеров, поэтому желательно использовать:
# scrollbar = Scrollbar(master)
label = Label (master, textvariable=var2)
label.pack()
mainloop()
Результат работы программы:
Для осуществления запросов к собственному RDF-документу потребуется настроить локальный SPARQL-сервис. Например, на основе Fuseki. Getting Started With Fuseki
Потребуется версия Java 8. Запуск сервиса из командной строки:
c:\apache-jena-fuseki-2.4.0>fuseki-server --update --mem /ds
После запуска откройте в браузере: localhost:3030
Далее представлен исходный текст программы для обращения к локальному SPARQL endpoint:
from SPARQLWrapper import SPARQLWrapper, JSON
# Локальный SPARQL endpoint
sparql = SPARQLWrapper("http://localhost:3030/book-pers/query")
queryString = """
SELECT ?subject ?predicate ?object
WHERE {
?subject ?predicate ?object
}
LIMIT 25
"""
sparql.setQuery(queryString)
# по умолчанию результат возвращается в XML формате: https://www.w3.org/TR/rdf-sparql-XMLres/
# преобразуем в JSON: https://www.w3.org/TR/rdf-sparql-json-res/
sparql.setReturnFormat(JSON)
# вернется словарь (dict), основанный на JSON
results = sparql.query().convert()
#print(results)
# проверяем количество записей, которое вернул сервис:
if (len(results["results"]["bindings"]) == 0):
print ("No results found.")
else:
for result in results["results"]["bindings"]:
print (result["object"]["value"])
Примечание: онтология для Fuseki загружается в формате RDF/XML ("Сохранить как" в Protege).
Пример автоматизации запроса: "Вы вегетарианец? Да."
from SPARQLWrapper import SPARQLWrapper, JSON
# SPARQL endpoint
sparql = SPARQLWrapper("http://localhost:3030/new-last-pizza/query")
queryString = """
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX owl: <http://www.w3.org/2002/07/owl#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX : <http://www.pizza.com/ontologies/pizza.owl#>
SELECT ?Pizza
WHERE {
?Pizza rdfs:subClassOf :VegetarianPizza.}
"""
sparql.setQuery(queryString)
# по умолчанию результат возвращается в XML формате: https://www.w3.org/TR/rdf-sparql-XMLres/
# преобразуем в JSON: https://www.w3.org/TR/rdf-sparql-json-res/
sparql.setReturnFormat(JSON)
# вернется словарь (dict), основанный на JSON
results = sparql.query().convert()
#print(results)
# проверяем количество записей, которое вернул сервис:
if (len(results["results"]["bindings"]) == 0):
print ("No results found.")
else:
for result in results["results"]["bindings"]:
print (result["Pizza"]["value"])
Пример ЭС с графичесим интерфейсом (на основе таблицы решений):
from SPARQLWrapper import SPARQLWrapper, JSON
from tkinter import *
# возвращает SPARQL-запрос в зависимости от выбора пользователя (a - (не)с мясом, b - (не)острая, c - (не)традиционная)
def queryResult (a, b, c):
prefix = '''
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX owl: <http://www.w3.org/2002/07/owl#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX : <http://www.pizza.com/ontologies/pizza.owl#>
'''
# любая без мяса
if a == "Да" and b == "Не знаю" and c == "Не знаю":
queryString = prefix + """
SELECT ?Pizza
WHERE {
?Pizza rdfs:subClassOf :VegetarianPizza.
}
"""
return queryString
# любая с мясом
elif a == "Нет" and b == "Не знаю" and c == "Не знаю":
queryString = prefix + """
SELECT ?Pizza
WHERE {
?Pizza rdfs:subClassOf :NamedPizza
MINUS {?Pizza rdfs:subClassOf :VegetarianPizza.}
}
"""
return queryString
# любая острая без мяса
elif a == "Да" and b == "Да" and c == "Не знаю":
queryString = prefix + """
SELECT ?Pizza
WHERE {
?Pizza rdfs:subClassOf :VegetarianPizza.
?Pizza rdfs:subClassOf :SpicyPizza.}
"""
return queryString
# Предпочитает пиццу с мясом, неострую и традиционную
elif a == "Нет" and b == "Нет" and c == "Да":
queryString = prefix + """
SELECT ?Pizza
WHERE {
?Pizza rdfs:subClassOf :ExoticPizza.
Minus {?Pizza rdfs:subClassOf :VegetarianPizza.}
Minus {?Pizza rdfs:subClassOf :SpicyPizza.}
}
"""
return queryString
# С мясом, неострая и нетрадиционная
elif a == "Нет" and b == "Нет" and c == "Нет":
queryString = prefix + """
SELECT ?Pizza
WHERE {
?Pizza rdfs:subClassOf :NamedPizza.
Minus {?Pizza rdfs:subClassOf :ExoticPizza}.
Minus {?Pizza rdfs:subClassOf :VegetarianPizza.}
Minus {?Pizza rdfs:subClassOf :SpicyPizza.}
}
"""
return queryString
return "Неизвестный запрос"
master = Tk()
var = StringVar()
var1 = StringVar()
var2 = StringVar()
var3 = StringVar()
label = Label(master, text="Для выбора подходящей пиццы необходимо ответить на вопросы:")
label.pack()
var1.set("Не знаю")
var2.set("Не знаю")
var3.set("Не знаю")
label = Label(master, text="Вы вегетарианец?")
label.pack()
option = OptionMenu(master, var1, "Да", "Нет", "Не знаю")
option.pack()
label = Label(master, text="Вы любите острое?")
label.pack()
option = OptionMenu(master, var2, "Да", "Нет", "Не знаю")
option.pack()
label = Label(master, text="Предпочитаете традиционную пиццу?")
label.pack()
option = OptionMenu(master, var3, "Да", "Нет", "Не знаю")
option.pack()
def ok():
sparql = SPARQLWrapper("http://localhost:3030/new-last-pizza/query")
queryString = queryResult(var1.get(), var2.get(), var3.get())
if queryString != "Неизвестный запрос":
sparql.setQuery(queryString)
sparql.setReturnFormat(JSON)
results = sparql.query().convert()
if (len(results["results"]["bindings"]) == 0):
var.set("No results found.")
else:
res = ""
for result in results["results"]["bindings"]:
# split('#')[1] позволяет вывести только имя пиццы (см. п.11.5 эл. учебника)
res = res + result["Pizza"]["value"].split('#')[1] + '\n'
var.set(res)
else:
var.set("Что-то пошло не так, повторите выбор :-(")
button = Button(master, text="Готово!", command=ok)
button.pack()
label = Label(master, textvariable=var)
label.pack()
mainloop()
Результат работы программы: