Что такое полнотекстовый поиск

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

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

Форма поиска по сайту от Яндекса и Гугла

Качество поиска

Для начала нужно понять, из чего вообще складывается понятие качества поиска. Качество поиска зависит от многих факторов. О многих из них можно прочитать в книге известного поискового оптимизатора кандидата технических наук Игоря Ашманова. (Скажу по секрету, что недавно видел её на torrents.ru). Все факторы условно можно разбить на три категории: полнота, точность и ранжирование.

Полнота

Причина этого чаще всего в том, что на большинстве сайтов реализован поиск только по динамическим данным из-за того, что поисковая программа получает данные из базы данных. Более того, обычно вебмастер (или создатель CMS) решает, какие таблицы в БД самые важные, а какие недостойны внимания. В результате «за бортом» поиска остаются некоторые «малозначимые» динамические данные и все статические страницы.

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

Точность

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

Источник

Мега-Учебник Flask, Часть 10: Полнотекстовый поиск

Это десятая статья в серии, где я описываю свой опыт написания веб-приложения на Python с использованием микрофреймворка Flask.

Краткое повторение

В предыдущей статье мы улучшили наши запросы так, чтобы они возвращали посты на страницу.

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

Для многих типов веб-сайтов можно просто позволить Google, Bing, и т.п. проиндексировать все и предоставлять результаты поиска. Это хорошо работает с сайтами, которые имеют в основе статические страницы, такие как форум. В нашем маленьком приложении базовая единица контента это короткий пользовательский пост, а не целая страница. Мы хотим более динамичный результат поиска. Для примера, если мы ищем слово «dog» мы хотим видеть все сообщения пользователей, включающие это слово. Очевидно, что страница результата поиска не существует до тех пор, пока никто не проведет поиск, поэтому поисковики не смогут проиндексировать ее.

Введение в системы полнотекстового поиска

К сожалению поддержка полнотекстового поиска в реляционных базах данные не стандартизирована. Каждая база даннные реализует полнотекстовый поиск по-своему, и SQLAlchemy не имеет на этот случай подходящей абстракции.

Мы сейчас используем SQLite для нашей базы, поэтому мы просто могли создать полнотекстовый индекс, используя возможности предоставялемые SQLite, в обход SQLAlchemy. Но это плохая идея, потому что если в один прекрасный день мы решим перейти на другую базу данных, нам придется переписать наш полнотекстовый поиск для другой базы данных.

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

Есть несколько систем полнотекстового поиска с открытым исходным кодом. Только одна, насколько мне известно, имеет расширение Flask называемое Whoosh, и движок ее тоже написан на Python. Преимущество использования чистого Python это возможность установить его и запустить везде, где доступен Python. Недостатком является эффективность поиска, которая не сравнится с движками написанными на C или C++. На мой взгляд было бы идеальным решением иметь расширение для Flask которое может соединятся с разными системами и абстрагировать нас от деталей, как это делает Flask-SQLAlchemy освобождая нас от ньюансов различных баз данных, но в области полнотекстового поиска нет пока ничего подобного. Разработчики на Django имеют очень хорошее расширение, которое поддерживает различные системы полнотекстового поиска называемое django-haystack. Может быть в один прекрасный день кто-нибудь создаст аналогичное расширение для Flask.

Но сейчас, мы реализуем наш поиск с помощью Whoosh. Расширение, которое мы собираемся использовать это Flask-WhooshAlchemy, которая объединяет базу Whoosh с моделью Flask-SQLAlchemy.

Если у вас пока нет Flask-WhooshAlchemy в вашем виртуальном окружении, самое время установить его. Пользователи Windows должны сделать так:

Все другие могу сделать так:

Конфигурация

Конфигурация у Flask-WhooshAlchemy очень простая. Мы просто должны сказать расширению имя нашей базы для полнотекстового поиска (файл config.py ):

Изменения модели

Поскольку Flask-WhooshAlchemy интегрируется Flask-SQLAlchemy, нам нужно указать какие данные должны быть проиндексированы в каких моделях (файл app/models.py ):

Так как мы не меняли формат нашей базы, нам не нужно делать новую миграцию.

К сожалению все посты, которые были в базе до добавления движка полнотекстового поиска, не будут проиндексированы. Чтобы убедиться что база данных и движок поиска синхронизированы мы должны удалить все посты из базы и начать сначала. Сначала запускаем интерпретатор Python. Для пользователей Windows:

