Простое извлечение микроформатов с помощью XSLT

Микроформаты — это хорошо, просто и понятно.

Как можно извлечь микроформатированный контент из документа и сделать с ним миллион классных штук?

XSLT

Удобно для запросов в XML-документе на извлечение микроформатированного контента использовать выражения XPath.

Вот пример «ручной» работы с XHTML-документом, содержащим μf:

Пример работы с XPath

(Требуется утилита xmllint из пакета libxml).

Запрашиваем страницу:

$ wget dzhus.org/author

Открываем XML-консоль:

$ xmllint --shell index.html

Устанавливаем префикс пространства имён, чтобы были видны XHTML-элементы:

/ > setns xhtml=http://www.w3.org/1999/xhtml

Запрашиваем элемент с именем класса «vcard» (вернёт все hCard на странице):

/ > xpath //xhtml:*[@class='vcard']
Object is a Node Set :
Set contains 1 nodes:
1  ELEMENT div
    ATTRIBUTE id
      TEXT
        content=owner-vcard
    ATTRIBUTE class
      TEXT
        content=vcard

Или из всех hCard на странице выбираем ссылки, у которых установлен XFN-аттрибут rel="me":

/ > xpath //xhtml:*[@class='vcard']/xhtml:a[@rel='me']
Object is a Node Set :
Set contains 1 nodes:
1  ELEMENT a
    ATTRIBUTE class
      TEXT
        content=url
    ATTRIBUTE rel
      TEXT
        content=me
    ATTRIBUTE href
      TEXT
        content=http://dzhus.org
    ATTRIBUTE title
      TEXT
        content=#D0#9B#D0#B8#D1#87#D0#BD#D1#8B#D0#B9 #D1#81#D0#B0#D0#B9#D1#82

В реальных задачах по извлечению μf применимы преобразования XML при помощи XSLT. В XSLT можно извлекать данные из исходного XML-документа с применением выражений XPath.

Использование XSLT позволяет разделить логику программной обработки страниц и непосредственно извлечение/форматирование данных, позволяет использовать одни и теже механизмы извлечения в разных программах на разных языках. Кроме того, XSLT просто ближе к предметной области, чем явное кодирование разбора XML-дерева в тексте обрабатывающей программы, ИМХО. Синтаксис выражений XPath также скрывает детали реализации сложной фильтрации и выборки элементов из XML.

Недостатком XSLT является достаточно громоздкий синтаксис (в общем случае это пипец полный, но увы — XML =).

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

Микроформаты, GRDDL и Семантическая Паутина

Поскольку микроформаты — маленький промежуточный шаг на пути к Semantic Web, по-хорошему нужно использовать GRDDL — специальный механизм обозначения RDF-совместимых данных в XML-документе, а при помощи GRDDL однообразно извлекать микроформатированный контент. Возможно, GRDDL обеспечит тот будущий великий перенос данных на сверхвысокий уровень представления, к которому сейчас готовятся многие люди, маленькими шажками (например, используя микроформаты) увеличивая «семантичность» данных. На страницах w3.org предлагается интересный обзор возможных случаев использования GRDDL.

С RDF-данными можно работать на гораздо более высоком уровне, применяя специальные языки запросов. По сравнению с возможностями RDF-технологий копание в коде веб-страниц при помощи XSLT выглядит минимум суетливо.

На землю

Semantic Web — конечно великая цель и очень хорошо, но большинство двуногих обезьян, создающих страницы для Сети не могут написать даже well-formed документ (не говоря уже о валидности), чтобы хоть как-то упростить жизнь создателям средств обработки данных. Так что бок о бок с XML-парсерами существуют «real-world» парсеры даже не HTML, а чего-то непонятного (часто это деликатно называется Tag Soup, на самом деле это обычно блевота из тэгов), которые пытаются разобрать и угадать как можно больше из того некорректного кода, который часто встречается в Интернете, как это делают браузеры. Эвристика, болото частных случаев и попытка заочно вправить мозги задним числом верстальщикам — в итоге не всегда устойчивый результат и непрозрачная логика.

