Поиск не парсит слэши
GRbit opened this issue · comments
Чеклист
- Я поискал поиском по трекеру похожие проблемы, в том числе в закрытых Issues
- Баг стабильно воспроизводится и я знаю как это сделать
Описание бага
Есть пост https://vas3k.club/question/18282/
Очень хочется его найти по слову mastodon, но так как рядом с ним слэш — поиск не выдаёт ничего путного:
https://vas3k.club/search/?q=mastodon&type=post
Ожидаемый результат
Видеть пост в результатах поиска
Шаги к воспроизведению
Да вроде и так всё ясно
Это особенность работы ts_vector, он не в одном из конфигов не считает / как разделитель слов, мало ли, вдруг ищете http/2.0 и хотите чтобы он находил только его, а не http и 2 и 0 :)
вроде бы . тоже является разделителем только если в конце строки, или с пробелом
и mastodon.twitter, также не будет находиться по mastodon.twitter
(точки потому что тип host, слеши потом что тип url, ну а собачка не разделитель так как email)
вообще имхо стоило бы в таком случае индексить и то и другое, но это как бы поведение, которое должно быть настраиваемым.
мне видится четыре решения:
- Переделывать парсер постгри, (CREATE TEXT SEARCH PARSER) , а дальше альтерить конфигурации чтобы они юзали новый парсер. - в принципе самое адекватное решение, но жесткий оверкилл
- Приделать другой fts - manticore/lucene и переделывать куча всего - не вариант
- Преобразовывать данные перед поиском, и перед индексацией. То есть в зависимости от того какое поведение желаемое, либо просто заменять / на пробел, либо добавляя в конце отдельно разделенные слова. При втором варианте не нужно будет изменять данные перед поиском, только перед индексацией. Да индекс вырастит, да и индексацию возможно придется делать в виде чего то типа to_tsvector('russian', concat(field, translate(field,'/@.',' ')), как это подружить с джанго, я без понятия. но это максимально просто решение в плане трудозатрат.
- Забить.
@zyuhel
Пункт 3 правда звучит неплохо. Понятия не имею правда как именно это сделать.
Могу только предложить регулярку для поиска элементов через слэши которые не похожи на веб адрес:
[^ :/\.]*(/[^ /]*)+
Вот можно ей проходится по тексту, и в в итоге то что она найдёт делить на слова и добавлять к индексу.
Чуть поясню за реглярку: ищет что-то что начинается с пробела, так чтобы первое слово не было похоже на веб адрес. Похожесть проверяется по содержанию двоеточия или точки. Т.е. то что начинается с vas3k.com или http:// не подпадает. А дальше ищутся не разделённые пробелами куски типа /чето-то/там/ещё в любом количестве.
@GRbit с учетом того что код кидает в django'вский SearchVector, вот так _multi_search_vector("full_name", weight="A") , а дальше это идет в ts_vector, я опасаюсь что приделывание туда регулярок будет выглядеть максимально костыльно. я ни черта не разбираюсь в django, мне на ум приходят только костыли, в виде создания вирт колонки, с нужным преобразованием, и потом использовать ее как дополнительное поле по которому ищется. Но оно даже кажется не красивым
Не так прочитал
@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) + +
- Названия переменных в питоне пишут в snake_case, а не camelCase
improve_title_search
: плохо передает суть функции, лучше сделать его@property
и назвать как-то в духеsearch_index_title
- в
post.title
записывать ничего не надо, если не собираешься сохранять в базу all
уже зарезервировано в языке, нужно другое названиеgroupsJoined = [i[0]+i[1] for i in all]
: плохо понятно, что тут происходит.i
обычно используют как название для индекса при итерации
Вообще лучше сразу PR сделать, его сильно удобнее ревьюить
@ngrishanov спасибо, это было полезно, теперь я осмелел до PR. Сделаю на днях, но мне с ним явно нужна будет помощь)
- Ок
- Ок
- Косяк. Но как тогда это в поисковый индекс добавить?
- Блин, а чего PyCharm молчит? И кстати я что-то не нахожу https://www.w3schools.com/python/python_ref_keywords.asp точно all зарезервирован?
- Поправлю
Это особенность работы ts_vector, он не в одном из конфигов не считает / как разделитель слов, мало ли, вдруг ищете http/2.0 и хотите чтобы он находил только его, а не http и 2 и 0 :) вроде бы . тоже является разделителем только если в конце строки, или с пробелом и mastodon.twitter, также не будет находиться по mastodon.twitter (точки потому что тип host, слеши потом что тип url, ну а собачка не разделитель так как email) вообще имхо стоило бы в таком случае индексить и то и другое, но это как бы поведение, которое должно быть настраиваемым.
мне видится четыре решения:
- Переделывать парсер постгри, (CREATE TEXT SEARCH PARSER) , а дальше альтерить конфигурации чтобы они юзали новый парсер. - в принципе самое адекватное решение, но жесткий оверкилл
- Приделать другой fts - manticore/lucene и переделывать куча всего - не вариант
- Преобразовывать данные перед поиском, и перед индексацией. То есть в зависимости от того какое поведение желаемое, либо просто заменять / на пробел, либо добавляя в конце отдельно разделенные слова. При втором варианте не нужно будет изменять данные перед поиском, только перед индексацией. Да индекс вырастит, да и индексацию возможно придется делать в виде чего то типа to_tsvector('russian', concat(field, translate(field,'/@.',' ')), как это подружить с джанго, я без понятия. но это максимально просто решение в плане трудозатрат.
- Забить.
ку, а почему пункт 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. переход на сфинкс в своё время частенько приводил к экономии ресурсов