brief: | i18n.site теперь поддерживает бессерверный полнотекстовый поиск.

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

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


Чистый Интерфейс, Инвертированный Полнотекстовый Поиск

Последовательность

После нескольких недель разработки i18n.site (чисто статический инструмент markdown multilingualtranslation & для создания веб-сайтов) теперь поддерживает полнотекстовый поиск в чистом интерфейсе.

В этой статье будет представлена техническая реализация полнотекстового поиска в интерфейсе i18n.site Посетите i18n.site чтобы ощутить эффект поиска.

Код с открытым исходным кодом : Поиск ядра / интерактивный интерфейс!

Обзор Решений Для Бессерверного Полнотекстового Поиска

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

Решения для бессерверного полнотекстового поиска делятся на две большие категории:

Во-первых, аналогичные algolia.com сторонние поставщики услуг поиска предоставляют интерфейсные компоненты для полнотекстового поиска.

Такие услуги требуют оплаты в зависимости от объема поиска и часто недоступны для пользователей в материковом Китае из-за таких проблем, как соответствие веб-сайта требованиям.

Его нельзя использовать в автономном режиме, нельзя использовать в интрасети и он имеет большие ограничения. В этой статье многого не обсуждается.

Второй — чистый полнотекстовый поиск во внешнем интерфейсе.

В настоящее время обычный полнотекстовый поиск во внешнем интерфейсе включает lunrjs и ElasticLunr.js (на основе lunrjs вторичной разработки).

lunrjs Есть два способа построения индексов, и у обоих есть свои проблемы.

  1. Готовые индексные файлы

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

  2. Загружайте документы и создавайте индексы на лету

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


Помимо lunrjs , существуют и другие решения для полнотекстового поиска, например :

fusejs , вычислите сходство между строками для поиска.

Производительность этого решения крайне низкая и его нельзя использовать для полнотекстового поиска (см Fuse.js длинный запрос занимает более 10 секунд, как его оптимизировать? ).

TinySearch , используйте фильтр Блума для поиска, не может использоваться для поиска по префиксу (например, введите goo , поиск good , google ) и не может обеспечить аналогичный эффект автоматического завершения.

Из-за недостатков существующих решений i18n.site разработала новое чисто интерфейсное решение для полнотекстового поиска, которое имеет следующие характеристики :

  1. Поддерживает многоязычный поиск и имеет небольшой размер. Размер поискового ядра после упаковки gzip равен 6.9KB (для сравнения, размер lunrjs равен 25KB ).
  2. Создайте инвертированный индекс на основе indexedb , который занимает меньше памяти и работает быстрее.
  3. При добавлении/изменении документов переиндексируются только добавленные или измененные документы, что сокращает объем вычислений.
  4. Поддерживает префиксный поиск, который может отображать результаты поиска в режиме реального времени, пока пользователь печатает.
  5. Доступно Офлайн

Ниже будут подробно описаны i18n.site деталей технической реализации.

Многоязычная Сегментация Слов

Сегментация слов использует собственную сегментацию слов браузера Intl.Segmenter , и все основные браузеры поддерживают этот интерфейс.

Код сегментации слова coffeescript выглядит следующим образом:

SEG = new Intl.Segmenter 0, granularity: "word"

seg = (txt) =>
  r = []
  for {segment} from SEG.segment(txt)
    for i from segment.split('.')
      i = i.trim()
      if i and !'|`'.includes(i) and !/\p{P}/u.test(i)
        r.push i
  r

export default seg

export segqy = (q) =>
  seg q.toLocaleLowerCase()

в:

Построение Индекса

5 таблиц хранения объектов были созданы за IndexedDB :

Передайте массив документа url и номера версии ver и найдите, существует ли документ в таблице doc Если он не существует, создайте инвертированный индекс. При этом удалите инвертированный индекс для тех документов, которые не были переданы.

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

При внешнем взаимодействии может отображаться индикатор выполнения индекса, чтобы избежать задержки при первой загрузке. См. «Индикатор выполнения с анимацией на основе одной progress + Pure css реализации» English / Chinese .

IndexedDB С Высоким Уровнем Одновременной Записи

Проект idb на основе асинхронной инкапсуляции IndexedDB

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

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

pusher = =>
  ing = new Map()
  (table, id, val)=>
    id_set = ing.get(id)
    if id_set
      id_set.add val
      return

    id_set = new Set([val])
    ing.set id, id_set
    pre = await table.get(id)
    li = pre?.li or []

    loop
      to_add = [...id_set]
      li.push(...to_add)
      await table.put({id,li})
      for i from to_add
        id_set.delete i
      if not id_set.size
        ing.delete id
        break
    return

rindexPush = pusher()
prefixPush = pusher()

Точность И Отзыв

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

Предположим, что после сегментации слов N слов. При возврате результатов сначала будут возвращены результаты, содержащие все ключевые слова, а затем результаты, содержащие N-1 , N-2 ,..., 1 ключевых слова.

Результаты поиска, отображаемые первыми, обеспечивают точность запроса, а результаты, загружаемые впоследствии (нажмите кнопку «Загрузить еще»), обеспечивают скорость отзыва.

Загрузка По Требованию

Чтобы повысить скорость ответа, поиск использует генератор yield для реализации загрузки по требованию и возвращает результат limit раз, когда запрашивается результат.

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

Префиксный Поиск В Реальном Времени

Чтобы отобразить результаты поиска, пока пользователь печатает, например, когда вводится wor , отображаются слова с префиксом wor такие как words и work .

Ядро поиска будет использовать таблицу prefix для последнего слова после сегментации слова, чтобы найти все слова с его префиксом и выполнить поиск последовательно.

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

export default (wait, func) => {
  var timeout;
  return function(...args) {
    clearTimeout(timeout);
    timeout = setTimeout(func.bind(this, ...args), wait);
  };
}

Доступно Офлайн

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

Для выделения результатов поиска требуется перезагрузка исходного текста, а соответствие service worker позволяет избежать повторных сетевых запросов.

В то же время, поскольку service worker кэширует все статьи, как только пользователь выполняет поиск, весь веб-сайт, включая поиск, становится доступен в автономном режиме.

Оптимизация Отображения Документов MarkDown

i18n.site чисто интерфейсное решение для поиска оптимизировано для MarkDown документов.

При отображении результатов поиска будет отображаться название главы, и при нажатии на нее будет осуществляться навигация.

Подвести Итог

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

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