В этой статье показано, как выглядит работа с паутиной данных с точки зрения программиста. Примеры даны на языке Python, но должны быть в основном понятны и незнакомым с Python программистам.
В статье «Публикация Linked Data» мы добавили LD в интернет-магазин «Рога и копыта». В магазине есть одно предложение — молоток — чей адрес в паутине данных http://linked-data.ru/example/products/1/#product. Представим себя на месте программы-клиента Linked Data, которая должна, зная только этот адрес, выяснить цену товара и телефон предлагающей компании.
Для работы с данными, представленными по модели RDF, нам нужна специальная RDF-библиотека. В её обязанности входит, по меньшей мере:
Таких библиотек существует немало, разной степени полноты и законченности, для всевозможных платформ и языков, например:
В этой статье мы будем использовать RDFLib в интерактивной среде Python. Принципы использования других библиотек похожи. Если вы хотите повторить примеры самостоятельно, вам потребуется RDFLib версии 3.0. Можете скачать программу-пример shop.py целиком.
Начинаем с очевидного.
Python 2.5.2 (r252:60911, Jan 24 2010, 17:44:40) [GCC 4.3.2] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import rdflib >>>
По условию нам известен адрес (URI) товара, о котором мы хотим разузнать побольше. В RDFLib предметы с адресами представляются с помощью класса URIRef.
>>> product = rdflib.URIRef('http://linked-data.ru/example/products/1/#product')
>>> print product
http://linked-data.ru/example/products/1/#product
Как и при публикации Linked Data, при потреблении приходится постоянно иметь дело с адресами, которые часто берутся из одних и тех же словарей. В сериализациях RDF эта проблема решается с помощью префиксов, позволяющих сокращать общие части адресов. Похожая идея используется в RDFLib: мы создаём объекты-префиксы (класс Namespace), а потом извлекаем из них отдельные URIRef’ы.
>>> rdf = rdflib.Namespace('http://www.w3.org/1999/02/22-rdf-syntax-ns#')
>>> gr = rdflib.Namespace('http://purl.org/goodrelations/v1#')
>>> vcard = rdflib.Namespace('http://www.w3.org/2006/vcard/ns#')
>>> print gr.Offering
http://purl.org/goodrelations/v1#Offering
Теперь мы создаём в памяти RDF-граф, который, собственно, и будет содержать все наши данные.
>>> graph = rdflib.Graph() >>>
Нам нужно извлечь из Паутины данные по адресу product и добавить их в рабочий граф. Для этого достаточно использовать метод parse.
>>> graph.parse(product, format='rdfa') <Graph identifier=MlptZeKp0 (<class 'rdflib.graph.Graph'>)>
В аргументе format мы указываем ожидаемый формат данных. Вообще-то RDFLib пытается угадать его сама, но это у неё не всегда получается. Если общением с сервером занимается само приложение (например, через библиотеку httplib2), то оно может судить о формате по HTTP-заголовку Content-Type.
Теперь мы можем убедиться, что в графе есть какие-то данные.
>>> print len(graph) 8
8 — это число рёбер (стрелок) в графе. Они ещё называются утверждениями, а также триплетами (triples), так как состоят из трёх частей: субъект (предмет, от которого начинается стрелка), предикат (адрес самой стрелки) и объект (предмет или значение, к которому ведёт стрелка).
Определим цену предложения. Для этого нам сначала надо найти предмет-цену, то есть тот предмет, который связан с product свойством gr:hasPriceSpecification. Для этого мы, методом graph.objects, попросим RDFLib перечислить объекты всех триплетов, в которых субъектом является product, а предикатом — gr:hasPriceSpecification. Другими словами, graph.objects перечисляет все узлы, к которым ведут стрелки gr:hasPriceSpecification от узла product.
>>> [price] = graph.objects(product, gr.hasPriceSpecification) >>> print price http://linked-data.ru/example/products/1/#price
Аналогично определяем саму величину цены.
>>> [roubles] = graph.objects(price, gr.hasCurrencyValue) >>> print roubles 1200
Итак, цену мы выяснили. Следующая задача — узнать телефон компании. Для нахождения в графе предмета-компании вместо метода objects мы используем subjects. Он, наоборот, перечисляет все узлы, от которых идут указанные стрелки (gr:offers) к указанному объекту (product).
>>> [company] = graph.subjects(gr.offers, product) >>> print company http://linked-data.ru/example/#company
В соответствии с принципами Linked Data, чтобы узнать больше о компании, нам надо перейти по её адресу.
>>> graph.parse(company, format='rdfa') <Graph identifier=MlptZeKp5 (<class 'rdflib.graph.Graph'>)> >>> print len(graph) 13
По сути, мы перешли по ссылке (от product к company), которую нашли в описании товара, и тем самым получили новые данные — описание компании. В граф добавилось 5 новых утверждений.
Найдём теперь в графе предмет-телефон и через него определим сам номер телефона.
>>> [tel] = graph.objects(company, vcard.tel) >>> [tel_no] = graph.objects(tel, rdf.value) >>> print tel_no +7 495 123 45 67
Для RDF существует полноценный язык запросов, который называется SPARQL. Он же одновременно является протоколом, по которому можно передавать запросы к удалённым серверам. Но далеко не все сайты, на которых есть Linked Data, предоставляют публичные точки SPARQL. Это и понятно, ведь SPARQL позволяет делать произвольные запросы, которые могут сильно нагрузить сервер.
Однако SPARQL можно применять и на стороне клиента: собрать данные с одного или нескольких сайтов в память или локальное хранилище, а затем уже над ними производить нужные запросы. Реализации SPARQL встроены во многие RDF-библиотеки (в RDFLib 3.0, к сожалению, поддержка SPARQL не входит, но её можно взять в дополнительном пакете rdfextras).
Вот как может выглядеть запрос, извлекающий из графа все товары с их ценами и телефонами соответствующих компаний.
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX gr: <http://purl.org/goodrelations/v1#>
PREFIX vcard: <http://www.w3.org/2006/vcard/ns#>
SELECT ?product ?roubles ?tel_no
WHERE {
?product gr:hasPriceSpecification [ gr:hasCurrencyValue ?roubles ] .
[ vcard:tel [ rdf:value ?tel_no ] ] gr:offers ?product .
}
Для работы с паутиной данных существует большое количество готовых средств. С их помощью можно создать LD-клиента, который сможет обрабатывать данные на любых сайтах (если эти данные записаны с помощью соответствующих словарей). Более того, он может объединять данные с разных сайтов и делать к ним сложные запросы.