Для всех остальных:

Этим запросом мы удаляем все посты:

Поиск

Сейчас мы готовы к поиску. Давайте сначала добавим несколько постов в БД. У нас есть два способа сделать это. Мы можем запустить приложение и добавить посты через web-браузер, как обычный пользователь, или мы можем сделать это через интерпретатор.

Через интерпретатор мы можем сделать это следующим образом:

Расширение Flask-WhooshAlchemy очень крутое, потому что соединяется с Flask-SQLAlchemy автоматически. Нам не нужно поддерживать индекс полнотекстового поиска, все делается прозрачно для нас.

Сейчас мы имеем несколько постов проиндексированных для полнотекстового поиска и можем попробовать поискать:

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

Интеграция полнотекстового поиска в наше приложение

Чтобы сделать поиск доступным для пользователей нашего приложения мы должны сделать несколько маленьких изменений.

Конфигурация

В конфигурации мы должны указать сколько результатов поиска нужно вернуть (файл config.py ):

Форма поиска

Мы собираемся добавить форму поиска в строку навигации вверху страницы. Расположнение в верхней части очень удачное, так как поиск будет доступен со всех страниц.

Сначала мы должны добавить класс формы поиска (файл app/forms.py ):

Затем мы добавим форму в наш шаблон(файл app/templates/base.html ):

Обратите внимание мы отображаем форму поиска только когда пользователь вошел в систему. Точно так же, обработчик before_request создаст форму только когда пользователь вошел в систему, поскольку наше приложение не показывает никакого контента неавторизованым гостям.

View. Функция Search

Поле action для нашей формы был установлен выше, чтобы отправлять все запросы в функцию search нашего представления. Это то место где мы будем выполнять наши полнотекстовые запросы (файл app/views.py ):

Эта функция на самом деле не такая уж и большая, она просто собирает запрос из формы и перенаправляет его на другую страницу, принимающую запрос в качестве аргумента. Мы не делаем поиск напрямую в этой функции чтобы браузер пользователя не выдавал предупреждение о повторной отправке формы, если пользователь попытается обновить страницу. Этой ситуации можно избежать сделав редирект на POST-запрос, тогда при обновлении страницы браузер будет обновлять ту страницу, на которую был редирект, а не сам запрос.

Страница результатов

После того как строка запроса передана формой, обработчик POST передает ее через перенаправление в обработчик search_results (файл app/views.py ):

Функция search_result отправляет запрос в Whoosh, передавая вместе с запросом ограничение по количеству результатов, чтобы защититься от потенциально большого количества результатов поиска.

Поиск завершается в шаблоне search_result (файл app/templates/search_results.html ):

Заключительные слова

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

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

Ниже я выкладываю обновленную версию приложения microblog во всеми изменениями, сделанными в этой статье.

Как всегда, базы данных нет, вы должны создать ее самостоятельно. Если вы следите за этой серией статей, вы знаете как это делать. Если же нет, то вернитесь к статье о базе данных, чтобы узнать.

Источник

Как построить полнотекстовый поиск с помощью нейронных сетей

Почему с помощью обычного полнотекстового поиска сложно искать очень короткие документы и как быть, если хочется это сделать.

Введение

Мы все постоянно сталкиваемся с так называемым полнотекстовым поиском — нахождением документов по поисковой фразе. Самый известный пример — это поиск Google.

Существует немало инструментов для построения полнотекстового поиска документов. Достаточно популярным решением является, например, Elasticsearch. На его основе можно организовать поиск по огромным базам данных с десятками миллионов документов.

Мы в DD Planet разрабатываем сервисы поиска и аналитики по базам данных B2B-закупок и активно используем Elasticsearch. Но когда мы попытались организовать поиск и сопоставление очень коротких документов (наименований товаров), то столкнулись с рядом проблем.

Как работает полнотекстовый поиск

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

Пусть у нас есть корпус из базы из трех текстов:

тогда обратный индекс будет выглядеть следующим образом:

