
Я реализовал первый прототип собственного механизма поиска, который сокращённо назвал 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К).
▍ Чему я научился на этом прототипе
Текущий прототип имеет несколько ограничений:
- Мёртвые ссылки в файле pages.txt значительно замедляют процесс сбора содержимого. Мне нужно найти способ исключить попадание таких ссылок в этот файл или наладить их удаление из него.
- В выводе PageFind используются страницы, скачанные мной на локальную машину. Было бы лучше, если бы получаемые ссылки переводились и указывали на фактический источник страницы. Думаю, это можно реализовать с помощью JS-кода в файле index.html при настройке элемента PageFind, отвечающего за результат поиска. Этот вопрос нужно изучить подробнее.
16K страниц – это очень скромный объём. В ходе тестирования я получил интересные результаты, но недостаточно хорошие для того, чтобы начинать реально использовать эту систему поиска. Предполагаю, что для более-менее полноценного её применения нужно собрать хотя бы 100К страниц.
Очень круто иметь собственный локальный поисковый движок. Он позволит мне продолжать работать, даже если возникнут проблемы с домашним выходом в интернет. Мне это нравится. Поскольку сайт, сгенерированный для моей локальной системы, является «статическим», я могу с лёгкостью воссоздать его в сети и сделать доступным для других машин.
На данный момент значительное время занимает сбор содержимого страниц в индекс. Я пока не знаю, сколько конкретно места потребуется для планируемого объёма в 100К страниц.
Настройка индексирования и интерфейса поиска оказалась простейшим этапом процесса. С PageFind гораздо легче работать, нежели с корпоративными приложениями для поиска.
▍ Предстоящие доработки
Я вижу несколько способов расширения поискового массива. Первый подразумевает создание зеркал для нескольких сайтов, которые я использую в качестве ссылок. В wget есть функция зеркала. Опираясь на список из sites.txt, я мог бы периодически отображать эти сайты, делая их содержимое доступным для индексирования.
Экспериментируя с опцией зеркала, я заметил, что получаю PDF-файлы, привязанные к отображаемым страницам. Если я использую команду Linux
find
для обнаружения всех этих PDF, то смогу применить другой инструмент для извлечения их текста. Таким образом я расширю свой поиск за пределы простого текста и HTML. Нужно хорошенько продумать этот вариант, так как в конечном итоге я хочу иметь возможность восстанавливать путь к PDF при отображении этих результатов.Ещё один подход будет заключаться в работе со всей историей браузера и его закладками. Это позволит значительно расширить массив страниц для поиска. Тогда я также смогу проверять «шапку» HTML на наличие ссылок на фиды, которые можно будет агрегировать в общий банк фидов. Это позволит захватывать содержимое из интересных для чтения источников, не пропуская посты, которые оказались бы упущены из-за ограничения времени чтения.
Для просмотра интересных страниц из моего RSS-агрегатора я использую приложение Pocket. У этого приложения есть API, и я могу получать из него дополнительные интересные страницы. В Pocket также есть различные организованные списки, в которых может присутствовать интересное содержимое для сбора и индексирования. Думаю, нужно будет реализовать механизм сопоставления выдаваемых системой предложений с определённым списком исключений. Например, нет смысла пытаться собрать содержимое платёжного шлюза или коммерческих сайтов в целом.
Помоги спутнику бороться с космическим мусором в нашей новой игре! ?
