Из командной строки за знаниями


    Один из наиболее распространенных стандартов работы с базами знаний являются представление RDF и язык запросов SPARQL. Доступ к базе обычно осуществляется через SPARQL-endpoint по протоколу HTTP (Jena и Sesame могут использоваться как встраиваемые базы, например через обертку banana-rdf, а к Virtuoso можно обращаться так же по ODBC, добавив к строке запроса префикс 'SPARQL ').
    Есть много открытых «точек доступа SPARQL» — по wikipedia DBpedia, большой набор биологических баз знаний, геоданные.
    К endpoint, как правило, прилагается web-интерфейс, но браузер — это слишком громоздко, и мы хотим обращаться к ним напрямую из командной строки!

    Для серьезной работы можно воспользоваться готовыми библиотеками, существующими под многие языки, в том числе и ориентированные на анализ данных (например R). Нас же интересует возможность быстро составить запрос для получения информации или отладки самого запроса.

    Язык запросов SPARQL синтаксически похож на SQL, а по семантике — на Prolog. Знания представляются неким подобием графа «с пометками» на узлах и ребрах. «Пометки» обычно представляют из себя URL (который не обязан ни куда вести), а вершины без исходящих ребер — еще и типизированные данные. В SELECT задается шаблон подграфа и список полей этого шаблона, которые нас интересуют.

    В Unix-like операционных системах (например Windows 10) можно использовать bash, curl и специальный пакет jq для работы с json:
    curl -H "Accept: application/sparql-results+json" "http://data.semanticweb.org/sparql?query=PREFIX%20foaf%3A%20%3Chttp%3A%2F%2Fxmlns.com%2Ffoaf%2F0.1%2F%3E%0ASELECT%20DISTINCT%20%3Fperson%20%3Fname%0AWHERE%20%7B%20%3Fperson%20a%20foaf%3APerson%3B%0Afoaf%3Aname%20%3Fname%20%7D%20LIMIT%2010" | jq .results.bindings
    

    Использование PowerShell позволяет делать все это более по человечески.
    Опишем функцию, делающую запрос на получение данных к SPARQL-серверу:
    function sparql_raw([String]$query, [String]$endpoint, [String]$graph="", [String]$prefix="", [String]$format="application/sparql-results+json") {
      $dg = if ($graph -eq "") {
        ""
      } else {
        "default-graph-uri=$([uri]::EscapeDataString($graph))&"
      }
      $req = "${endpoint}?${dg}query=$([uri]::EscapeDataString($prefix+$query))&format=$([uri]::EscapeDataString($format))"
      Invoke-RestMethod -Headers @{"Accept"=$format} -uri $req
    }
    

    Для удобства можно задать параметры по умолчанию.
    function prefixes([String]$key) {
      "PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>"
    }
    
    function defaultGraph([String]$key) {
      switch -Regex ($key) {
       "http://dbpedia.org/sparql" { "http://dbpedia.org" }
       default { "" }
      }
    }
    
    function sparql_raw([String]$query, [String]$endpoint="http://dbpedia.org/sparql", [String]$graph=(defaultGraph $endpoint), [String]$prefix=(prefixes $endpoint), [String]$format="application/sparql-results+json") {
      $dg = if ($graph -eq "") {
        ""
      } else {
        "default-graph-uri=$([uri]::EscapeDataString($graph))&"
      }
      $req = "${endpoint}?${dg}query=$([uri]::EscapeDataString($prefix+$query))&format=$([uri]::EscapeDataString($format))"
      Invoke-RestMethod -Headers @{"Accept"=$format} -uri $req
    
    }
    

    Помимо строки запроса, она получает URL сервера, используемый по умолчанию граф (аналог имени базы данных в традиционных СУБД) и ожидаемого формата ответа. Стандарт описывает множество допустимых форматов, от html, до csv, я выбрал самый простой с сохранением метаинформации.
    Ответ выглядит примерно так:
    {
            "head": {
                    "vars": [ "person", "name" ]
            },
            "results": {
                    "bindings": [
                            {
                                    "person": { "type": "uri", "value": "http:\/\/kantenwerk.org\/metadata\/foaf.rdf#me" },
                                    "name": { "type": "literal", "value": "Knud Möller" }
                            },
                            {
                                    "person": { "type": "uri", "value": "http:\/\/tomheath.com\/id\/me" },
                                    "name": { "type": "literal", "value": "Tom Heath" }
                            }
                    ]
            }
    }
    

    Формат ответа некоторые сервера ожидают в параметрах GET-запроса, а некоторые — в заголовке Accept. Наша функция, на всякий случай, передает его и там, и там.

    Теперь можно представить ответ в читабельном виде:
    function sparql_light([String]$query, [String]$endpoint="http://dbpedia.org/sparql", [String]$graph=(defaultGraph $endpoint)) {
      $res = (sparql_raw -format 'application/sparql-results+json' $query $endpoint $graph)
      $vars = $res.head.vars
      $r = $res.results.bindings
      foreach ($i in $r) {
        $h = @{}
        foreach ($n in $vars) {
          $h[$n] = $i.$n.value
        }
        new-object PSCustomObject -Property $h
      }
    }
    


    Теперь мы можем узнать много нового не отрываясь от терминала!

    Выяснить в каких метаболических путях какие вещества участвуют:
    sparql_light -endpoint "http://kegg.bio2rdf.org/sparql" '
       select distinct ?subst ?path
       where {
         ?x <http://bio2rdf.org/kegg_vocabulary:interaction> ?y.
         ?x <http://bio2rdf.org/kegg_vocabulary:pathway>?p.
         ?p <http://purl.org/dc/terms/title> ?path.
         ?y <http://purl.org/dc/terms/title> ?subst.
    } LIMIT 100'
    


    Или какие данные в Википедии есть про Лондон:
    sparql_light '
      select distinct ?label ?type ?value
      where {
       ?x ?p <http://en.wikipedia.org/wiki/London>.
       ?x ?y ?value.
       BIND(DATATYPE(?value) as ?type).
       FILTER(bound(?type)).
       ?y <http://www.w3.org/2000/01/rdf-schema#label> ?label.
       FILTER (LANG(?label) = "en" ).
       FILTER (not exists {?x ?y ?a. ?x ?y ?b. FILTER(?a != ?b).}).
    }'
    


    Получить список хакспейсов:
    sparql_light -endpoint "http://linkedgeodata.org/sparql" -graph "http://linkedgeodata.org" "
      select ?name ?addr ?home
      where {
       ?x a <http://linkedgeodata.org/ontology/Hackerspace>.
       ?x <http://linkedgeodata.org/ontology/addr%3Acity> ?addr.
       ?x <http://xmlns.com/foaf/0.1/homepage> ?home.
       ?x <http://www.w3.org/2000/01/rdf-schema#label> ?name.
     } limit 100"
    


    Узнать кто аффелирован не IBM Research:
    sparql_light -endpoint "http://data.semanticweb.org/sparql" '
      PREFIX foaf: <http://xmlns.com/foaf/0.1/>
      PREFIX swrc: <http://swrc.ontoware.org/ontology#>
      SELECT DISTINCT ?person ?affiliation
      WHERE {
       ?personid a foaf:Person.
       ?personid swrc:affiliation $affiliationid.
       ?perfonid foaf:name ?person.
       ?affiliationid foaf:name ?affiliation.
       filter( ?affiliation != "IBM Research" && ?affiliation != "IBM Research Laboratory")
    } limit 100'
    


    А вы какие интересные запросы придумали?
    Удачи в добыче знаний!
    Share post

    Comments 3

      0
      проблема есть с линкед датой — проекты по большей частью институтские, то есть пока грант есть — оно обновляется. Как закончился — все, ку. мне вот сейчас контрол-поисковик научных публикаций сделать хочется, помню в облаке такой огромный сегмент был. И в итоге похоже буду Mendeley API использовать вместо романтического SPARQL
        0
        Есть такое. Но кое-что поддерживают разработчики Virtuoso. Dbpedia работает достаточно стабильно.
        0
        Вообще-то «добычи знаний» я как-то не заметил. Есть обычное «Data Mining» — не более. То есть, извлечение данных. Соответственно, нет и «базы знаний», а есть только базы данных.

        Может, автор сможет прояснить ситуацию?

        Only users with full accounts can post comments. Log in, please.