SPARQL запросы к содержимому HTML страниц

в 8:50, , рубрики: grab, python, semantic web, sparql, Семантическая Сеть, метки: , , ,

Здравствуйте.
После посещения одной конференции у меня появилась идея, воплощение которой я и представляю.
Данный пост предоставляет пример работы с библиотеками grab и rdflib, а также готовый класс для выполнения SPARQL запросов к содержимому web-страниц.

С целью выполнения SPARQL запросов к html содержимому необходимо создать локальное rdf хранилище, и наполнить его информацией, полученной из html страницы посредством grab.

Для начала импортируем все необходимые библиотеки, а также зарегистрируем использование SPARQL плагина.

# -*- coding: utf-8 -*-

import grab
import rdflib
from rdflib import *
from rdflib import plugin

plugin.register(
    'sparql', rdflib.query.Processor,
    'rdfextras.sparql.processor', 'Processor')
plugin.register(
    'sparql', rdflib.query.Result,
    'rdfextras.sparql.query', 'SPARQLQueryResult')

Определим наш класс, и построим конструктор для него.
Конструктор может принимать url страницы, которую необходимо раcпарсить и поместить в хранилище, а также может определять нестандартное расположение определения пространства имён.

class SQtHD():
    '''
    sparql query to html documents
    '''

    def __init__(self,url=None,htmlNamespace='http://localhost/rdf/html#'):
        '''
        Constructor
        '''
        self.__grab__=grab.Grab()#Наш парсер
        self.__storage__=Graph()#Наше хранилище
        self.__namespace__=Namespace(htmlNamespace)#Создаем пространство имен
        self.__storage__.bind('html', URIRef(htmlNamespace))#Задаем пространству имен короткое имя
        self.__initnamespace__=dict(self.__storage__.namespace_manager.namespaces())
        if url:#Если необходимо, то загружаем содержимое указанной страницы в хранилище.
            self.__store__(url)

Далее необходимо определить служебную функцию для наполнения хранилища содержимым страницы.

    def __store__(self,url):
        self.__storage__.remove((None,None,None))#Очищаем хранилище
        self.__grab__.go(url)#Выполняем переход по указанному адресу средствами grab
        root=self.__grab__.tree.getroottree().getroot()
        self.__parse__(root)#Парсим содрежимое страницы.

В локальное хранилище помещается следующая информация о элементах:

  • Информация о типе элемента (какой html тэг)
  • Информация о родительском элементе
  • Информация о позиции элемента по отношению к братьям
  • Информация о уровне вложенности элемента
  • Тест содержащийся в элементе
  • Информация о количестве дочерних элементов
  • Ссылки на дочерние элементы
  • Значение атрибутов для элемента

Следующая служебная функция рекурсивно проходит по дереву элементов собирая необходимую информацию и добавляя её в хранилище.

    def __parse__(self,element,parent=None,children_position=None,children_level=0):
        current_element=BNode()
        children_elements=element.getchildren()
        if str(element.tag)=='<built-in function Comment>':
            self.__storage__.add((current_element, RDF.type, 
                                  self.__namespace__['comment']))
        else:
            self.__storage__.add((current_element, RDF.type, 
                                  self.__namespace__[element.tag]))
        if not parent==None:
            self.__storage__.add((current_element,self.__namespace__['parent'],parent))   
            self.__storage__.add((parent,self.__namespace__['children'],
                                  current_element))    
            self.__storage__.add((current_element,self.__namespace__['children_position'],
                                  Literal(children_position)))     
        self.__storage__.add((current_element,self.__namespace__['children_level'],
                              Literal(children_level)))     
        if element.text and len(element.text.strip())>0:
            self.__storage__.add((current_element,self.__namespace__['text'],
                                  Literal(element.text.strip())))
        if element.text_content() and len(element.text_content().strip())>0:
            self.__storage__.add((current_element,self.__namespace__['text_content'],
                                  Literal(element.text_content().strip())))
        self.__storage__.add((current_element,self.__namespace__['children_count'],
                              Literal(len(children_elements))))
        for i in element.attrib:
            self.__storage__.add((current_element,self.__namespace__[i],
                                  Literal(element.attrib[i])))
        for i in range(len(children_elements)):
            self.__parse__(children_elements[i],current_element,i,children_level+1) 

Данная функция выполняет SPARQL запрос к локальному хранилищу.

    def executeQuery(self,query,url=None):
        '''
        execute query on storadge
        '''
        if url:#Если необходимо, то загружаем содержимое указанной страницы в хранилище.
            self.__store__(url)
        return self.__storage__.query(query,
                                  initNs=self.__initnamespace__)#Возвращаем результат выполнения запроса.

Данная функция наполняет хранилище содержимым указанной страницы.

    def loadStoradge(self,url):
        '''
        load and parse html page to local rdf storadge
        '''
        self.__store__(url)

И напоследок, несколько простых примеров запросов.

if __name__ == "__main__":
    endPoint = SQtHD()#Создаем экземпляр класса SQtHD
    
    endPoint.loadStoradge('http://habrahabr.ru')#Загружаем страницу в хранилище

    print "All sources for images given by tag <img>:"#Вывести все уникальные адреса картинок
    q=endPoint.executeQuery('SELECT DISTINCT ?src { ?a rdf:type html:img. ?a html:src ?src. }')
    for row in q.result:
        print row
    print

    print "All link urls:"#Вывести все уникальные адреса ссылок
    q=endPoint.executeQuery('SELECT DISTINCT ?href { ?a rdf:type html:a. ?a html:href ?href. }')
    for row in q.result:
        print row
    print
        
    print "All class names for elements:"#Вывести все уникальные имена классов
    q=endPoint.executeQuery('SELECT DISTINCT ?class { ?a html:class ?class. }')
    for row in q.result:
        print row
    print
    
    '''
    print "All scripts (without loaded by src):"#Тест всех внутристраничных скриптов.
    q=endPoint.executeQuery('SELECT ?text { ?a rdf:type html:script. ?a html:text ?text. }')
    for row in q.result:
        print row
    print'''
    
    print "All script srcs:"#Все ссылки на скрипты.
    q=endPoint.executeQuery('SELECT ?src { ?a rdf:type html:script. ?a html:src ?src. }')
    for row in q.result:
        print row
    print

Результат исполнения запроса на вывод всех ссылок на скрипты:

All script srcs:
/javascripts/1341931979/all.js
/javascripts/1341931979/_parts/posts.js
/javascripts/1341931979/_parts/to_top.js
/javascripts/1341931979/_parts/shortcuts.js
/javascripts/1341931979/libs/jquery.form.js
/javascripts/1341931979/facebook_reader.js
/js/1341931979/adriver.core.2.js
/javascripts/1341931979/libs/highlight.js
/javascripts/1341931979/hubs/all.js
/javascripts/1341931979/posts/all.js

Таким образом, существует 3 способа наполнить хранилище:

  1. При инициализации класса
  2. Посредством функции loadStoradge
  3. При каждом запросе к хранилищу

Использовать данный инструмент предполагается для превращения информации с сайтов, которые не предоставляют её в структурированном виде (rdf-тройки, xml, json), в понятный «машинам» вид.

GIST проекта, содержит также определение пространства имен посредством xml. Пространство имен определяет, что является тегом, закладывает необходимые свойства и отношения а также определяет тэги html 4.
Рекомендуемая литература:
«Programming the Semantic Web» By Toby Segaran, Colin Evans, Jamie Taylor

Автор: rndr

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js