Тем не менее, я представлю простой пример основанного на XSLT обработчика распределённых социальных сетей, основанных на XFN. Во время обработки буду также учитывать rel-tag.

Реализация

Инструменты

В libxml есть возможности «восстановления» «повреждённого» кода с веб-страниц. Там всё равно достаточно строгий парсер, хотя с включённым восстановлением удаётся увеличить количество разбираемых страниц в несколько раз. Тем не менее, не удаётся разобрать до 35% процентов веб-страниц. Было бы удобно, однако, использовать libxml, поскольку в ней также очень быстрый XSLT-процессор.

Tidy — то, что нужно. Tidy позволяет с большой долей успеха формировать из побитых веб-страничек валидные XHTML-документы.

Для извлечения микроформатированного контента будет использоваться простой стилевой файл XSL.

Принцип работы

В принципе, схема рекурсивной обработки XFN должна быть уже очевидна. Программа будет принимать пару параметров — URL «точки входа» и глубину обработки. Страница, запрошенная по переданному в качестве параметра URL, будет обработана Tidy и разобрана парсером libxml, после чего на неё будет наложено XSL-преобразование. Предлагается в качестве результата обработки каждой страницы использовать промежуточный XML-формат, простой и удобный для последующей обработки.

XSL-преобразование, применяемое к каждой странице, будет извлекать интересующий микроформатированный контент, в числе которого будут размеченные с учётом XFN ссылки. Список этих ссылок составит новый набор страниц для обработки, к каждой из которых вся схема будет применена аналогично. Таким образом, используется тип рекурсивной обработки «в глубину» (а не «в ширину»).

На каждом шаге обработки будет проверяться текущая «глубина» (то, насколько в работе программа ушла от «нулевого уровня» — самой первой страницы). При достижении максимально допустимой глубины уже не будет производиться поиск новых страниц для обработки.

Страницы, находящиеся сразу за самым глубоким уровнем, однако, всё равно будут запрошены и обработаны, но по упрощённой схеме: будет извлечён лишь заголовок страницы. Это очень расточительно (запрос, обработка страницы лишь для извлечения значения <title></title>!), но в таком случае будет известен заголовок каждой страницы, на которую ссылается любая другая в обрабатываемой сети. Это очень полезно для генерации карты сети по результатам работы программы (если бы не запрашивались заголовки страниц на «границе» обработки, пришлось бы в качестве названий узлов на границе карты использовать их URL — в большом количестве выглядит убого и сильно мешает восприятию; впрочем, заголовки страниц в качестве меток — тоже громоздко).

Результатом работы предполагается XML-файл со списком обработанных страниц. В каждом элементе будут содержаться приемлемо организованные извлечённые микроформатированные данные.

<?xml version="1.0"?>
  <network>
    <site url="http://bopik.com/">
      <title>Bopik's Writings</title>
      <rss>http://bopik.com/feed.xml</rss>
      <relations>
        <rel url="http://hardwaremaniacs.org/blog" type="friend colleague" />
        <rel url="http://qurinal.com" type="date neighbor" />
      </relations>
      <tags>
        <tag>violence</tag>
        <tag>microformats</tag>
        <tag>phtagn</tag>
      </tags>
    </site>
    <site url="http://qurinal.com">
      <title>qUrinal: best info source for developers</title>
    </site>
    <site url="http://hardwaremaniacs.org/blog">
      <rss>http://hardwaremaniacs.org/feeds/blog/25</rss>
      <title>Harware Maniacs</title>
    </site>
  </network>

Он, в свою очередь, подлежить дальнейшей обработке с целью получения более осязаемых результатов.

Рабочий пример

