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

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

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


С целью выполнения 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. При каждом запросе к хранилищу


GIST проекта, содержит также определение пространства имен посредством xml. Пространство имен определяет, что является тегом, закладывает необходимые свойства и отношения а также определяет тэги html 4.
Рекомендуемая литература:
«Programming the Semantic Web» By Toby Segaran, Colin Evans, Jamie Taylor
Share post
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 5

    0
    В сторону HTSQL не смотрели?
      0
      пардон, спутал…
      +1
      У нас для аналогичных целей используется другой подход.
      Формируется SPARQL-запрос с внедрёнными XPath-локаторами и, далее, в потоковом режиме извлекаются соответствующие ноды. Только потом формируется граф и, при необходимости, фильтруется и обрабытывается construct часть. Это позволяет работать с достаточно крупными документами (до 2-5 Гб в зависимости от сложности).
      Изначально использовался подход, аналогичный Вашему, но возникли проблемы с производительностью и сложностью самих SPARQL-запросов. Всё же XPath в данном случае удобнее.
      • UFO just landed and posted this here
          0
          RDF RDF-у рознь.
          Написали б в первых строках, какого рода отношения и между какого рода сущностями Вы получаете из текста страницы — было б многое понятнее.

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

          Этот перечень можно понимать с разной степени буквальности, и максимально буквально — в последнюю очередь. Как-то не сразу верится, что ваша «Информация о» — это просто запись собственно того, что стоит после «о», а не каких-то разнообразных подробностей информации об этом. В смысле, «Информация о родительском элементе» — не какие-то там подробности о нём, а просто RDF-запись «вот этот элемент — имеет родителем — вот тот элемент».

          Однако, при внимательном чтении следующего за тем абзацем исходника, получается, что в RDF-хранилище попадает не более чем. Элементы — лишь то, что возвращает внутри функции HTML-парсера функция BNode, то есть, лишь элементы DOM страницы. Типы отношений — всего несколько, из пространства имён 'http://localhost/rdf/html#'

          Про них рекурсивно выписываются лишь эти тройки:
          current_element — RDF.type — self.__namespace__['comment'] или self.__namespace__[element.tag]))
          current_element — self.__namespace__['parent'] — parent
          parent — self.__namespace__['children'] — current_element)
          current_element — self.__namespace__['children_position'] — Literal(children_position)
          current_element — self.__namespace__['children_level'] — Literal(children_level)
          current_element — self.__namespace__['text'] — Literal(element.text.strip())
          current_element — self.__namespace__['text_content'] — Literal(element.text_content().strip())
          current_element — self.__namespace__['children_count'] — Literal(len(children_elements)
          current_element — self.__namespace__[i] — Literal(element.attrib[i])

          И никакого NLP, никакого OWL и никаких онтологий и семантики.

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