Поиск по одному слову очевиден — это просто список всех документов, соответствующих поисковому слову. При поиске фразы можно производить как пересечение, так объединение результатов поиска по отдельным словам с целью ранжировать результаты по релевантности. Рассмотрим, например, поиск фразы «красная ручка». Поиск по слову «красный» дает результат <2>, по слову «ручка» — <0>. Пересечение пусто, поэтому точно совпадения нет. Если считать частичное совпадение, то получится два документа <0, 2>c степенью соответствия ½. В реальных поисковых системах обычно применяются более сложные методики ранжирования, например, с учетом TF-IDF, однако в результаты выдачи не может попасть документ без текстового совпадения с поисковым запросом.

Проблемы полнотекстового поиска

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

NLP как решение проблем

Одним из технологичных способов решить вышеупомянутые задачи является применение NLP подходов. NLP (Natural Language Processing) — общее направление искусственного интеллекта и математической лингвистики, которое изучает проблемы компьютерного анализа и синтеза естественных языков.

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

Как определить степень «похожести» фраз

В NLP есть следующая задача — Paraphrase Identification — это изучение двух текстовых объектов (например, предложений) и определения, имеют ли они одинаковое значение (то есть являются парафразами). Пример парафраз: «Завтра кафе работает до 17:00» и «На следующий день кафетерий будет работать до пяти вечера». Почему это нам интересно? С помощью парафразирования мы можем анализировать текстовые описания товаров и выяснять, связаны ли они с одним товаром или нет.

Эта задача хорошо исследована. Существуют большие базы данных парафраз на русском и английском языках. В качестве инструмента для решения этой проблемы мы обратились к библиотеке DeepPavlov.ai [1], мощному фреймворку, разработанному в МФТИ. В нем собраны различные подходы и модели, связанные с обработкой русского языка.

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

Экспериментируя с разными моделями, имеющимися в DeepPavlov, мы поняли — основной вклад в результат дают не настройки модели, а качество наших данных.

Как организовать поиск, когда у нас почти ничего нет

Однако нам нужно не просто определять степени соответствия, а находить наиболее похожие товары. Как это сделать? Можно использовать полный перебор, однако он имеет сложность поиска и занимает много времени, тогда как обратный индекс Elasticsearch имеет сложность поиска . Попробуем достичь такой же скорости поиска.

Что мы имеем: множество строк и некоторую функцию, описывающую степень соответствия между этими строками. Будем далее называть эту функцию расстоянием.

Если покопаться в теории, можно наткнуться на следующую математическую структуру: метрическое пространство — это множество, на котором определена функция такая, что выполняются следующие аксиомы:

Зачем нам нужна такая сложная структура? Она нужна для эффективного решения задач поиска ближайших соседей (Nearest neighbor search) — в нашем случае поиска похожих товаров. Такая задача может быть решена в метрическом пространстве с помощью структуры vantage-point tree, поиск в которой имеет асимптотику поиска [2]. Заметим, если бы речь шла о векторах, эта задача решилась намного проще, например, с помощью Kd-деревьев. Однако так как наши объекты более абстрактны, все гораздо сложнее.

Vantage-point tree

Посмотрим, как работает vantage-point tree [3]. Оно напоминает ball-tree, используемый в векторных пространствах. Его структура представляет собой бинарное дерево. Рассмотрим, как оно строится. Мы берем в качестве вершины некоторый объект из множества (vantage-point) и рисуем вокруг него окружность (изображена на рисунке ниже).

Все объекты, находящиеся внутри окружности (то есть на расстоянии от vantage-point), идут в левую часть дерева. Остальные — в правую часть. Далее процедура повторяется для каждого поддерева. Чтобы дерево было сбалансировано, надо стараться выбирать S так, чтобы исходное множество объектов делилось на примерно равные части. Это несложно сделать, если заранее просчитать все расстояния и найти их медиану.

Предположим, мы хотим найти K ближайших соседей к точке (отмечена красным крестиком на рисунке). Мы еще не нашли ни одной точки, поэтому в качестве ближайшего кандидата берем вершину дерева (возможно, потом мы удалим эту точку). Запоминаем значение — текущее расстояние до самого дальнего кандидата. Теперь надо решить, нужно ли нам искать в обоих поддеревьях или достаточно проверить только одно и них.

Так как точка находится внутри нашей окружности, мы должны сначала проверить «внутреннее» поддерево. Находим в этом поддереве синюю точку и обновляем . Надо ли нам проверять «внешнее» поддерево? Мы рассмотрели все точки внутри окружности, но так как T$» data-tex=»inline»/> (расстояние от X до окружности), за пределами окружности могут существовать более близкие точки. Значит, нам надо проверить «внешнее» поддерево.

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

