java / open source
44,6
рейтинг
26 ноября 2014 в 12:01

Разработка → Как найти любовь или приключения с помощью crate.io и kibana

Про результативность, качество и КПД сайтов знакомств можно спорить, можно искать 101 повод чем лучше в клубе/баре/_дополнить_варианты_/парке искать знакомства. То что еще лет десять-пятнадцать назад вызывало смех — теперь мейнстрим. Так не проще ли попытаться использовать еще одну возможность для поиска и общения в интернет с переходом к знакомству в жизни…



Гиковский вариант технологии поиска, скринкаст приложения под катом. В конце статьи ссылка на архив с работающим приложением под Apache License v2.0 и небольшим набором данных для примера.


Звучит приободряюще, не правда ли!? Реальность несколько сложнее: армии ботов и фейк аккаунтов, работниц древнейшей профессии, попытки сервисами знакомств выжать максимум денег с минимумом результата и даже воры в поисках добычи. Еще интереснее? Не все так грустно и при правильном подходе игра стоит свеч!

Обещаный скринкаст приложения:


Рассмотрим програмную часть для поиска. Делим задачу на две части, как с рисованием совы:

  • Первая часть — рисуем овал. Для нас это найти, собрать и структурировать данные для дальнейшего поиска. Любой язык программирования с библиотекой html клиента, с регулярными выражениями или работой с DOM/xPath. Для меня эта часть не была проблемой, как разработчика с солидным опытом в интеграции ИТ систем и разработчика распределенного поискового робота для поискового стартапа Visuvi. Если вы считаете, что эта тема интересна, выскажитесь в голосовании за новую тему статьи.
  • Вторая часть — дорисовываем оставшуюся часть совы. Это как сохранить данные в хранилищее информации, проиндексировать их и написать фронтэнд для поиска и просмотра данных.


На помощь нам спешит crate.io — это набор плагинов для хранения двоичных данных в файловой системе и выполнения распределенных SQL запросов с помощью возможностей, которые уже есть в поисковом сервере elasticsearch. В двух словах это NoSQL shared nothing база в основе и facebook Presto SQL парсер и планировщик надстройкой над ней. Распределенное решение из мира big data, которое мы будем использовать пока в виде одного процесса на одном компьютере.

Почему crate.io? Нам нужно где-то хранить фото и при этом нужен Elasticsearch, да и SQL может пригодиться для статистики и отчетов в будущем. Успокою вас и в этот раз обойдемся без энтерпрайза, hibernate и JPA). Как увидете, работать crate не сложнее, чем с реляционной базой.

Kibana — HTML5 приложение, позволяющее визуализировать данные из elasticsearch, работать с временными рядами, фильтровать данные, сохранять параметры поиска в виде дашбордов.

Как это может помочь в поисках!? Минимум программирования и максимум результата.
Работать с crate.io можно из Python, Ruby, PHP, Java — jdbc type 4 драйвера. Но мне удобнее было включить REST API elasticsearch, который зачем-то скрывают в crate и буду работать через него.

В файле config/crate.yml добавляем параметры
es.api.enabled: true
udc.enabled: false

Второй параметр отключает отчеты об использовании crate.io, отправляемые по UDP на сервер проекта и я сразу же удалил двоичные файлы из библиотеки мониторинга sigar, чтобы не смущать ваш антивирус.

В таком виде «ящик» становится дружелюбным для работы через elasticsearch REST и с помощью spring data elasticsearch.

Для запуска сервера обязательно нужна java jre версии 7 или старше.
Запускаю проект bin/crate ( в случае с windows нужен файл bin\crate.bat)

С помощью утилиты коммандной строки crash или веб консоли
http://localhost:4200/_plugin/crate-admin/#/console

создаю хранилище для фотографий с названием images.

bin/crash -c "create blob table images clustered into 7 shards 
with (number_of_replicas=0)"
+-----------------------+-----------+---------+-----------+---------+
| server_url            | node_name | version | connected | message |
+-----------------------+-----------+---------+-----------+---------+
| http://127.0.0.1:4200 | Brigade   | 0.45.3  | TRUE      | OK      |
+-----------------------+-----------+---------+-----------+---------+
CONNECT OK
CREATE OK (1.104 sec)


Elasticsearch не требует чтобы мы определяли формат данных. В таком решении дьявол кроется в деталях, это скорее тема для обсуждения в комментариях к статье. Я все же укажу типы данных явно с помощью Mapping API, чтобы не было проблем с поиском и отображением в kibana.

