InterSystems iKnow. Загружаем данные из Вконтакте

Эта статья продолжает цикл рассказов (раз, два) об основных способах/сценариях использования iKnow — инструмента Natural Language Processing’а из стека технологий InterSystems.

Предыдущие посты на эту тему были в основном посвящены работе с данными уже после того, как те были помещены в домен (место, в котором и проходит весь анализ текста). Эта же статья будет о том, как правильно и удобно загрузить информацию в iKnow. В качестве примера рассмотрим загрузку информации о пользователях Вконтакте: их личных данных, постах и т.д.

Статья подразумевает некий базовый бэкграунд в области технологий InterSystems (в частности Caché ObjectScript).

Долгая дорога в домен

image

Если верить официальной документации, есть два сценария загрузки данных в существующий домен:

  1. Создается инстанс класса %iKnow.Source.Loader. Он привязан к конкретному домену (тому, id которого был передан в конструктор). Создается инстанс класса, реализующего интерфейс листера. У этого инстанса вызывается метод AddListToBatch с некоторыми аргументами, специфицирующими загружаемую информацию. Таким образом к текущему батчу домена добавляется новый список информации для загрузки. Это может быть проделано несколько раз. Для того, чтобы загрузить текущий батч в домен, у лоадера нужно вызвать метод ProcessBatch. Этот вариант лучше подходит для загрузок больших объемов.
  2. Создается инстанc класса, реализующего интерфейс листера, у этого инстанса вызывается метод ProcessList с некоторыми аргументами, специфицирующими загружаемую информацию, и загрузка происходит сразу в домен напрямую. Этот варинт лучше подходит для загрузок малых объемов.

Кастомизация листинга

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

Перед тем, как писать листер для постов Вконтакте, я написал обертку для некоторых методов Вконтакте API на COS, оперирующих данными в открытом доступе. Весь код доступен на github в пакете VKReader.

Я решил, что было бы интересно, если бы листер мог загружать последние посты по какому-нибудь ключевому слову, ну, и каким-нибудь еще параметрам. Оказалось, что этого совсем не сложно добиться. Глава документации, посвященная кастомизации, говорит, что для создания своего листера нужно пронаследоваться от системного класса и переопределить несколько методов.

Итак, все в том же пакете я создал класс VKReader.Lister, наследующийся от класса %iKnow.Source.Lister. Если вы пишете свой листер, он тоже должен наследоваться от этого класса.

Каждому листеру должно быть присвоено уникальное короткое имя (alias), по которому к нему будут обращаться системные методы iKnow. Если это имя не будет указано, вместо него будет использоваться полное имя класса этого листера.
Чтобы указать alias, просто переопределите в вашем классе класс-метод GetAlias. Для нашего листера Вконтакте я сделал это так:

ClassMethod GetAlias() As %String

{

Quit «VKAPI»

}

 

Все источники данных, представленные для загрузки, имеют external id, который должен содержать короткое имя листера и full reference, который, в свою очередь, состоит из имени группы источников и local reference.
Для работы листера нужно переопределить класс-методы BuildFullRef и SplitFullRef, соответственно, собирающий full reference из groupname и local reference и разбивающий его на эти две части.
Extrenal id в нашем случае получился такой:

VKAPI:searchQuery:::vkPostId

Здесь VKAPI — короткое имя нашего листера, поисковой запрос играет роль имени группы источников, а id записи Вконтакте — local reference.
Код методов BuildFullRef и SplitFullRef:

ClassMethod SplitFullRef(domainId As %IntegerfullRef As %StringOutput groupName As %String
Output localRef As %StringAs %Status Private ]

{

set delim «:::»

set localRef $piece(fullRefdelim$l(fullRefdelim))

set groupName $e(fullRef, 1, *-$l(localRef)-$l(delim))

Quit $$$OK

}

 

ClassMethod BuildFullRef(domainId As %IntegergroupName As %StringlocalRef As %StringAs %String Private ]

{

quit groupName_«:::»_localRef

}

 

Также нужно указать, какой Processor будет стандартным для этого листера. В iKnow Processor — это объект, который занимается непосредственной обработкой загружаемых данных. Есть несколько типов различных обработчиков (Processor-ов), но, поскольку в нашем случае данные будут храниться только непосредственно в памяти, я решил использовать обработчик для временного хранилища. Обработчик также указывается через переопределение.


ClassMethod 
DefaultProcessor() As %String

{

Quit «%iKnow.Source.Temp.Processor»

}

 

