среда, 29 октября 2014 г.

Разбор статей Wikipedia с помощью парсера Sweble.org. Как распарсить википедию.

Извлечение информации из вики-страниц с учётом их структуры представляется непростой задачей. Зачастую язык вики-разметки (WML) не имеет формального описания. Часто WML развивается путем добавления новых фич без стадии перепроектирования с большим грузом обратной совместимости. В итоге обычно описание WML сводится к набору примеров из документации и в "окончательном виде" язык определен в коде парсера вики-движка. При этом внутри вики движка разбор проводится не на основе грамматических правил, как это делается,  для большинства языков программирования и языков разметки, таких, как XML, а на основе поиска по шаблону. Найденные шаблоны превращаются в кусочки HTML-разметки, которые в итоге формируют вики-страницу в браузере пользователя. Это делает структурированный разбор вики-страниц трудной задачей. Тем не менее для разбора вики-страниц существуют готовые решения, и к счастью обычно не требуется прибегать к велосипедостроению. Об эдном из решений - парсере Sweble.org я и хочу рассказать в этом посте.

Некоторое время назад я делал "химичесикй поиск" по википедии, сервис, который позволяет искать статьи о химических веществах как по тексту, так и по структурной формуле. Для этого я скачивал дамп википедии, извлекал статьи про химические вещества и индексировал их с помощью поискового движка Lucene. Первая (и на данный момент единственная) версия поиска не делала какой-то особой обработки страниц, требовалось просто извлечь текст самой статьи, отделив его от разметки. Перед этим название статьи и структурная формула вещества извлекалась с помощью регулярного выражения perl-скриптом. Немного поискав в инетрнете, я нашёл несколько парсеров вики-разметки, при этом как-то случайно само собой получилось, что быстрее всего пошли дела с парсером Sweble.org. Теоретическую вводную в работу парсера Sweble.org можно получить в статье авторов (pdf).

Как и многие другие парсеры Sweble.org строит дерево разбора страницы. Это дерево в терминах Sweble.org называется WOM, или "Wiki Object Model". Парсер написан на Java и работать с ним проще всего из Java. Документация почти отсутствует, но имеются примеры, которые сильно помогают.

Итак, как же работать с разобранным деревом? Разобранная вики-статья представляется в виде дерева, узлы которого -- объекты реализующие интерфейс WtNode. Эти объекты принадлежат унаследованным интерфейсам и классам WtNodeList (интерфейс), WtUnorderedList, WtOrderedList, WtListItem, EngPage, EngProcessedPage, WtText, WtWhitespace, WtBold, WtXmlCharRef, WtXmlEntityRef, WtUrl, WtExternalLink, WtInternalLink, WtSection, WtParagraph, WtHorizontalRule, WtXmlElement и другим.

Обход дерева реализуется в соответствии с шаблоном проектирования посетитель (Visitor).

Для анлиза дерева разбора я написал небольшую утилитку, которая разбирает вики-статью с помощью Sweble.org и выводит дерево в текстовом формате. Например, для статьи Manzanate


{{Orphan|date=April 2011}}

{{chembox
| verifiedrevid = 425210668
|   Name =Ethyl 2-methylpentanoate
|   Reference =
|   ImageFile = Manzanate.png
|   ImageSize = 200px
|   ImageName =
|   IUPACName = Ethyl 2-methylpentanoate
|   OtherNames = Ethyl α-methylvalerate; Melon valerate
| Section1 = {{Chembox Identifiers
|   CASNo_Ref = {{cascite|correct|??}}
| CASNo = 39255-32-8
|   SMILES = CCCC(C)C(=O)OCC
  }}
| Section2 = {{Chembox Properties
|   Formula = C8H16O2
|   MolarMass = 144.21 g/mol
|   Density = 
|   MeltingPt =
|   BoilingPt =
  }}
}}