Типы данных
{
  "info": {
    "mappings": {
      "default": {
        "properties": {
          "accommodation": {
            "type": "string",
            "index": "not_analyzed"
          },
          "age": {
            "type": "long"
          },
          "build": {
            "type": "string",
            "index": "not_analyzed"
          },
          "drinkingHabits": {
            "type": "string",
            "index": "not_analyzed"
          },
          "education": {
            "type": "string",
            "index": "not_analyzed"
          },
          "ethnicity": {
            "type": "string",
            "index": "not_analyzed"
          },
          "first": {
            "type": "date",
            "format": "basic_date_time"
          },
          "height": {
            "type": "long"
          },
          "images": {
            "type": "string"
          },
          "info": {
            "properties": {
              "": {
                "type": "string"
              },
              "Вес": {
                "type": "string"
              },
              "Внешность": {
                "type": "string"
              },
              "Дети": {
                "type": "string"
              },
              "Знание языков": {
                "type": "string"
              },
              "Кого я хочу найти": {
                "type": "string"
              },
              "Материальное положение": {
                "type": "string"
              },
              "Образование": {
                "type": "string"
              },
              "Ориентация": {
                "type": "string"
              },
              "Отношение к алкоголю": {
                "type": "string"
              },
              "Отношение к курению": {
                "type": "string"
              },
              "Отношения": {
                "type": "string"
              },
              "Познакомлюсь": {
                "type": "string"
              },
              "Проживание": {
                "type": "string"
              },
              "Рост": {
                "type": "string"
              },
              "Телосложение": {
                "type": "string"
              }
            }
          },
          "kids": {
            "type": "string",
            "index": "not_analyzed"
          },
          "last": {
            "type": "date",
            "format": "basic_date_time"
          },
          "login": {
            "type": "string"
          },
          "mainImage": {
            "type": "string",
            "index": "not_analyzed"
          },
          "message": {
            "type": "string"
          },
          "readableLogin": {
            "type": "boolean"
          },
          "realName": {
            "type": "string"
          },
          "relationship": {
            "type": "string",
            "index": "not_analyzed"
          },
          "replyRate": {
            "type": "long"
          },
          "searchingFor": {
            "type": "string"
          },
          "self": {
            "properties": {
              "В друзьях я больше всего ценю": {
                "type": "string"
              },
              "В женщинах я особенно ценю": {
                "type": "string"
              },
              "В жизни я ставлю перед собой цель": {
                "type": "string"
              },
              "В мужчинах я особенно ценю": {
                "type": "string"
              },
              "Есть ли у меня домашние животные": {
                "type": "string"
              },
              "Из всех известных людей я хотела бы быть": {
                "type": "string"
              },
              "Как долго я смогу прожить без общения": {
                "type": "string"
              },
              "Место, где я бы хотела жить": {
                "type": "string"
              },
              "Мое любимое блюдо": {
                "type": "string"
              },
              "Мое образование": {
                "type": "string"
              },
              "Мое свободное время я хотела бы провести так": {
                "type": "string"
              },
              "Мои любимые литературные герои": {
                "type": "string"
              },
              "Мои любимые музыкальные исполнители": {
                "type": "string"
              },
              "Мои любимые писатели": {
                "type": "string"
              },
              "Мои любимые фильмы": {
                "type": "string"
              },
              "Мои любимые художники": {
                "type": "string"
              },
              "Мой девиз": {
                "type": "string"
              },
              "Мой любимый город": {
                "type": "string"
              },
              "Наивысшее счастье для меня": {
                "type": "string"
              },
              "Самое поразительное открытие для меня": {
                "type": "string"
              },
              "Самой привлекательной чертой своего характера я считаю": {
                "type": "string"
              },
              "Самый ценный совет, который я получила в жизни": {
                "type": "string"
              },
              "Хотела бы я иметь детей": {
                "type": "string"
              },
              "Я больше всего горжусь этим достижением": {
                "type": "string"
              },
              "Я мечтаю о работе": {
                "type": "string"
              }
            }
          },
          "smoker": {
            "type": "string",
            "index": "not_analyzed"
          },
          "updated": {
            "type": "date",
            "format": "basic_date_time"
          },
          "viewed": {
            "type": "long"
          },
          "weight": {
            "type": "long"
          }
        }
      }
    }
  }
}



Запускаем скрипт, который выкачивает html страницы с сайтов, парсит html и извлекает нужные нам данные и сохраняет с помощью REST API/ elasticsearch java client.
Обязательно загружаю json с index type = «default», чтобы можно было выполнять SQL запросы.



Пример одного из json документов.



cr> select count(*) from info;
+----------+
| count(*) |
+----------+
|      291 |
+----------+
SELECT 1 row in set (0.030 sec)


Какой средний возраст в данных из примера?

cr> select avg(age) from info;
+---------------+
|      avg(age) |
+---------------+
| 24.7275862069 |
+---------------+
SELECT 1 row in set (0.038 sec)


Этот же скрипт скачивает изображения, считает sha1 дайджест и делает http PUT для каждой фотографии в crate.io:
"http://127.0.0.1:4200/_blobs/images/"+fileDigest


Можем проверить, что появились записи в blob.images:

cr> select count(*) from blob.images;
+----------+
| count(*) |
+----------+
|     2813 |
+----------+
SELECT 1 row in set (0.029 sec)


Отлично, данные в базе!

Скачиваю архив с kibana и распаковываю в директорию plugins/kibana/_site. При перезапуске сервер найдет фронтэнд как плагин site.

В plugins/kibana/_site/config.js указываем адрес к REST API Elasticserch

<b>elasticsearch: "http://"+window.location.host,</b>


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

Этот фрагмент angularJS шаблона выводит селектор оценки для поля _id в основоной таблице и фотографию, при видимом поле mainImage.

plugins/kibana/_site/app/panels/table/module.html

Код отображение фото в таблице, голосование за оценку
                    <tr ng-click="toggle_details(event)" class="pointer">
                        <td ng-if="panel.fields.length<1"
                            bo-text="event._source|stringify|tableTruncate:panel.trimFactor:1"></td>
                        <td ng-show="panel.fields.length>0" ng-repeat="field in panel.fields"><span
                                ng-if="(!panel.localTime || panel.timeField != field) && field!='mainImage' && field!='_id'"
                                bo-html="(event.kibana.highlight[field]||event.kibana._source[field]) |tableHighlight | tableTruncate:panel.trimFactor:panel.fields.length"
                                class="table-field-value"></span>
                        <span ng-if="field=='_id' ">
                            <span ng-repeat="t in [0,2,3,4,5]">
                                <input type="radio" name="item_{{event.kibana._source[field]}}" value="{{t}}" onclick="postESUpdate('{{event.kibana._source["_index"]}}','{{event.kibana._source["_type"]}}','{{event.kibana._source[field]}}',{{t}})" ng-if="event.kibana._source["rate"]!=t">
                                <input type="radio" name="item_{{event.kibana._source[field]}}" value="{{t}}" onclick="postESUpdate('{{event.kibana._source["_index"]}}','{{event.kibana._source["_type"]}}','{{event.kibana._source[field]}}',{{t}})" ng-if="event.kibana._source["rate"]==t" checked>{{t}}
                            </span>
                        </span>
                        <span ng-if="field=='mainImage' "><img src="/_blobs/images/{{event.kibana._source[field]}}"/></span>


                            <span
                                ng-if="panel.localTime && panel.timeField == field && field!='mainImage'"
                                bo-html="event.sort[1]|tableLocalTime:event" class="table-field-value"></span>

                        </td>
                    </tr>



Чтобы отобразить несколько изображений для одной записи при просмотре записи:

Код отображения всех фотографий
                                <tr ng-repeat="(key,value) in event.kibana._source track by $index"
                                    ng-class-odd="'odd'">
                                    <td style="word-wrap:break-word" bo-text="key"></td>
                                    <td style="white-space:nowrap"><i class="icon-search pointer"
                                                                      ng-click="build_search(key,value)"
                                                                      bs-tooltip="'Add filter to match this value'"></i>
                                        <i class="icon-ban-circle pointer" ng-click="build_search(key,value,true)"
                                           bs-tooltip="'Add filter to NOT match this value'"></i> <i
                                                class="pointer icon-th" ng-click="toggle_field(key)"
                                                bs-tooltip="'Toggle table column'"></i></td>
                                    <td style="white-space:pre-wrap;word-wrap:break-word">
                                        <span ng-if=" key != 'images' " bo-html="value|noXml|urlLink|stringify"></span>
                                    <span ng-if=" key == 'images' "><div ng-repeat="img in value"><img src="/_blobs/images/{{img}}"/></div></span></td>
                                </tr>



Для скрипта голосования, воспользуемся jquery, который уже есть в kibana

plugins/kibana/_site/index.html

Обновление оценки в json документе, запрос на сервер
        function postESUpdate(index, type, id, rate){
            $.ajax({
                type: "POST",
                url: "http://"+window.location.host+"/"+index+"/"+type+"/"+id+"/_update",
                data: '{"doc":{"rate":'+rate+'}}'
            }).done(function(){//alert("success"
            }).fail(function(){alert("error")});
        }


Это вызов elasticsearch Update API для обновления поля документа rate.

На этом программирование заканчивается. Дальше только веб интерфейс!



Кратко про создание фильтров вы уже посмотрели в скринкасте в начале статьи.
Там же показано как выбрать поддиапазон времени на гистограмме или с помощью timepicker. Все ваши фильтры и настройки можно сохранить в виде дашборда в kibana и загрузить когда нужно по имени.

За рамками этой статьи остались поиск по регулярным выражениям, безопасность сервиса, мониторинг и администрирования crate.io, SQL запросы через jdbc или клиентов для вашего языка программирования.

Повторюсь, что для запуска проекта необходима jvm 7 или старше.

Приложение, с данными для примера, вы можете скачать c дропбокса (234MB tar.gz), распаковать и запустить в *nix командой:
bin/crate
или windows:
bin\crate.bat

Откройте готовый дашборд в браузере:
http://localhost:4200
/_plugin/kibana/#/dashboard/elasticsearch/When%20first%20photo%20was%20uploaded


Желаю удачи с crate.io/kibana и в реальных знакомствах!!!

P.S. Dropboxs решил не выдавать сегодня(27.11.2014) архив. Подскажите пожалуйста в комментариях какой общедоступный хостинг файлов позволит выложить 234Мб файл без ограничений на количество скачиваний.


По результатам вашего голосования написал статью «Что нам стоит сайт распарсить. Основы webdriver API»
На какую тему мне написать следующую статью?
59%
(144)
Написание простейшего поискового робота, извлечение информации из веб страниц
37%
(91)
Распределенный поисковый робот и управления его заданиями в кластере
32%
(77)
Более подробный рассказ про elasticsearch/crate.io, распределенная система, разработка плагинов для elasticsearch
27%
(65)
В топку сайты знакомств, даешь статьи про энтерпрайз и java!!!