Пример исходников доступен по адресу http://github.com/dzhus/xfn-spider (лучше брать самую последнюю ревизию):

  • mf-extract.xsl и mf-extract-bottom.xsl — пара простых стилей для извлечения µf. mf-extract-bottom.xsl применяется к страницам, которые находятся на уровне глубины обработки после максимального и извлекает только заголовок.

  • get-urls.xsl накладывается на результат работы mf-extract.xsl и формирует список URLов для последующей обработки

  • postprocess.xsl мочит дубликаты с разными URL-ами, но одинаковыми заголовками страницы. Достаточно успешно.

  • xfn-spider.py — пример обёртки на Python вышеупомянутых четырёх стилей. Думаю, что исходник вполне понятен, и его можно легко перенести на другой язык (включая enterprise-level языки типа whitespace).

Работает так:

$ python xfn-spider.py http://entry-point.com 5 > output.xml

Отладочные сообщения выводятся на stderr и не попадают в output.xml.

Можно добавлять дополнительные действия по извлечению контента на уровне XSL-преобразований, не меняя Python-обёртку: механизм отделён от политики.

Пояснения также есть в файлах README и TODO.

Что делать дальше

Полученный в результате работы output.xml — бесполезный полуфабрикат.

Можно сделать с ним что-нибудь прикольное.

Например, визуализировать.

Many eyes

Many eyes — инициатива IBM по созданию общественного ресурса по обмену статистическими данными и предоставлению средств визуализации этих данных. Короче говоря, можно туда загрузить свой dataset и визуализировать его. Возможностей в тысячу раз меньше, чем у нормальных средств визуализации (R, OpenDX или ROOT), но в Many Eyes дан упор на общественность процесса и «collaborating». И ещё там везде Java. Корпорэйты, что с них взять.

Карта XFN

Мне приглянулась имеющаяся там визуализация «Network»: более-менее нормальные картинки, сглаживание, красивая подсветка и всё такое. На входе для визухи сети нужен список пар — соединённых узлов, типа:

Петя Вася
Коля Вася
Вася Настя
Петя Настя

Колонки разделены символом табуляции.

Из полученного в результате работы xfn-spider.py XML-я такой набор пар несложно получить ещё одним XSL-преобразованием (лежит в репо под именем make-manyeyes-network.xsl):

$ xsltproc make-manyeyes-network.xsl output.xml > many-eyes.txt
$ cat many-eyes.txt

Полученный набор данных можно загрузить на Many Eyes и получить интерактивненькую карту XFN (Челик сказал «woha»):

Визуализация на Many Eyesmisc image

(Эта визуализация очень неполная и была получена на начальном этапе работы над описываемыми инструментами; в реальности узлов гораздо больше.)

Минус — тип связей никак не используется (впрочем, при большом количестве узлов разноцветные рёбра читаются ещё хуже просто чёрных).

Облако тэгов на всю XFN

Помимо XFN-ссылок извлекались также и размеченные при помощи rel-tag тэги на сайтах. Можно из результирующего XML просто дёрнуть их список и получить облако тэгов (HEY LOOK I’M WEB 2.0!).

Пусть у меня есть output.xml — результат xfn-spider.py. Тэги можно вырезать и простым грепом, но я снова хочу использовать Many Eyes и визуализацию «Tag Cloud», для неё лучше подготовить не просто список тэгов, а пары «тэг — количество упоминаний». Для этого снова можно применить совсем примитивное XSL-преобразование make-manyeyes-tags.xsl:

$ xsltproc make-manyeyes-tags.xsl output.xml > tags.txt
$ cat tags.txt
craig ferguson  1
cryotherapy 1
cryptography    2
culture 2
danny hillis    1
data mining 1
data web    2
data-web    4

Используя такой набор данных, можно получить подобное «облако»:

misc

Graphviz

Рисовать сети на Many Eyes — забавно, но есть Graphviz — славный набор средств для качественной отрисовки графов. Можно преобразовывать описание XFN в формат DOT, который принимают программы Graphviz. Это имеет смысл при обработке особо больших сетей (на глубину >20), а то, скажем апплет Many Eyes может поперхнуться таким количеством узлов в сети.

Создание файла OPML со списком лент XFN

