vas3k / vas3k.club

No bullshit IT community with private membership

Home Page:https://vas3k.club

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Поиск не парсит слэши

GRbit opened this issue · comments

Чеклист

  • Я поискал поиском по трекеру похожие проблемы, в том числе в закрытых Issues
  • Баг стабильно воспроизводится и я знаю как это сделать

Описание бага

Есть пост https://vas3k.club/question/18282/
Очень хочется его найти по слову mastodon, но так как рядом с ним слэш — поиск не выдаёт ничего путного:
https://vas3k.club/search/?q=mastodon&type=post

Ожидаемый результат

Видеть пост в результатах поиска

Шаги к воспроизведению

Да вроде и так всё ясно

commented

Это особенность работы ts_vector, он не в одном из конфигов не считает / как разделитель слов, мало ли, вдруг ищете http/2.0 и хотите чтобы он находил только его, а не http и 2 и 0 :)
вроде бы . тоже является разделителем только если в конце строки, или с пробелом
и mastodon.twitter, также не будет находиться по mastodon.twitter
(точки потому что тип host, слеши потом что тип url, ну а собачка не разделитель так как email)
вообще имхо стоило бы в таком случае индексить и то и другое, но это как бы поведение, которое должно быть настраиваемым.

мне видится четыре решения:

  1. Переделывать парсер постгри, (CREATE TEXT SEARCH PARSER) , а дальше альтерить конфигурации чтобы они юзали новый парсер. - в принципе самое адекватное решение, но жесткий оверкилл
  2. Приделать другой fts - manticore/lucene и переделывать куча всего - не вариант
  3. Преобразовывать данные перед поиском, и перед индексацией. То есть в зависимости от того какое поведение желаемое, либо просто заменять / на пробел, либо добавляя в конце отдельно разделенные слова. При втором варианте не нужно будет изменять данные перед поиском, только перед индексацией. Да индекс вырастит, да и индексацию возможно придется делать в виде чего то типа to_tsvector('russian', concat(field, translate(field,'/@.',' ')), как это подружить с джанго, я без понятия. но это максимально просто решение в плане трудозатрат.
  4. Забить.

@zyuhel
Пункт 3 правда звучит неплохо. Понятия не имею правда как именно это сделать.

Могу только предложить регулярку для поиска элементов через слэши которые не похожи на веб адрес:
[^ :/\.]*(/[^ /]*)+
Вот можно ей проходится по тексту, и в в итоге то что она найдёт делить на слова и добавлять к индексу.
Чуть поясню за реглярку: ищет что-то что начинается с пробела, так чтобы первое слово не было похоже на веб адрес. Похожесть проверяется по содержанию двоеточия или точки. Т.е. то что начинается с vas3k.com или http:// не подпадает. А дальше ищутся не разделённые пробелами куски типа /чето-то/там/ещё в любом количестве.

commented

@GRbit с учетом того что код кидает в django'вский SearchVector, вот так _multi_search_vector("full_name", weight="A") , а дальше это идет в ts_vector, я опасаюсь что приделывание туда регулярок будет выглядеть максимально костыльно. я ни черта не разбираюсь в django, мне на ум приходят только костыли, в виде создания вирт колонки, с нужным преобразованием, и потом использовать ее как дополнительное поле по которому ищется. Но оно даже кажется не красивым

commented

@vas3k тонкий стеб? :)

Не так прочитал

@zyuhel Я с джанго не работал никогда. Как видишь ток в регулярки могу) Ну и когда ты говоришь про вирт колонки мне это ни о чём не говорит, к сожалению.

Почитал код немножк. Ну регулярки смотрятся костыльно, но работать же будут) Вариант который мне видится более "общим" это добавление доп поля в Post типа "search_additions" и его индексирование. Может это и есть то что ты имел ввиду с вирт колонкой.

Тут как говорится "шо то костыль, шо это костыль". Грамотно будет фиксить то как индекс строится и учить его отличать ссылки от простого перечисления через слэши, при этом давать больший вес на матч всего куска со слэшами и меньший на мэтч отдельных элементов, но тип кто за такую фундаментальную штуку возьмётся?

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

Я к сожалению на PyCharm community editions и с питоном последний раз связывался лет 7 назад, так что мне стыдно PR открывать, но по-моему что-то такое должно помочь.