Проголосовало 243 человека. Воздержалось 88 человек.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Игорь @igor_suhorukov
карма
67,7
рейтинг 44,6
java / open source
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (25)

  • +2
    Очень показательное использование верных технологий!

    crate.io — шикарная штука которая совсем не упоминается на Хабре, но верю что выстрелит во всей красе. Ребята очень хорошую базу закрутили, жаль до релиза еще далековато.
    • +2
      crate.io еще только в начале своего пути, пока сыроват. Зря они прячут Elasticsearch только)
  • 0
    Расскажите лучше как справляетесь с ботами, трапами и девушками древнейшей профессии.)
    • +1
      Как справлялся!?))) С ботами — своя эвристика(на лурке неплохо описали похожую), остальное по общению. Не стоит давать сразу свой телефон, если просят. Немного пообщаться в чате и все становится ясно, кого избегать
  • 0
    может все таки просто начать знакомится с девушками, организовывать личные встречи, если вообще есть такая цель — ?

    на личную встречу можно потратить час-два и все будет ясно. у меня есть непосредственный опыт :-).
    • +2
      На личные встречи уходит много времени. И проводит встречи со всеми подряд вовсе не так круто, как проведя предварительный отсев.
    • 0
      Кто-то говорил что вместо личных встреч!? Со всеми в подряд было бессмысленно встречаться.
      Мне было жаль пару своего времени на встречу и дорогу. К тому же мы не во Франции и оплачивает счет мужчина)
      • 0
        Договоритесь на этапе знакомства что счет оплачиваете каждый сам за себя.
  • +1
    Со всеми подряд я не предлагаю, интересные люди могут пройти мимо отсева, вообще написал к тому. Чтобы предварительный отсев не заменил встречи вообще. Что то вроде ну ничего не отсеялось вот и не буду встречаться.
    • 0
      Ага, так можно через чур увлечься. В жизни смотришь: красивая девушка, подошел знакомиться, она еще телефон спросила — и ты такой по давно отложенным алгоритмам своей программы «хм, наверное тут что-то не то, в отсев её» :)
  • +1
    Ага, значит вот оно прикладное примение связки elastiksearch+logstash+kibana про которую вчера рассказывал))))
  • 0
    вот вспомнил к примеру некоторые девушки пишут возраст 60, чтобы ограничить количество сообщений
    • 0
      Бывает, что некоторые пишут 20, а на вид все 40! Фотошоп никто не отменял)
      • 0
        Вранье и провокация — основная проблема Интеренета. Площадка для кидал и врунов. Надо бы как-то думать как с этим бороться. Поможет ли мне в этом ваш creat.io? Тут даже капчи наверное не спасут.
        • 0
          Гарантированно спасет от 99.99% обитательниц сайта надпись в анкете «счет оплачиваете каждый сам за себя» ;-)
          • 0
            А в чем с такими проблема, просто интересно? Я конечно не спец по знакомствам в инете, но по моему как раз эта фраза — демонстрация современных взглядов девушки и не желания ее «вешаться на шею» (понятно что мне то не удобно будет и я все равно постараюсь настоять на том, что раз уж пригласил девушку в ресторан, то и кушать за мой счет).
            • 0
              Никаких проблем. Это был шуточный совет vit1251 на основе его же высказываний.

              И только boston заметил новую технологию!
              Реально пошел оффтоп. Статья техническая, между прочим!!!)
  • 0
    Спасибо за статью, автор! Хотелось бы прочитать статьи похожей тематики (как автоматизировать знакомства в интернете)!
    Давно руки чешутся написать что-то типо бота. Всё равно свои приветственные фразы копипащу так как незнакомой девчонке особо ничего проницательного не скажешь. Знакомый писал простенький бот который только приветы умел раздавать, дык и то с этого бота много пользы было. А если его по-умнее сделать, то можно даже как продукт продавать, хаха. Кстати, нужно погуглить, наверно кто-то уже догадался и написал.
    ЗЫ Какое-то время вишу на мамбе, дык там боты попадались крайне редко, наверно один раз в жизни. Вообще не понимаю какой смысл в девчонках-ботшах. А проститутки обходят стороной и не палятся если ты молодой нормальный пацан.
  • 0
    Тут самая большая беда в том, что для сколь угодно продвинутой обработки данных нужны сами эти данные. :) А большинство абонентов сайтов знакомств хорошо, если добавит одно-два фото, да заполнит минимальный профиль (возраст, рост, вес, семейное положение и т.п.). Если пишут что-то от себя, то в основном всякую ерунду («люблю музыку, активный отдых, смотреть на звезды...»). Плюс на большинстве сайтов откровенно убогие анкеты-опросники, в которых даже понимающему человеку трудно описать себя и партнера адекватно.

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

    Соответственно, сколь угодно навороченная система поиска и обработки, получающая на входе неполные или противоречивые данные, не сможет сделать заметно более эффективной выборки, чем тупой ручной перебор. А дальше в полный рост встанет та же самая проблема — которым из полусотни симпатичных девушек с уныло-стандартными анкетами написать, чтобы потом не жалеть о времени, потраченном на переписку. :)

    Если ставить целью действительно эффективный поиск партнера — нужно начинать с создания адекватного сайта, а уже на него ставить систему умного поиска, статистической обработки и прочего.
  • 0
    Очень сумбурно написано.
    Во-первых, непонятно что дает апп?
  • 0
    А как вы создали таблицу в Crate? Не могли бы вы кинуть SQL с create table
    В CRATE его можно посмотреть командой SHOW CREATE TABLE имя таблицы
    • 0
      Сохранял данные не через JDBC, а через elasticsearch REST API
      Но мне удобнее было включить REST API elasticsearch, который зачем-то скрывают в crate и буду работать через него.

      Базу сейчас найти почти нереально на старом HDD. Но уверен, у вас получится сохранить данные!
    • 0
      Запускаем скрипт, который выкачивает html страницы с сайтов, парсит html и извлекает нужные нам данные и сохраняет с помощью REST API/ elasticsearch java client.
      Обязательно загружаю json с index type = «default», чтобы можно было выполнять SQL запросы.


      И не факт, что это будет работать с новыми версиями crate.io
      Расскажете потом, спрятали ли они elasticsearch API
  • 0
    Да этот крате мне давно приглянулся… Но всеже джоины как-то пугают ;)
    Мне все трудно понять где его ниша…
    НедоCassndra
    НедоSQL
    Проще варится чем Mongo, и вроде на чтении скалирует так-же…
    Дадите немного инфы про то в чем фишка по вашему мнению?
    • 0
      Full text search + distributed binary storage. Почти все сильные стороны elasticsearch, но при этом возможно работать через jdbc и инструментарий который его поддерживает

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.