Метрическая функция

Мы решили использовать нашу функцию парафразирования в качестве расстояния для построения vantage-point tree и столкнулись с рядом проблем:

Не выполняется первая аксиома — расстояние между точками равно нулю только при условии, что они равны. Это условие не выполняется уже на обучающей выборке, так как мы полагали, что некоторые фразы означают одно и то же при их разном написании. Для решения проблемы мы добавили к основному расстоянию cosine расстояние по Doc2Vec с небольшим весом — оно будет гарантировано различно для разных объектов.

Однако вопрос подбора весового коэффициента ε остается открытым — с этим надо экспериментировать на реальных данных.

Модель несимметрична. Почему? Понять это достаточно сложно, но, скорее всего, проблема в ошибке округления float32. Из-за сложности модели ошибка накапливается в слоях нейронов. Эта проблема легко решается вычислением сначала расстояния от до , а потом наоборот, и суммированием результата.

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

Это наглядный пример, когда Из-за этого поисковая выдача иногда получается неупорядоченной. Исправить аналитически это не получается, но компенсировать это может увеличение веса расстояния Doc2Vec — для него неравенство треугольника выполняется всегда.

Выполнив формальные критерии, описанные выше, мы столкнулись на практике с другой проблемой — время поиска росло линейно, а не логарифмически, как мы ожидали. Покопавшись в литературе, мы выяснили: логарифмическая скорость поиска имеет место для примерно равномерного распределения точек в пространстве [2]. А у нас объекты распределены неравномерно — они чаще дальше, чем ближе.

Читайте также:  Что такое геральдика нумизматика

Поэтому нам пришла следующая идея. Посчитаем все возможные пары расстояний и нанесем их на график (на рисунке слева). Если мы растянем верхнюю часть графика, а нижнюю сожмем, то точки будут распределены примерно одинаково (как показано на рисунке справа). Заметим, что порядок в множестве расстояний от такого преобразования не изменится. Можно при необходимости увеличить количество интервалов и получить «идеальное» распределение расстояний.

Итоги

Пока что мы проводили тестирование на небольших базах данных (до миллиона объектов), и там наш поиск работает достаточно хорошо. Время сборки дерева растет практически линейно с небольшим логарифмическим коэффициентом.

Рост времени поиска близок к логарифмическому. Почему рост времени поиска не всегда равномерен? Дело в том, что работа поиска зависит от распределения точек в пространстве. Возникает интересный вопрос: если бы мы могли управлять распределением точек в пространстве, то какое распределение было бы оптимальным? В литературе по vantage-point tree этот вопрос обычно не обсуждается, но обсуждается смежный вопрос — как оптимально выбрать vantage-point.

Например, в метрическом пространстве нужно брать самую крайнюю точку[2], тогда на границе будет меньше всего точек и поиск станет лучше. Но эта идея реализуема только в пространстве с понятиями лево, право и край. В пространстве без порядка объектов эта идея не работает.

Наше решение не является «серебряной пулей». Мы пробовали его для поиска товаров, и получилось отлично. Если выбрать другую тематику, результаты могут быть совершенно другими.

Источник

Мега-Учебник Flask, Часть XVI: Полнотекстовый поиск

(издание 2018)

Miguel Grinberg

Туда Сюда

Это шестнадцатая часть серии Мега-учебников Flask, в которой я собираюсь добавить в микроблог возможность полнотекстового поиска.

Под спойлером приведен список всех статей этой серии 2018 года.

Примечание 1: Если вы ищете старые версии данного курса, это здесь.

Примечание 2: Если вдруг Вы захотели бы выступить в поддержку моей(Мигеля) работы, или просто не имеете терпения дожидаться статьи неделю, я (Мигель Гринберг)предлагаю полную версию данного руководства(на английском языке) в виде электронной книги или видео. Для получения более подробной информации посетите learn.miguelgrinberg.com.