Вся основная загрузочная деятельность происходит в еще одном переопределяемом методе с красноречивым названием ExpandList. Этот метод расширяет список для загрузки в домен. Аргументы методов ProcessList и AddListToBatch будут такими же, какими вы определите их в ExpandList.
Приведем сначала весь код метода для нашего случая.
Аргументы у нас будут следующие (по порядку): слово-запрос, по которому хотим искать записи; число записей; булевское значение, соответствующее тому, хотим ли мы проверять список для загрузки на существование источника с такими же local reference; ограничения на время публикации записи.


Method 
ExpandList(listparams As %ListAs %Status

{

set query $li(listparams, 1)

set count $li(listparams, 2)

set checkExists = +$lg(listparams, 3, 1)

set startDate $lg(listparams, 4)

set startTime $lg(listparams, 5)

set endDate $lg(listparams, 6)

set endTime $lg(listparams, 7)

#dim response As %ListOfObjects

set tSC ##class(VKReader.Requests.APIPublicMethodsCaller).NewsfeedSearch(.responsequery,
 count,,,startDatestartTimeendDateendTime)

quit:$$$ISERR(tSCtSC

do ..RegisterMetadataKeys($lb(«PostDate»«PostTime»«AuthorID»«AuthorCity»«AuthorCountry»,
 «AuthorDOB»«AuthorSex»))

set userIds «1»

set groupIds «1»

for = 1: 1: response.Count() {

if (response.GetAt(i).FromID < 0) {

set groupIds groupIds «,» _ (-(response.GetAt(i).FromID))

else {

set userIds userIds «,» response.GetAt(i).FromID

}

}

set tSC ##class(VKReader.Requests.APIPublicMethodsCaller).UsersGet(.responseUsersuserIds,
 «sex,city,bdate,country»)

quit:$$$ISERR(tSCtSC

set tSC ##class(VKReader.Requests.APIPublicMethodsCaller).GroupsGetById(.responseGroupsgroupIds,
 «city,country»)

quit:$$$ISERR(tSCtSC

for = 1: 1: response.Count() {

set tPostDate response.GetAt(i).Date

set tPostTime response.GetAt(i).Time

set tOwnerID response.GetAt(i).OwnerID

set tFromID response.GetAt(i).FromID

set tID response.GetAt(i).ID

#dim tTextStream as %GlobalCharacterStream

set tTextStream response.GetAt(i).Text

if (tFromID < 0) {

set tAuthorCity responseGroups.GetAt(-tFromID).City

set tAuthorCountry responseGroups.GetAt(-tFromID).Country

set tAuthorDOB «»

set tAuthorSex «»

else {

set tAuthorCity responseUsers.GetAt(tFromID).City

set tAuthorCountry responseUsers.GetAt(tFromID).Country

set tAuthorDOB responseUsers.GetAt(tFromID).DOB

set tAuthorSex responseUsers.GetAt(tFromID).Sex

}

set tLocalRef tOwnerID «#» tFromID «#» tID

if (checkExists{

continue:..RefExists(querytLocalRefcheckExists — 1)

}

set tRef $lb(i%ListerClassId, ..AddGroup(query), tLocalRef)

do tTextStream.Rewind()

if (tTextStream.Size = 0) {

continue

}

set len = 32000

while (len = 32000) {

do ..StoreTemp(tReftTextStream.Read(.len))

}

do ..SetMetadataValues(tRef$lb(tPostDatetPostTimetFromIDtAuthorCitytAuthorCountry,
 tAuthorDOBtAuthorSex))

}
}

Пройдемся по коду более подробно.
Сначала выделим аргументы.

set query $li(listparams, 1)

set count $li(listparams, 2)

set checkExists = +$lg(listparams, 3, 1)

set startDate $lg(listparams, 4)

set startTime $lg(listparams, 5)

set endDate $lg(listparams, 6)

set endTime $lg(listparams, 7)

Сделаем запрос к API Вконтакте через наш метод-обертку. Результатом работы этого метода является список объектов класса VKReader.Data.Post, который содержит некоторые характерные для записи Вконтакте поля.

#dim response As %ListOfObjects

set tSC ##class(VKReader.Requests.APIPublicMethodsCaller).NewsfeedSearch(.responsequery,
 count,,,startDatestartTimeendDateendTime)

quit:$$$ISERR(tSCtSC

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

do ..RegisterMetadataKeys($lb(«PostDate»«PostTime»«AuthorID»«AuthorCity»«AuthorCountry»,
 «AuthorDOB»«AuthorSex»))

Сохраним comma-separated-list’ы id пользователей и групп, являющихся авторами найденных нами записей. Id групп, как и в API Вконтакте, являются отрицательными целыми числами, а id пользователей — положительными.

set userIds «1»

set groupIds «1»

for = 1: 1: response.Count() {

if (response.GetAt(i).FromID < 0) {

set groupIds groupIds «,» _ (-(response.GetAt(i).FromID))

else {

set userIds userIds «,» response.GetAt(i).FromID

}

}

Получим информацию об этих пользователях и группах при помощи методов-оберток. Они возвращают списки объектов типов VKReader.Data.User и VKReader.Data.Group, содержащих поля, характерные для пользователей и групп Вконтакте (вроде города, страны и всего прочего).

set tSC ##class(VKReader.Requests.APIPublicMethodsCaller).UsersGet(.responseUsersuserIds,
 «sex,city,bdate,country»)

quit:$$$ISERR(tSCtSC

set tSC ##class(VKReader.Requests.APIPublicMethodsCaller).GroupsGetById(.responseGroupsgroupIds,
 «city,country»)

quit:$$$ISERR(tSCtSC

В цикле обработаем все найденные посты. Сначала выделим всю полученную метаинформацию в локальные переменные.

set tPostDate response.GetAt(i).Date

set tPostTime response.GetAt(i).Time

set tOwnerID response.GetAt(i).OwnerID

set tFromID response.GetAt(i).FromID

set tID response.GetAt(i).ID

#dim tTextStream as %GlobalCharacterStream

set tTextStream response.GetAt(i).Text

if (tFromID < 0) {

set tAuthorCity responseGroups.GetAt(-tFromID).City

set tAuthorCountry responseGroups.GetAt(-tFromID).Country

set tAuthorDOB «»

set tAuthorSex «»

else {

set tAuthorCity responseUsers.GetAt(tFromID).City

set tAuthorCountry responseUsers.GetAt(tFromID).Country

set tAuthorDOB responseUsers.GetAt(tFromID).DOB

set tAuthorSex responseUsers.GetAt(tFromID).Sex

}

Local reference — id хозяина стены, id отправителя и id записи, разделенные решеткой.

set tLocalRef tOwnerID «#» tFromID «#» tID

Если необходимо, проверим, есть ли источники с таким же local reference.

if (checkExists{

continue:..RefExists(querytLocalRefcheckExists — 1)

}

Следующий код мог бы быть и другим, если бы был выбран другой обработчик источников. Я использую обработчик для временного хранилища, так что мне нужно расширять список при помощи метода StoreTemp (более подробно для каждого обработчика можно посмотреть на странице с его документацией). Также мне нужно установить полученные значения для полей метаданных.

set tRef $lb(i%ListerClassId, ..AddGroup(query), tLocalRef)

do tTextStream.Rewind()

if (tTextStream.Size = 0) {

continue

}

set len = 32000

while (len = 32000) {

do ..StoreTemp(tReftTextStream.Read(.len))

}

do ..SetMetadataValues(tRef$lb(tPostDatetPostTimetFromIDtAuthorCitytAuthorCountry,
 tAuthorDOBtAuthorSex))

Все. Листер написан!
Протестируем его работу.

 

Тестируем листер

Я написал небольшое веб-приложение, которое, используя реализованный нами листер, позволяет просматривать, искать похожие, добавлять по запросу и удалять записи из домена. Вот несколько скриншотов:

Изначально пустой домен.

image
Нажимаем на плюсик, чтобы добавить новые посты.
У появившейся формы заполняем поля и жмем на кнопку, чтоб добавить записи.

image
Ждем некоторое время и записи добавляются.

image
Для тех пользователей или групп, которые предоставили данные о себе в открытый доступ, наш листер сохраняет их в поля метаинформации, а это небольшое демо отображает их в виде не слишком элегантной таблицы.

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

image

Резюме

По ходу статьи мы разобрались в том, как работает загрузка данных в домен, подробно обсудили, как работает среднестатистический листер и как написать свой листер, который тоже будет работать. Написали свой листер для работы с данными Вконтакте, а также убедились в том, что он действительно работает по модулю того, что домен и конфигурация были созданы где-то за кулисами.
В случае, если имеется желание заглянуть за эти кулисы, весь код, который был изложен, использовался или упоминался в статье, может быть найден на страничке проекта на github.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *