Как сделать поиск на питоне
Я собираюсь создать быструю поисковую систему для сайта, которая сможет поддерживать поисковые запросы с автоматическим заполнением (живым поиском) и бесплатным размещением на хостинге. Для этого буду использовать wget, Python, SQLite, Jupyter, sqlite-utils. А также открытое программное обеспечение Datasette , чтобы создать API-интерфейс.
Рабочий пример создаваемой поисковой системы.
Шаг 1: сканирование данных
Сначала нужно получить копию данных, которые должны быть доступны для поиска.
Нам не нужно загружать каждую страницу сайта ‒ нас интересуют только актуальные статьи. Поэтому вытянем только публикации за определенные года. Для этого используем аргумент -I следующим образом:
Установим время ожидания для каждого запроса продолжительностью в 2 секунды: --wait 2
Также исключим из сканирования станицы комментариев, используя команду -X "/*/*/comments" .
Чтобы повторно не загружать в результатах поиска одинаковые страницы, используем параметр —no-clobber.
Код, отвечающий за запуск перечисленных выше команд:
Запустите его и через несколько минут вы получите подобную структуру:
Для проверки работоспособности примера подсчитаем количество найденных HTML-страниц:
Мы загрузили все публикации до 2017 года включительно. Но также нужно загрузить и статьи, которые опубликованы в 2018 г. Поэтому необходимо указать сканеру на публикации, размещенные на главной странице:
Теперь в компьютере есть папка, которая содержит HTML-файлы каждой статьи, опубликованной на сайте. Используем их, чтобы создать поисковой каталог.
Создание поискового каталога с помощью SQLite
SQLite – это наиболее широко используемая СУБД в мире. Она используется встроенными мобильными приложениями.
SQLite имеет очень мощную функциональность полнотекстового поиска. Откройте для себя расширение FTS5 .
Я создал библиотеку утилит на Python под названием sqlite-utils для того, чтобы создание баз данных SQLite было максимально простым. Библиотека предназначена для использования в веб-приложении Jupyter Notebook .
Для использования Jupyter на компьютере должен быть установлен Python 3. Можно использовать виртуальную среду Python, чтобы убедиться, что устанавливаемое программное обеспечение не конфликтует с другими установленными пакетами:
Теперь вы должны оказаться в веб-приложении Jupyter. Нажмите New -> Python 3, чтобы начать новый блокнот.
Особенность блокнотов Jupyter заключается в том, что при их публикации на GitHub они будут отображаться как обычный HTML. Это делает их очень мощным способом обмена кодом с аннотациями. Я опубликовал блокнот, который использовал для построения поискового каталога, в своей учетной записи GitHub.
Вот код Python, который я использовал для очистки релевантных данных из загруженных HTML- файлов. В блокноте приводится построчное объяснение того, что делает код.
Запустив этот код, я получил список словарей Python. Каждый из них содержит документы, которые необходимо добавить в каталог. Список выглядит примерно так:
Библиотека sqlite-utils может взять такой список и на его основе создать таблицу базы данных SQLite. Вот как это сделать, используя приведенный выше список словарей.
Можно проверить созданную таблицу с помощью утилиты командной строки sqlite3 (изначально встроена в OS X). Это можно сделать следующим образом:
Вызываем метод enable_fts(), чтобы получить возможность поиска по полям заголовка, автора и содержимого:
Представляем Datasette
Datasette – это открытое программное обеспечение, которое позволяет с легкостью публиковать базы данных SQLite в интернете. Мы анализировали нашу новую базу данных SQLite с помощью sqlite3 –инструмента командной строки. Возможно, стоит использовать интерфейс, который был бы удобнее для пользователей?
Если хотите установить Datasette локально, можно повторно использовать виртуальную среду, которую мы создали при разработке Jupyter:
Приведенная выше команда установит Datasette в папку ./jupyter-venv/bin/. Также можно установить его для всей системы, используя команду pip install datasette.
Теперь можно запустить Datasette для файла 24ways.db, который мы создали ранее:
Если вы хотите протестировать Datasette, не создавая собственный файл 24ways.db, можете загрузить мой.
Публикация базы данных в интернете
Фасетный поиск
Datasette находит таблицы с настроенным полнотекстовым поиском SQLite и добавляет поле поиска непосредственно на страницу. Взгляните сюда , чтобы увидеть его в действии.
Поиск SQLite поддерживает универсальные символы. Поэтому если вы хотите выполнять поиск с автозаполнением, нужно добавить * в конце запроса.
Особенность Datasette заключается в возможности рассчитать фасеты на основе ваших данных. Вот страница , показывающая результаты поиска с подсчетом фасетов, рассчитанных по столбцам year и topic:
Каждая страница, видимая через Datasette, имеет соответствующий JSON API, доступ к которому можно получить, добавив расширение .json к URL:
Используем собственный SQL для улучшения результатов поиска
Результаты поиска, которые мы получаем из ../articles?_search=svg, достаточно релевантны. Но порядок, в котором они отображаются, не идеален. Результаты размещаются так, как они были загружены в базу данных.
Базовый SQL-запрос выглядит следующим образом:
Можно добиться лучшего результата, создав собственный SQL-запрос. Мы будем использовать следующий запрос:
Давайте разберем код SQL построчно:
Мы используем snippet(), встроенную функцию SQLite , чтобы получить те слова, которые соответствуют запросу.
Это другие поля, которые нам нужно вернуть. Большинство из них из таблицы articles. но мы извлекаем rank (демонстрирует релевантность поиска) из таблицы article_fts.
articles – это таблица, которая содержит наши данные. article_fts – это виртуальная таблица SQLite, которая реализует полнотекстовый поиск. Нужно подключиться к ней, чтобы появилась возможность отправлять ей запросы.
Как превратить это в API? Секрет заключается в добавлении расширения .json. Datasette поддерживает множество видов JSON. Но мы будем использовать ?_Shape=array, чтобы получить простой массив объектов:
JSON API вызывает поиск по статьям с ключевым словом SVG
Простой интерфейс поиска на JavaScript
Для создания интерфейса живого поиска мы будем использовать Vanilla JS. Нам нужно несколько служебных функций. Во-первых, классическая функция debounce:
Она будет отправлять запросы fetch() один раз в 100 мс во время ввода пользователем своего поискового запроса. Поскольку мы отображаем данные, которые могут включать в себя теги HTML, используем функцию htmlEscape. Я очень удивлен, что браузеры все еще не поддерживают одну из следующих команд по умолчанию:
Нам нужен HTML для формы поиска и div для отображения результатов:
А вот и реализация живого поиска на JavaScript:
Есть еще одна вспомогательная функция, используемая для построения HTML:
Как избежать многопоточности в живом поиске
Это ужасный пользовательский опыт: пользователь увидел нужные результаты на долю секунды, а потом они вдруг исчезли и появились результаты более раннего запроса.
К счастью, есть простой способ избежать этого. Я создал переменную с именем requestInFlight, которая изначально равна нулю.
Каждый раз, когда запускается новый запрос fetch(), создается новый объект currentRequest=<> и присваивается переменной requestInFlight.
Когда функция fetch() завершается, я использую requestInFlight!==currentRequest для проверки, что объект currentRequest идентичен объекту requestInFlight. Если пользователь вводит новый запрос в момент обработки текущего запроса, мы сможем это обнаружить и не допустить обновления результатов.
На самом деле, здесь не так уж много кода
Код выглядит весьма неаккуратно. Но вряд ли это важно, если вся реализация поисковой системы укладывается менее чем в 70 строк JavaScript. Использование SQLite– это надежный вариант. СУБД легко масштабируется до сотен МБ (или даже ГБ) данных. А тот факт, что он основан на SQL, обеспечивает простое взаимодействие.
Если использовать Datasette для API, то можно создавать относительно сложные приложения, с минимальными усилиями.
Дайте знать, что вы думаете по данной теме статьи в комментариях. За комментарии, дизлайки, подписки, лайки, отклики низкий вам поклон!
Дайте знать, что вы думаете по данной теме статьи в комментариях. Мы очень благодарим вас за ваши комментарии, дизлайки, подписки, лайки, отклики!
Мы познакомились с линейным поиском, теперь настала очередь бинарного (двоичного).
В чем же он заключается?
Есть отсортированный список чисел, берем число из его середины. Если это число больше искомого, то поиск продолжаем в левой половине списка, если меньше искомого - в правой.
Продолжаем поиск, когда находим искомое число, возвращаем его индекс, иначе делаем вывод, что такого числа нет в списке.
Как утверждается в этой статье, только 10% программистов способны написать двоичный поиск.
Поэтому рекомендую сначала реализовать самим на любимом ЯП, а потом прочитать дальше и поделиться результатом.
Возвращаясь к нашей книжной полке, предположим, что все книги отсортированы по автору, слева направо. То есть вначале полки находится книга Ахматовой, а в конце - Шолохов.
Нам же надо найти книгу, автор которой Пушкин.
Берем книгу посередине полки, смотрим, кто является ее автором. С первого раза мы потерпели неудачу, и автором данной книги оказался Тютчев.
Вспоминая, что книги отсортированы по автору, мы понимаем, что правее данной книги искомой нет, следовательно, надо искать левее.
Поэтому из поиска мы исключаем правую половину полки. Теперь возьмем книгу посередине левой половины полки и посмотрим, кто ее автор. Например, Лесков.
Значит, искомая книга находится правее. Далее берем книгу посреди оставшейся части полки. Если это та книга, которую мы искали, то все шикарно, иначе снова исключаем половину (левую или правую, в зависимости от автора) и тд.
В итоге мы либо находим искомую книгу, либо добираемся до такой малой части полки, где не будет ни одной книги. Тогда можем с увереностью сказать, что искомой книги на полке нет.
Скажу сразу, я нигде не нашел стандартного условия, описывающего ситуацию с дубликатами на полке. Поэтому будем считать, что дублей у нас не будет, но если и будут, то будем возвращать первый найденный индекс.
Процедура будет принимать те же входные параметры, что и в линейном поиске:
Здесь мы на всякий случай сортируем список.
p и r - наши первые границы. q - середина рассматриваемого “подсписка”, среднее значение суммы p и r, без дробной части.
Искомый элемент найден, если значение середины “подсписка” lst[q] равно x.
Если lst[q] > x, мы исключаем все элементы справа от q.
Ясно одно: во время экзамена невозможно искать вопросы в Интернете, но я могу быстро сделать снимок, когда экзаменатор отвернулся. Это первая часть алгоритма. Как-то мне нужно извлечь вопрос из картинки.
Кажется, есть много сервисов, которые могут предоставить инструменты для извлечения текста, но мне нужен какой-то API для решения этой проблемы. Наконец, Google Vision API был именно тем инструментом, который я ищу. Самое замечательное то, что первые 1000 вызовов API бесплатны раз в месяц, чего вполне достаточно для тестирования и использования API.
Vision AI
Сначала зайдите и создайте учетную запись Google Cloud, затем выполните поиск Vision AI в сервисах. Используя Vision AI, вы можете выполнять такие действия, как назначение меток для изображения, чтобы упорядочить ваши изображения, обнаружить известные пейзажи или места, извлечь тексты и кое-что еще.
Проверьте документацию, чтобы включить и настроить API. После настройки вам необходимо создать файл JSON, содержащий ваши ключи для загрузки на ваш компьютер.
Выполните следующую команду для установки клиентской библиотеки:
Затем предоставьте учетные данные для аутентификации для своего кода приложения, установив переменную среды GOOGLE_APPLICATION_CREDENTIALS.
Когда вы запустите код, вы увидите ответ в формате JSON, который включает в себя спецификации обнаруженных текстов. Но нам нужно только чистое описание, поэтому я извлек эту часть из ответа.
Поиск вопроса в Google
Следующим шагом является поиск в разделе вопросов в Google, чтобы получить некоторую информацию. Я использовал библиотеку регулярных выражений для извлечения части вопроса из описания (ответа). Затем мы должны пройтись по извлеченной части вопроса, чтобы иметь возможность искать ее.
Сканирование информации
Мы будем использовать BeautifulSoup для сканирования первых 3 результатов, чтобы получить некоторую информацию по этому вопросу, потому что ответ, вероятно, находится в одном из них.
Кроме того, если вы хотите сканировать определенные данные из списка поиска Google, не используйте элемент inspect для поиска атрибутов элементов, вместо этого напечатайте всю страницу, чтобы увидеть атрибуты, потому что она отличается от фактической.
Нам нужно сканировать первые 3 ссылки в результатах поиска, но эти ссылки действительно перепутаны, поэтому важно получить чистые ссылки для сканирования.
Как видите, фактическая ссылка находится между q= и &. Используя Regex, мы можем получить это конкретное поле или действительный URL.
Это основная часть алгоритма. После сканирования информации из первых 3 результатов, программа должна определить ответ путем итерации документов. Сначала я подумал, что лучше использовать алгоритм сходства для обнаружения документов, который наиболее похож на вопрос, но я понятия не имел, как его реализовать.
Давайте сначала установим пакет:
Я загружаю предварительно обученные модели и данные вручную, используя функции загрузки, которые включены в приведенный ниже пример кода:
вывод должен выглядеть так:
Он печатает точный ответ и параграф, который включает в себя ответ.
В основном, когда вопрос извлечен из изображения и отправлен в систему, ретривер выберет список документов из просканированных данных, которые с наибольшей вероятностью содержат ответ. Как я уже говорил ранее, он вычисляет косинусное сходство между вопросом и каждым документом в просканированных данных.
После выбора наиболее вероятных документов система делит каждый документ на параграфы и отправляет их с вопросом в Reader, который в основном представляет собой предварительно обученную модель глубокого обучения. Используемая модель была Pytorch-версией хорошо известной NLP модели BERT.
Затем Reader выводит наиболее вероятный ответ, который он может найти в каждом параграфе. После Reader в системе есть последний слой, который сравнивает ответы с использованием внутренней функции оценки и выводит наиболее вероятную в соответствии с оценками, которые будут ответом на наш вопрос.
Вот схема системного механизма.
Вы должны установить свой фрейм данных (CSV) в определенной структуре, чтобы он мог быть отправлен в конвейер cdQA.
Но на самом деле я использовал конвертер PDF для создания входного фрейма данных из каталога файлов PDF. Итак, я собираюсь сохранить все просканированные данные в PDF-файл для каждого результата.
Надеемся, что у нас будет всего 3 файла PDF (может быть 1 или 2). Дополнительно, нам нужно назвать эти PDF-файлы, поэтому я просканировал заголовок каждой страницы.
Функция search подходит в том случае, когда надо найти только одно совпадение в строке, например, когда регулярное выражение описывает всю строку или часть строки.
Рассмотрим пример использования функции search в разборе лог-файла.
Содержимое файла log.txt:
При этом MAC-адрес может прыгать между несколькими портами. В таком случае очень важно знать, с каких портов прилетает MAC.
Попробуем вычислить, между какими портами и в каком VLAN образовалась проблема. Проверка регулярного выражения с одной строкой из log-файла:
Регулярное выражение для удобства чтения разбито на части. В нём есть три группы:
- (\d+) - описывает номер VLAN
- (\S+) and port (\S+) - в это выражение попадают номера портов
В итоге, в группы попали такие части строки:
В итоговом скрипте файл log.txt обрабатывается построчно, и из каждой строки собирается информация о портах. Так как порты могут дублироваться, сразу добавляем их в множество, чтобы получить подборку уникальных интерфейсов (файл parse_log_search.py):
Результат выполнения скрипта такой:
Обработка вывода show cdp neighbors detail¶
Попробуем получить параметры устройств из вывода sh cdp neighbors detail.
Пример вывода информации для одного соседа:
Задача получить такие поля:
- имя соседа (Device ID: SW2)
- IP-адрес соседа (IP address: 10.1.1.2)
- платформу соседа (Platform: cisco WS-C2960-8TC-L)
- версию IOS (Cisco IOS Software, C2960 Software (C2960-LANBASEK9-M), Version 12.2(55)SE9, RELEASE SOFTWARE (fc1))
И для удобства надо получить данные в виде словаря. Пример итогового словаря для коммутатора SW2:
Пример проверяется на файле sh_cdp_neighbors_sw1.txt.
Первый вариант решения (файл parse_sh_cdp_neighbors_detail_ver1.py):
Тут нужные строки отбираются с помощью метода строк startswith. И в строке с помощью регулярного выражения получается требуемая часть строки. В итоге все собирается в словарь.
Читайте также: