Мой первый прототип поискового движка

в 13:01, , рубрики: pagefind, ruvds_перевод, sqlite, wget, поисковый движок

Мой первый прототип поискового движка - 1


Я реализовал первый прототип собственного механизма поиска, который сокращённо назвал PSE (Personal Search Engine). Создал я его с помощью трёх скриптов Bash, возложив всю основную работу на sqlite3, wget и PageFind.

Браузер Firefox вместе с Newsboat сохраняют полезную информацию в базах данных SQLite. В moz_places.sqlite содержатся все посещённые URL-адреса и адреса закладок (то есть moz_bookmarks.sqlite базы данных SQLite). У меня получилось около 2000 закладок. Это меньше, чем я предполагал, так как многие оказались нерабочими из-за битых ссылок.

Нерабочие URL-адреса страниц сильно замедляют процесс сбора, так как wget приходится ожидать истечения различных таймаутов (например, DNS, ответа сервера, время скачивания). URL-адреса из «истории» составили бы интересную коллекцию для сбора, но тут не обойтись без списка исключений (например, нет смысла сохранять запросы к поисковым системам, веб-почте, онлайн-магазинам). Изучение этого вопроса я отложу до следующего прототипа.

Кэш Newsboat cache.db обеспечил богатый источник контента и намного меньше мёртвых ссылок (что не удивительно, поскольку я мониторю этот список URL-адресов гораздо активнее, нежели закладки). Из обоих этих источников я собрал 16,000 страниц. Затем с помощью SQLite 3 я запросил из разных БД значения URL, упорядочив их в одном текстовом файле построчно.

После создания списка страниц, по которым я хотел выполнять поиск, нужно было скачать их в каталог с помощью wget. У этого инструмента есть множество настроек, из которых я решил включить регистрацию временны́х меток, а также создание каталогов на основе протокола, сопровождаемого доменом и путём каждого элемента. Это позволило в дальнейшем преобразовать локальные пути элементов в URL-адреса.

После сбора содержимого я использовал PageFind для его индексирования. Поскольку я стал использовать PageFind изначально, то сопроводил этот инструмент опцией --serve, которая предоставляет веб-службу localhost на порту 1414. Всё, что мне требовалось – это добавить файл index.html в каталог, где я собрал всё содержимое и сохранил индексы PageFind. После этого я снова использовал PageFind для предоставления собственного механизма поиска на базе localhost.

И хотя общее число страниц было невелико (16,000), мне удалось получить интересные результаты, просто опробуя случайные слова. Так что прототип получился перспективный.

▍ Текущие компоненты прототипа

Я использую простой скрипт Bash, который получает URL-адреса из закладок Firefox и кэша Newsboat, после чего генерирует файл pages.txt с уникальными URL.

Далее, используя этот файл, с помощью wget я собираю и организую всё содержимое в структуру дерева:

  • htdocs
    • http (все URL-адреса с типом соединения HTTP);
    • https (все URL-адреса с типом соединения HTTPS);
    • pagefind (здесь содержатся индексы PageFind и JavaScript-код интерфейса поиска);
    • index.html (здесь находится страница для интерфейса поиска, использующего библиотеки из pagefind).

Поскольку я скачал только HTML, 16K страниц заняли на диске не сильно много места.

▍ Реализация прототипа

Вот скрипты Bash для получения URL-адресов, сбора контента и запуска локального движка поиска на основе PageFind.

После сбора URL-адресов я использую две переменные среды для обнаружения различных баз данных SQLite 3 (то есть PSE_MOZ_PLACES и PSE_NEWSBOAT):

1.	#!/bin/bash
2.	
3.	if [ "$PSE_MOZ_PLACES" = "" ]; then
4.	    printf "the PSE_MOZ_PLACES environment variable is not set."
5.	    exit 1
6.	fi
7.	if [ "$PSE_NEWSBOAT" = "" ]; then
8.	    printf "the PSE_NEWSBOAT environment variable is not set."
9.	    exit 1
10.	fi
11.	
12.	sqlite3 "$PSE_MOZ_PLACES" 
13.	    'SELECT moz_places.url AS url FROM moz_bookmarks JOIN moz_places ON moz_bookmarks.fk = moz_places.id WHERE moz_bookmarks.type = 1 AND moz_bookmarks.fk IS NOT NULL' 
14.	    >moz_places.txt
15.	sqlite3 "$PSE_NEWSBOAT" 'SELECT url FROM rss_item' >newsboat.txt
16.	cat moz_places.txt newsboat.txt |
17.	    grep -E '^(http|https):' |
18.	    grep -v '://127.0.' |
19.	    grep -v '://192.' |
20.	    grep -v 'view-source:' |
21.	    sort -u >pages.txt

Следующим шагом я с помощью wget получаю страницы:

1.	#!/bin/bash
2.	#
3.	if [ ! -f "pages.txt" ]; then
4.	    echo "missing pages.txt, skipping harvest"
5.	    exit 1
6.	fi
7.	echo "Output is logged to pages.log"
8.	wget --input-file pages.txt 
9.	    --timestamping 
10.	    --append-output pages.log 
11.	    --directory-prefix htdocs 
12.	    --max-redirect=5 
13.	    --force-directories 
14.	    --protocol-directories 
15.	    --convert-links 
16.	    --no-cache --no-cookies