Тут тоже всё понятно. На большинстве сайтов уважают RSS и в коде есть указание на RSS-ленту:

<link rel="alternate" type="application/rss+xml" href="/blah-blah-feed.xml" />

При обработке XFN нужно выжимать из страниц как можно больше информации, если она удобно размечена.

Есть OPML — достаточно широко используемый XML-формат для обмена структурированными списками. Часто применяется для импорта/экспорта списков RSS/Atom-лент и обмена списками лент между разными RSS-читалками.

Так вот, выгодно при обработке сети заодно для каждого сайта сохранять ссылку на его RSS-представление, потом из списка RSS-представлений генерить файл OPML (или другого формата), чтобы потом его использовать для импорта в любимой RSS-читалке (например, Google Reader) и читать всю тусовку!

make-opml.xsl does the job:

$ xsltproc make-opml.xsl output.xml > feeds.opml

После чего feeds.opml можно импортировать прямиком в RSS-ридер.

Однажды собрав список лент и получив доступ к «синдицированному» контенту, можно тоже отслеживать всякие тренды и подбивать статистику по всей XFN.

Недостаткомъ подхода является то, что не все ставят ссылку на RSS-представление в надлежащее место — тэг <link>.

Если во время обработки XFN не просто собирать XFN-ссылки, но ещё и извлекать размеченные при помощи OPML блогроллы (понятно, что список XFN-ссылок на друзей и просто блогроллы не всегда совпадают) — toodoo.ru становится ненужным :)

Обработка социальных сетей

Однажды я поставил обработку на глубину 30, но через десять минут понял, что такого количества трафика у меня нет: уже на третьем уровне паучок вышел на чей-то профиль в twitter.com, а там на каждой странице участника список друзей размечен при помощи XFN и модно иметь по 30 «друзей» :) В общем, через час просто надоело ждать :- Как-то странно вышло, до этого при тестировании робот не выходил на Twitter-овские связи. Quick googling показало, что как раз в этот день на Twitter внедрили микроформаты

Так что визухи, ссылки на которые были выше — весьма неполные :)

Ясно что уже 30³ — это 27000 и до фига трафика и времени. Так что социальные сети обрабатывать стоит с осторожностью и, похоже, XFN-ссылки — не лучший способ для этого (слишком ресурсоёмко).

В общем, обработка XFN с точкой входа на том же http://tantek.com на глубину хотя бы 7 — задача явно не для PC, причём именно из-за того, что робот выходит в Twitter, где сверхплотные и сверхгустые XFN-связи.

Вот, что мне удалось в итоге получить (глубина всего лишь 3):

Many Eyes visualization misc

Недостатки использования XSLT

Fucking slow! Задержки сети при получении всей страницы — огромные! Обработки сети с глубиной 10 — дело многих минут. Основные проблемы — когда попадается особо тормозной хост, запрос страницы с которого идёт чуть ли не десять секунд. Сам парсинг занимает очень мало времени. В данном случае выгодно держать на каждом что-то типа кэша, как в XFN crawler, но это убивает соль микроформатов — простоту добавления и редактирования, и это лишний связующий уровень, снижающий прозрачность технологии.

Есть баги, связанные с обработкой разных ссылок на одни и те же ресурсы. Это просто сложно фильтровать. Я применил такую политику: из группы страниц с одинаковыми заголовками выбирается та, у которой больше всего связей. Она остаётся в результирующем файле без изменений. Все остальные лишаются списка тэгов и списка связей, для них остаются только заголовки. В итоге получается достаточно разумно и почти ничего не ломается. Фильтрация происходит на уровне postprocess.xsl. Отладка обработки затрудняется значительными временными затратами на извлечение данных.

Стандартная отмазка от автора

(афтара?)

Я не знаю XSLT — в стилях есть ужасные места (например, обработку XFN-ссылок, возможно, было бы проще сделать с использованием регулярных выражений из EXSLT; текущий вариант создан под воздействием grokXFN.xsl — стиля для преобразования XFN в FOAF).

git