Цель этой главы — реализовать функцию поиска для Microblog, чтобы пользователи могли находить интересные сообщения с использованием привычного языка. Для многих типов веб-сайтов можно просто позволить Google, Bing и т. д. Индексировать весь контент и предоставлять результаты поиска через свои API поиска. Это работает для сайтов, которые имеют в основном статические страницы, например форум. Но в моем приложении основной единицей контента является пользовательский пост, который представляет собой небольшую часть всей веб-страницы. Поиск, который мне нужен, относится к этим отдельным сообщениям в блоге, а не целым страницам. Например, если я ищу слово «собака», то хочу видеть сообщения, которые включают это слово в блогах разных пользователей. Проблема в том, что страница, которая показывает все сообщения, в которых есть слово «собака» (или любой другой возможный термин поиска), не существует как страница в блоге, которую могут найти и индексировать большие поисковые системы, поэтому у меня нет другого выбора, кроме как сотворить свою собственную функцию поиска.

Введение в полнотекстовые поисковые системы

Поддержка полнотекстового поиска не стандартизирована, как реляционные базы данных. Существует несколько полнотекстовых движков с открытым исходным кодом: Elasticsearch, Apache Solr, Whoosh, Xapian, Sphinx и т.д. Как будто этого недостаточно! Есть несколько баз данных, которые также предоставляют возможности поиска, сравнимые с выделенными поисковыми системами, такими как те, которые я перечислил выше. SQLite, MySQL и PostgreSQL предлагают некоторую поддержку для поиска текста, а также базы данных NoSQL, такие как MongoDB и CouchDB.

Если вам интересно, какие из них могут работать в приложении Flask, ответ — все они! Это одна из сильных сторон Flask, он делает свою работу и не упрямится. Так что это лучший выбор?

По моему мнению, из списка специализированных поисковых систем Elasticsearch — особенно выделяется. И как самый популярный, и значимый стоит первым в качестве символа «E» в стеке ELK для индексирования логов, совместно с Logstash и Kibana. Использование возможностей поиска в одной из реляционных баз данных может быть хорошим выбором, но с учетом того факта, что SQLAlchemy не поддерживает эту функциональность, мне пришлось бы обрабатывать поиск с помощью необработанных SQL-инструкций или найти пакет с доступом к текстовым запросам, а также возможность совместного использования с SQLAlchemy.

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

После установки Elasticsearch на компьютер можно проверить, работает ли он, введя http://localhost:9200 в адресной строке браузера, которая должна возвращать некоторую основную информацию о сервисе в формате JSON.

Поскольку управляться Elasticsearch будет из Python, я буду использовать клиентскую библиотеку Python:

Теперь не помешает обновить файл requirements.txt:

Elasticsearch Tutorial

Для начала я покажу вам основы работы с Elasticsearch из оболочки Python. Это поможет вам ознакомиться с этим сервисом и понять его реализацию, о которой я расскажу позже.

При желании index может хранить документы разных типов, и в этом случае аргумент doc_type может быть установлен в разные значения в соответствии с этими различными форматами. Я собираюсь хранить все документы в том же формате, поэтому я устанавливаю тип документа равным имени индекса.

Для каждого сохраненного документа Elasticsearch получает уникальный идентификатор и объект JSON с данными.

Давайте сохраним второй документ по этому же индексу:

И теперь, когда в этом индексе есть два документа, я могу выполнить поиск в свободной форме. В этом примере я буду искать this test :

Ответ es.search() представляет собой словарь Python с результатами поиска:

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

Теперь посмотрим результат для поиска слова second :

Полученная оценка довольно низкая, потому что мой поиск не соответствует тексту в этом документе, но поскольку только один из двух документов содержит слово «second», другой документ вообще не отображается.

Объект запроса Elasticsearch имеет много параметров и все они хорошо документированы. Среди них такие, как разбиение на страницы и сортировка, так же, как и в случае реляционных баз данных.

Не стесняйтесь добавить больше записей в этот индекс и попробовать различные варианты поиска. Когда вы закончите экспериментировать, можно удалить индекс с помощью следующей команды:

Конфигурация Elasticsearch

Интеграция Elasticsearch в приложение является отличным примером крутости Flask. Этот сервис и пакет Python, который не имеет ничего общего с Flask, но я все же постараюсь получить приличный уровень интеграции. Начну с конфигурации, которую буду писать в словаре app.config для Flask:

app/init.py: Elasticsearch instance.

Обратите внимание, как я использую условное выражение, чтобы присвоить None экземпляру Elasticsearch, если URL-адрес службы Elasticsearch не был определен в среде.