'''Manzanate''' is a [[flavor]] ingredient which has a fruity apple smell and with aspects of cider and sweet pineapple.[http://www.thegoodscentscompany.com/data/rw1006801.html Melon valerate]

==References==
{{reflist}}

[[Category:Carboxylate esters]]


{{ester-stub}}


будет сгенерирован примерно такой вывод

[WtListItem]

    [WtParagraph]
        [WtTemplate]
            [WtNodeList]
                [WtText] Orphan
            [WtNodeList]
                [WtTemplateArgument]
                    [WtNodeList]
                        [WtText] date
                    [WtNodeList]
                        [WtText] April 2011
    [WtText]
    [WtParagraph]
        [WtTemplate]
            [WtNodeList]
                [WtText] chembox
            [WtNodeList]
                [WtTemplateArgument]
                    [WtNodeList]
                        [WtText]  verifiedrevid
                    [WtNodeList]
                        [WtText]  425210668
                [WtTemplateArgument]
                    [WtNodeList]
                        [WtText]    Name
                    [WtNodeList]
                        [WtText] Ethyl 2-methylpentanoate
...поскипано
    [WtParagraph]
        [WtBold]
            [WtText] Manzanate
        [WtText]  is a
        [WtInternalLink]
            [WtText] flavor
        [WtText]  ingredient which has a fruity apple smell and with aspects of cider and sweet pineapple.
        [WtTagExtension]
    [WtText]
    [WtSection]
        --heading--
            [WtText] References
        --body--
            [WtText]
            [WtParagraph]
                [WtTemplate]
                    [WtNodeList]
                        [WtText] reflist
                    [WtNodeList]
            [WtText]
            [WtParagraph]
                [WtInternalLink]
            [WtText]
            [WtParagraph]
                [WtText]
                [WtTemplate]
                    [WtNodeList]
                        [WtText] ester-stub
                    [WtNodeList]
            [WtText] 


На первом этапе мне нужно было просто очистить статью от вики-разметки, оставивь только текст. Эта задача решена в примерах, поставляемых вместе с парсером Sweble.

Общий подход таков: сначала строим WOM-дерево как-то так:

import org.sweble.wikitext.engine.EngineException;
import org.sweble.wikitext.engine.PageId;
import org.sweble.wikitext.engine.PageTitle;
import org.sweble.wikitext.engine.WtEngineImpl;
import org.sweble.wikitext.engine.config.WikiConfig;
import org.sweble.wikitext.engine.nodes.EngProcessedPage;

...

//Установим простую конфигурацию по-умолчанию
WikiConfig config = DefaultConfigEnWp.generate();

//Тайтл меня особо не волнует. Сгенерируем что-нибудь на основе пути к данным в файловой системе
PageTitle pageTitle = PageTitle.make(config, pathStr);

PageId pageId = new PageId(pageTitle, -1);

//Скомипилируем (читай, распарсим) страницу.
EngProcessedPage cp = engine.postprocess(pageId, wikitext, null);


Напомню, что класс EngProcessedPage реализует интерфейс WtNode и, как видно из примера, представлят собой корневой узел WOM-дерева. Для того, чтобы обойти дерево произведя какие-либо полезные действия нужно создать класс, унаследованный от de.fau.cs.osr.ptk.common.AstVisitor и переопределить методы before, after и семейство методов visit В случае очистки от разметки можно взять из примеров проекта уже готовый класс org.sweble.wikitext.example.TextConverter.

Эту магию я планирую применить для извлечения и индексирования различных свойств химических веществ. Статьи, которые индексирует мой "химический поисковик" содержат специальный infobox (chembox, drugbox или ionbox, см. пример выше). В настоящий момент из этого инфобокса с помощью регулярного выражение извлекается только лишь структурная формула в формате SMILES. Использование парсера Sweble.org облегчит эту задачу и позволит единообразно извлечь множество других свойств вещества.

воскресенье, 5 октября 2014 г.

Простой термометр на Arduino UNO и температурном датчике DS18B20

Температурные датчики DS18B20 очень популярны. Они стоят очень дёшево, выдают температуру в цифровом виде, их достаточно легко подключить к микроконтроллерам по однопроводной шине 1-Wire таким образом, что одной ножки микроконтроллера достаточно чтобы адресовать несколько датчиков и считывать с них показания.

Я заказал несколько таких датчиков на eBay. Кроме того, у меня в распоряжении имелся LCD-индикатор размером 2 строки по 16 символов, оказавшийся, впрочем, слегка бракованным (некорректно отображаются 12 и 13-й символы в каждой строке), и мне уже давно хотелось попробовать его в действии. К моему удобству к этому ЖК-модулю был уже припаян I2C-адаптер на чипе PCF8574, точь в точь такой, как описан в этой статье. Из этой же статьи я почерпнул ряд полезных ссылок, в частности на библиотеку для работы LCD-экраном через этот I2C-адаптер.

Подружить Arduino UNO с температурным датчиком DS18B20 оказалось очень нетрудно. Датчик подключается через шину 1-Wire, пртокол которой можно программно реализовать в Arduino. Само собой, для этого имеется готовая библиотека. Кроме этого я использовал ещё одну внешнюю библиотеку для самого датчика DS18B20. В итоге я собрал вот такую схему:


... и написал такой нехитрый скетч:


Собранный термометр выглядит так:


На фотографии один датчик лежит просто так и показывает температуру 27.44 С, другой прикреплен кусочком изоленты к кружке с горячим чаем и показывает 55.00 С.