Можешь дать небольшой фидбек? Если кажется норм, то я могу PR открыть

diff --git a/search/models.py b/search/models.py
index 47b5745..f1131b4 100644
--- a/search/models.py
+++ b/search/models.py
@@ -37,6 +37,10 @@ class SearchIndex(models.Model):
 
     index = SearchVectorField(null=False, editable=False)
 
+    # regular expression to find words with "/" deimeter, but not web addresses like http://soonething.com/asdas or
+    # vas3k.com/some/word
+    reSlash = re.compile(' [^ \/:\.]+\/([^ ]+)')
+
     class Meta:
         db_table = "search_index"
         ordering = ["-created_at"]
@@ -80,6 +84,8 @@ class SearchIndex(models.Model):
                  + _multi_search_vector("topic__name", weight="C")
 
         if post.is_searchable:
+            post.title = cls.improve_title_search(post.title)
+
             SearchIndex.objects.update_or_create(
                 post=post,
                 defaults=dict(
@@ -96,6 +102,17 @@ class SearchIndex(models.Model):
         else:
             SearchIndex.objects.filter(post=post).delete()
 
+    def improve_title_search(cls, title):
+        all = cls.reSlash.findAll(title)
+        groupsJoined = [i[0]+i[1] for i in all]
+
+        words = []
+        for s in groupsJoined:
+            words += s.split("/")
+
+        return title + " ".join(words)
+
+

Я к сожалению на PyCharm community editions и с питоном последний раз связывался лет 7 назад, так что мне стыдно PR открывать, но по-моему что-то такое должно помочь.

Можешь дать небольшой фидбек? Если кажется норм, то я могу PR открыть

diff --git a/search/models.py b/search/models.py
index 47b5745..f1131b4 100644
--- a/search/models.py
+++ b/search/models.py
@@ -37,6 +37,10 @@ class SearchIndex(models.Model):
 
     index = SearchVectorField(null=False, editable=False)
 
+    # regular expression to find words with "/" deimeter, but not web addresses like http://soonething.com/asdas or
+    # vas3k.com/some/word
+    reSlash = re.compile(' [^ \/:\.]+\/([^ ]+)')
+
     class Meta:
         db_table = "search_index"
         ordering = ["-created_at"]
@@ -80,6 +84,8 @@ class SearchIndex(models.Model):
                  + _multi_search_vector("topic__name", weight="C")
 
         if post.is_searchable:
+            post.title = cls.improve_title_search(post.title)
+
             SearchIndex.objects.update_or_create(
                 post=post,
                 defaults=dict(
@@ -96,6 +102,17 @@ class SearchIndex(models.Model):
         else:
             SearchIndex.objects.filter(post=post).delete()
 
+    def improve_title_search(cls, title):
+        all = cls.reSlash.findAll(title)
+        groupsJoined = [i[0]+i[1] for i in all]
+
+        words = []
+        for s in groupsJoined:
+            words += s.split("/")
+
+        return title + " ".join(words)
+
+
  1. Названия переменных в питоне пишут в snake_case, а не camelCase
  2. improve_title_search: плохо передает суть функции, лучше сделать его @property и назвать как-то в духе search_index_title
  3. в post.title записывать ничего не надо, если не собираешься сохранять в базу
  4. all уже зарезервировано в языке, нужно другое название
  5. groupsJoined = [i[0]+i[1] for i in all]: плохо понятно, что тут происходит. i обычно используют как название для индекса при итерации

Вообще лучше сразу PR сделать, его сильно удобнее ревьюить

@ngrishanov спасибо, это было полезно, теперь я осмелел до PR. Сделаю на днях, но мне с ним явно нужна будет помощь)

  1. Ок
  2. Ок
  3. Косяк. Но как тогда это в поисковый индекс добавить?
  4. Блин, а чего PyCharm молчит? И кстати я что-то не нахожу https://www.w3schools.com/python/python_ref_keywords.asp точно all зарезервирован?
  5. Поправлю

Это особенность работы ts_vector, он не в одном из конфигов не считает / как разделитель слов, мало ли, вдруг ищете http/2.0 и хотите чтобы он находил только его, а не http и 2 и 0 :) вроде бы . тоже является разделителем только если в конце строки, или с пробелом и mastodon.twitter, также не будет находиться по mastodon.twitter (точки потому что тип host, слеши потом что тип url, ну а собачка не разделитель так как email) вообще имхо стоило бы в таком случае индексить и то и другое, но это как бы поведение, которое должно быть настраиваемым.

мне видится четыре решения:

  1. Переделывать парсер постгри, (CREATE TEXT SEARCH PARSER) , а дальше альтерить конфигурации чтобы они юзали новый парсер. - в принципе самое адекватное решение, но жесткий оверкилл
  2. Приделать другой fts - manticore/lucene и переделывать куча всего - не вариант
  3. Преобразовывать данные перед поиском, и перед индексацией. То есть в зависимости от того какое поведение желаемое, либо просто заменять / на пробел, либо добавляя в конце отдельно разделенные слова. При втором варианте не нужно будет изменять данные перед поиском, только перед индексацией. Да индекс вырастит, да и индексацию возможно придется делать в виде чего то типа to_tsvector('russian', concat(field, translate(field,'/@.',' ')), как это подружить с джанго, я без понятия. но это максимально просто решение в плане трудозатрат.
  4. Забить.

ку, а почему пункт 2 не вариант? я одно время активно внедрял сфинкс на волне его популярности, посмотрел мантик -- вроде всё просто. морфология из коробки, стоп-слова, словоформы итд итп всё есть. да и синтаксис поиска вполне понятный и богатый (https://manual.manticoresearch.com/Searching/Full_text_matching/Operators#Full-text-operators)
на примере поста с mastodon:

mysql> create table posts(`id`, `title` text, `text` text, `post_id` string, `type` string, `is_public` bool, `hotness` integer, `room_id` integer) morphology='stem_enru';
Query OK, 0 rows affected (0,01 sec)

mysql> insert into posts values (0, 'Клубные аккаунты в децентрализованных сетях типа mastodon/nostr/minds/diaspora/dtube', 'Есть посты про наши твиттеры, инстаграмы и фейсбуки, а про новомодные mastodon/nostr/whatever пока \(вроде\) ещё ничего официально не было.\n\nДавайте пиарить друг другу свои трансантиметакартошкафриджаз-аккаунты в
децентрализованных океанах слов, плавающих в пустоте одиночества китайской комнаты наших душ?..\n\nЯ сама, к сожалению, ничего туда не пишу \(ни в мастодон, ни в ностр\), но с удовольствием набрала бы себе более милую ленту.', '387ec440-64b6-4412-b8ed-1b6106c2c96e', 'question',1,0,1);
Query OK, 1 row affected (0,00 sec)

и результат

mysql> SELECT *, highlight() FROM posts WHERE match('mastodon');
+---------------------+---------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------+----------+-----------+---------+---------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| id                  | title                                                                                                                           | text                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      | post_id                              | type     | is_public | hotness | room_id | highlight()                                                                                                                                                                                                                                                         |
+---------------------+---------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------+----------+-----------+---------+---------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| 4182053103148728335 | Клубные аккаунты в децентрализованных сетях типа mastodon/nostr/minds/diaspora/dtube                                            | Есть посты про наши твиттеры, инстаграмы и фейсбуки, а про новомодные mastodon/nostr/whatever пока (вроде) ещё ничего официально не было.

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

Я сама, к сожалению, ничего туда не пишу (ни в мастодон, ни в ностр), но с удовольствием набрала бы себе более милую ленту.                                                                                                                                                                                                                                                                                                                                           | 387ec440-64b6-4412-b8ed-1b6106c2c96e | question |         1 |       0 |       1 | Клубные аккаунты в децентрализованных сетях типа <b>mastodon</b>/nostr/minds/diaspora/dtube |  ...  и фейсбуки, а про новомодные <b>mastodon</b>/nostr/whatever пока (вроде) ещё ...                                                                                |
+---------------------+---------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------+----------+-----------+---------+---------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0,00 sec)
--- 1 out of 1 results in 0ms ---

ну и реализовать 4 метода:

  • добавление поста
  • изменение фултекст-индекса (редактирование текста/заголовка)
  • изменение строк/значений (редактирование параметров поста)
  • удаление поста

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