Абстракция Full-Text Search

Как я сказал во введении к главе, я хочу упростить возможный переход от Elasticsearch к другим поисковым системам. И я не хочу кодировать эту функцию специально для поиска сообщений в блоге, а предпочитаю разрабатывать решение, которое в будущем я могу легко распространить на другие модели, если мне это понадобится. По всем этим причинам, я решил создать абстракцию для функции поиска. Идея состоит в том, чтобы разработать функцию в общих терминах, поэтому я не буду предполагать, что модель Post является единственной, которая должна быть проиндексирована, и я также не буду предполагать, что Elasticsearch является предпочтительным индекс-движком. Но если я не могу делать никаких предположений ни о чем, как я могу сделать эту работу?

app/models.py: Add a __searchable__ attribute to the Post model.

Я собираюсь написать весь код, который взаимодействует с индексом Elasticsearch в модуле app/search.py. Идея состоит в том, чтобы сохранить весь код Elasticsearch в этом модуле. Остальная часть приложения будет использовать функции в этом новом модуле для доступа к индексу и не будет иметь прямого доступа к Elasticsearch. Это важно, потому что если однажды я решу, что мне больше не нравится Elasticsearch и соберусь переключиться на другой движок, все, что мне нужно сделать, это перезаписать функции в этом одном модуле, и приложение будет продолжать работать по-прежнему.

Читайте также:  Что такое обработка в музыке определение кратко

Для этого приложения я решил, что мне нужно три вспомогательные функции, связанные с индексированием текста: мне нужно:

Вот модуль app/search.py, который реализует эти три функции для Elasticsearch, используя функциональность, которую я показал вам выше с консоли Python:

Функции принимают в качестве аргумента имя index. Во всех вызовах, которые я передаю Elasticsearch, я использую это имя в качестве имени индекса, а также в качестве типа документа, как я уже делал это в примерах консоли Python.

Это слишком запутанно? Возможно, демонстрация этих функций в консоли Python может помочь вам понять их получше. В следующем сеансе я вручную добавляю все сообщения из базы данных в индекс Elasticsearch. В моей тестовой базе данных у меня было несколько сообщений, в которых были цифры «one», «two», «three», «four» и «пять», поэтому я использовал их в поисковых запросах. Возможно, вам придется адаптировать свой запрос для соответствия содержимому вашей базы данных:

Запрос, который я отправил, возвращает семь результатов. Поначалу я запросил 1 страницу со 100 пунктами на страницу и получил все семь возможных. Затем следующие три примера показывают, как я могу разбивать страницы способом очень похожим, на то, как я это сделал для Flask-SQLAlchemy, за исключением того, что результаты приходят в виде списка идентификаторов вместо объектов SQLAlchemy.

Если вы хотите сохранить все в чистоте, удалите индекс posts после того, как наэкспериментируетесь с ним:

Интеграция поиска с SQLAlchemy

Решение, которое я показал вам в предыдущем разделе, приемлемое, но у него все еще есть несколько нерешенных проблем. Первая и наиболее очевидная заключается в том, что результаты приходят в виде списка числовых идентификаторов. Это очень неудобно, поскольку мне нужны модели SQLAlchemy, чтобы я мог передавать их в шаблоны для визуализации, и мне нужен способ заменить этот список чисел соответствующими моделями из базы данных. Вторая проблема заключается в том, что это решение требует, чтобы приложение явной выдавало вызовы индексирования. Сообщения добавляются или удаляются, что не страшно, но не идеально, поскольку ошибка, которая вызывает пропущенные индексации вызова при внесении изменений на стороне SQLAlchemy не собираются быть легко обнаружены. Две базы данных будут рассинхронизироваться все больше и больше каждый раз при возникновении ошибки и вы, вероятно это не заметите какое то время. Лучшим решением было бы автоматическое включение этих вызовов при внесении изменений в базу данных SQLAlchemy.

Проблема замены идентификаторов объектами может быть решена путем создания запроса SQLAlchemy, который прочитает эти объекты из базы данных. Это звучит просто, но реализовать эффективное выполнение одного запроса на самом деле не так то просто.

