Обновить

Подсчёт упоминаний бренда и ключей в текстах и выдаче: регулярное выражение по подстроке ошибается, лемматизация нет

При разметке выдачи и ответов нейросетей нужно считать, где назван бренд и где стоит ключ. Поиск подстрокой даёт ложные срабатывания: nord находит «nordwind», основа мар\w* для имени «Мария» находит «маршрут». В обратную сторону подстрока теряет склонения: «авторских туров на Бали» не совпадает с ключом «авторский тур бали».

Бренд латиницей ищется как целое слово, с границами по буквам и дефису (просмотр назад и вперёд из модуля re):

python

import re

def has_brand(text, brand):
    return re.search(rf'(?<![\w-]){re.escape(brand)}(?![\w-])', text, re.I) is not None

>>> bool(re.search('nord', 'перелёт nordwind, туры на север', re.I))
True
>>> has_brand('перелёт nordwind, туры на север', 'nord')
False
>>> has_brand('Nord Travel: туры на север', 'nord')
True

Русские ключи и имена сравниваются по начальным формам слов. Для этого подходит pymorphy3, морфологический анализатор для русского языка, продолжение pymorphy2 (документация):

python

import pymorphy3

morph = pymorphy3.MorphAnalyzer()

def lemmas(text):
    return {morph.parse(w)[0].normal_form
            for w in re.findall(r'[а-яёa-z0-9-]+', text.lower())}

def has_key(text, key):
    return lemmas(key) <= lemmas(text)

>>> has_key('авторских туров на Бали', 'авторский тур бали')
True
>>> has_key('тур на Бали', 'авторский тур бали')
False
>>> bool(re.search(r'\bмар\w*', 'маршрут по острову'))
True
>>> has_key('маршрут по острову', 'Мария')
False
>>> lemmas('Марией Марию Марии')
{'мария'}

Ключ засчитывается, если все его слова есть в тексте в любой форме и в любом порядке. Порядок и соседство слов для ключа обычно не важны: «туры на Бали авторские» тот же запрос. Если важна точная фраза, сравнивается последовательность лемм.

Тот же приём проверяет минус-слова рекламной кампании. Минус-слово, чья лемма совпадает с леммой рабочего ключа, отрезает этот ключ от показов. Такую пару стоит найти до запуска:

python

def conflicts(keys, minus):
    m = {morph.parse(w)[0].normal_form: w for w in minus}
    return [(k, m[l]) for k in keys for l in lemmas(k) if l in m]

>>> conflicts(['котельная под ключ', 'блочные котельные цена'], ['ключи', 'бесплатно'])
[('котельная под ключ', 'ключи')]

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

Источники:

Теги:
0
Комментарии0

Почему UFW не спасает от Docker: анатомия сетевой подставы и что ищут боты в открытых портах БД

Типичная картина при переезде на свежий VPS. Подняли чистую Ubuntu или Debian под новый проект, сразу навели базовый порядок: ufw default deny incoming, снаружи оставили только 22, 80 и 443. Глянули ufw status — всё четко, лишнего не торчит. Дальше ставится Docker, через компоуз взлетает бэкенд с PostgreSQL и Redis. Чтобы удобно ходить в базу с ноута через DBeaver и гонять миграции, на автомате дописываем ports: - "5432:5432". Кажется, что мы в домике: наружу ничего не светит, фаервол же режет всё, кроме веба и SSH.

А через пару дней прилетает абуза от хостера или срабатывает алерт в мониторинге: все ядра в полке, в htop резвится какой‑нибудь kdevtmpfsi или kinsing, а вместо таблиц в базе лежит одна записка README_TO_RECOVER с биткойн‑кошельком. Грабли старые, как сам Docker. Но на них с завидной регулярностью наступают и джуны, и вполне опытные админы, привыкшие доверять выводу ufw status.

Почему UFW не спасает от Docker: анатомия сетевой подставы и что ищут боты в открытых портах БД

Публикации