Наконец, у меня есть скрипт, который генерирует страницу index.html и XML-файл в формате OpenSearch Description, индексирует собранные сайты и запускает PageFind в режиме сервера:

1.	#!/bin/bash
2.	mkdir -p htdocs
3.	
4.	cat <<OPENSEARCH_XML >htdocs/pse.osdx
5.	<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"
6.	                       xmlns:moz="http://www.mozilla.org/2006/browser/search/">
7.	  <ShortName>PSE</ShortName>
8.	  <Description>A Personal Search Engine implemented via wget and PageFind</Description>
9.	  <InputEncoding>UTF-8</InputEncoding>
10.	  <Url rel="self" type="text/html" method="get" template="http://localhost:1414/index.html?q={searchTerms}" />
11.	  <moz:SearchForm>http://localhost:1414/index.html</moz:SearchForm>
12.	</OpenSearchDescription>
13.	OPENSEARCH_XML
14.	
15.	cat <<HTML >htdocs/index.html
16.	<html>
17.	<head>
18.	<link
19.	  rel="search"
20.	  type="application/opensearchdescription+xml"
21.	  title="A Personal Search Engine"
22.	  href="http://localhost:1414/pse.osdx" />
23.	<link href="/pagefind/pagefind-ui.css" rel="stylesheet">
24.	</head>
25.	<body>
26.	<h1>A personal search engine</h1>
27.	<div id="search"></div>
28.	<script src="/pagefind/pagefind-ui.js" type="text/javascript"></script>
29.	<script>
30.	    window.addEventListener('DOMContentLoaded', function(event) {
31.	        let page_url = new URL(window.location.href),
32.	            query_string = page_url.searchParams.get('q'),
33.	            pse = new PagefindUI({ element: "#search" });
34.	        if (query_string !== null) {
35.	            pse.triggerSearch(query_string);
36.	        }
37.	    });
38.	</script>
39.	</body>
40.	</html>
41.	HTML
42.	
43.	pagefind 
44.	--source htdocs 
45.	--serve

Затем я просто указываю в браузере адрес http://localhost:1414/index.html. При желании я могу даже передать строку запроса ?q=....

С функциональной точки зрения это очень примитивная система, и 16К страниц явно недостаточно для того, чтобы сделать её привлекательной для использования (думаю, для этого нужно где-то 100К).

▍ Чему я научился на этом прототипе

Текущий прототип имеет несколько ограничений:

  1. Мёртвые ссылки в файле pages.txt значительно замедляют процесс сбора содержимого. Мне нужно найти способ исключить попадание таких ссылок в этот файл или наладить их удаление из него.
  2. В выводе PageFind используются страницы, скачанные мной на локальную машину. Было бы лучше, если бы получаемые ссылки переводились и указывали на фактический источник страницы. Думаю, это можно реализовать с помощью JS-кода в файле index.html при настройке элемента PageFind, отвечающего за результат поиска. Этот вопрос нужно изучить подробнее.

16K страниц – это очень скромный объём. В ходе тестирования я получил интересные результаты, но недостаточно хорошие для того, чтобы начинать реально использовать эту систему поиска. Предполагаю, что для более-менее полноценного её применения нужно собрать хотя бы 100К страниц.

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

На данный момент значительное время занимает сбор содержимого страниц в индекс. Я пока не знаю, сколько конкретно места потребуется для планируемого объёма в 100К страниц.

Настройка индексирования и интерфейса поиска оказалась простейшим этапом процесса. С PageFind гораздо легче работать, нежели с корпоративными приложениями для поиска.

▍ Предстоящие доработки

Я вижу несколько способов расширения поискового массива. Первый подразумевает создание зеркал для нескольких сайтов, которые я использую в качестве ссылок. В wget есть функция зеркала. Опираясь на список из sites.txt, я мог бы периодически отображать эти сайты, делая их содержимое доступным для индексирования.

Экспериментируя с опцией зеркала, я заметил, что получаю PDF-файлы, привязанные к отображаемым страницам. Если я использую команду Linux find для обнаружения всех этих PDF, то смогу применить другой инструмент для извлечения их текста. Таким образом я расширю свой поиск за пределы простого текста и HTML. Нужно хорошенько продумать этот вариант, так как в конечном итоге я хочу иметь возможность восстанавливать путь к PDF при отображении этих результатов.

Ещё один подход будет заключаться в работе со всей историей браузера и его закладками. Это позволит значительно расширить массив страниц для поиска. Тогда я также смогу проверять «шапку» HTML на наличие ссылок на фиды, которые можно будет агрегировать в общий банк фидов. Это позволит захватывать содержимое из интересных для чтения источников, не пропуская посты, которые оказались бы упущены из-за ограничения времени чтения.

Для просмотра интересных страниц из моего RSS-агрегатора я использую приложение Pocket. У этого приложения есть API, и я могу получать из него дополнительные интересные страницы. В Pocket также есть различные организованные списки, в которых может присутствовать интересное содержимое для сбора и индексирования. Думаю, нужно будет реализовать механизм сопоставления выдаваемых системой предложений с определённым списком исключений. Например, нет смысла пытаться собрать содержимое платёжного шлюза или коммерческих сайтов в целом.

Помоги спутнику бороться с космическим мусором в нашей новой игре! 🛸

Автор: Дмитрий Брайт

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js