Для решения второй проблемы (автоматического отслеживания изменений индексирования) я решил выполнять обновления индекса Elasticsearch из events (событий) SQLAlchemy. SQLAlchemy предоставляет большой список событий, о которых приложения могут получать уведомления. Например, для каждого commit-а (фиксации изменений) сеанса, я мог бы иметь функцию в приложении, вызываемом SQLAlchemy, в которой мог применять те же обновления, которые были сделаны в сеансе SQLAlchemy к индексу Elasticsearch.

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

Методы before_commit() и after_commit() будут реагировать на два события из SQLAlchemy, которые запускаются до и после фиксации соответственно. Обработчик before полезен, потому что сеанс еще не был зафиксирован, поэтому я могу глянув на него выяснить, какие объекты будут добавлены, изменены и удалены, доступны как session.new session.dirty и session.deleted соответственно. Эти объекты больше не будут доступны после фиксации сеанса, поэтому мне нужно сохранить их до фиксации. Я использую session._changes словарь для записи этих объектов в месте, которое переживет все фиксации сеанса, потому что, как только сеанс пофиксится я буду использовать их для обновления индекса Elasticsearch.

Метод класса reindex() — это простой вспомогательный метод, который можно использовать для обновления индекса со всеми данными из реляционной стороны. Вы видели, как я делал что-то подобное из сеанса оболочки Python выше, чтобы выполнить начальную загрузку всех сообщений в тестовый индекс. Используя этот метод, я могу опубликовать Post.reindex(), чтобы добавить все записи в базу данных в индекс поиска (search index).

Обратите внимание, что вызовы db.event.listen() не входят в класс, а следуют после него. Они устанавливают обработчики событий, которые вызывают before и after для каждой фиксации. Теперь модель Post автоматически поддерживает индекс полнотекстового поиска для сообщений.

app/models.py: Добавление SearchableMixin class в Post model.

Я могу использовать метод reindex() для инициализации индекса из всех сообщений, находящихся в настоящее время в базе данных:

Форма поиска

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

Достаточно стандартный подход для веб-поиска заключается в том, чтобы строка поиска была передана, как аргумент q в строке запроса URL-адреса. Например, если вы хотите найти Python в Google, и вы хотите сэкономить пару секунд, Вы можете просто ввести следующий URL-адрес в адресной строке браузера, чтобы перейти непосредственно к результатам:

Разрешить поиску быть полностью инкапсулированным в URL-адрес удобно, потому что результаты могут быть разделены с другими людьми, которые просто нажав на ссылку получат доступ к результатам поиска.

Вот класс формы поиска, только с текстовым полем q :

Поскольку мне нужно, чтобы эта форма была видна на всех страницах, мне нужно создать экземпляр класса SearchForm независимо от страницы, которую просматривает пользователь. Единственное требование заключается в том, что пользователь вошел в систему, потому что для анонимных пользователей я пока не показываю никакого контента. Вместо того, чтобы создавать объект формы в каждом маршруте (route), а затем передавать форму всем шаблонам (templates), я покажу вам очень полезный трюк, который устраняет дублирование кода, когда вам нужно реализовать функцию во всем приложении. Я уже использовал обработчик before_request раньше, еще в главе 6, чтобы записать время последнего посещения для каждого пользователя. То, что я собираюсь сделать, это создать свою форму поиска в той же функции, но с изюминкой:

app/main/routes.py: Создание формы поиска в before_request handler.

app/templates/base.html: Отображение формы поиска в панели навигации.

Функция поиска в режиме просмотра

Последний бит функциональности в завершение функции поиска-это функция представления, которая получает представление формы поиска. Эта view — функция будет присоединена к маршруту /search, чтобы вы могли отправить запрос на поиск с помощью http://localhost:5000/search?q=search-words, как и Google.

После того, как страница результатов поиска и ссылки на страницы будут вычислены, все, что осталось-это отрисовать шаблон со всеми этими данными. Я мог бы найти способ повторно использовать index.html шаблон для отображения результатов поиска, но учитывая, что есть несколько отличий, я решил создать специальный search.html шаблон, который предназначен для отображения результатов поиска, воспользовавшись _post.html sub-шаблон для отображения результатов поиска:

app/templates/search.html: Шаблон результатов поиска.

Логика отрисовки предыдущей и следующей ссылок выглядит слегка запутанной. Возможно помоможет разобраться документация Bootstrap для компонента разбиения на страницы.

Туда Сюда

Источник

Информационный сайт