Я реализовал первый прототип собственного механизма поиска, который сокращённо назвал 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 также есть различные организованные списки, в которых может присутствовать интересное содержимое для сбора и индексирования. Думаю, нужно будет реализовать механизм сопоставления выдаваемых системой предложений с определённым списком исключений. Например, нет смысла пытаться собрать содержимое платёжного шлюза или коммерческих сайтов в целом.
Автор: Дмитрий Брайт