Веб-архив — это система, которая периодически сохраняет сайт (или часть сайта) в его оригинальном виде. Чаще всего это делается для потомков, чтобы они могли «поиграться, покликать и поностальгировать».
Основное требование к веб-архиву звучит просто и всеобъемлюще.
Офлайн-версия сайта должна быть полностью функциональной. В ней должны быть видны все оригинальные изображения, флэш-анимация, встроенное видео, работать скрипты и так далее. В идеале она не должна ничем отличаться от оригинала.
Для нас, разработчиков, выражение «полнофункциональная офлайн-версия» звучит очень, очень подозрительно. Можно даже сказать — крамольно звучит. Ведь современный сайт без скриптов не бывает, а скрипты всегда порождают неопределенность в поведении. Но, как говаривал один персонаж: «Не нужно спешить с выводами, не то выводы на тебя набросятся».
Матчасть
Честно говоря, в открытых источниках информации читать не перечитать. Можно начать со статьи в википедии. К сожалению, о реализации там не очень много, а больше говорится об организационных и юридических проблемах.
Для интересующихся — рекомендую почитать. Для остальных приведу несколько терминов для общего развития.
Веб-архив. Например, самый главный архив интернета — archive.org. Пугает своими объемами и сложностью использования.
Веб-краулер — программа, которая умеет перебирать страницы стайта, переходя по ссылкам. В настоящее время их развелось очень много. Пожалуй, самый известный робот, посещения которого так ждут — Google Bot. Для POC мы пользовались ABot.
Построение системы целиком требует хранилища, интерфейсов и проч., но, к сожалению, в одну статью все не поместится. Поэтому тут я расскажу только о самой трудной части — алгоритме работы обхода сайта и сохранения данных.
Подход к решению
Как решать задачу архивирования, думаю, очевидно. Сайты сделаны для Пользователей. Как поступает Пользователь? Открывает страницу, запоминает нужную информацию, переходит по ссылке на следующую страницу.
Попробуем сделать виртуального пользователя — робота, — и задачу немного автоматизировать.
«История» (привет, эджайл) работы робота выглядит так:
робот переходит со странцы на страницу по ссылкам (как пользователь). После перехода сохраняет страницу. Составляет список ссылок, по которым со страницы можно перейти. Уже пройденные ссылки игнорирует. Непройденные — сохраняет, и так далее.
Выглядит очень кратко, и очень абстракно. Первый шаг в проектировании всегда абстрактный, на то он и первый. Сейчас буду его детализировать.
Детализация №1
Для начала нужно определить для себя, что будет базовой «неделимой» сущностью в нашей модели данных. Пусть это будет назваться «Ресурс». Определяю его так:
Ресурс — это любой контент, который мы можем скачать по ссылке.
То есть, основные его свойства — это наличие ссылки (URI) и контента, который возвращает сервер. Для полноты нужно дополнить описание ресурса метаданными (тип, ссылка, времея последнего изменения, и т.д.). Кстати, ресурс может содержать ссылки на другие ресурсы.
Исходя из этого понятия, определяю общий алгоритм работы краулера.
- Подготовка: Поставить URI точки входа в очередь на обработку
- Основной цикл: Выбрать ссылку из очереди
- Скачать ресурс по указанной ссылке
- Сделать с ресурсом что-то полезное
- Выяснить, на какие ресурсы данные ресурс ссылается (referred resources)
- Поставить их в очередь
- Перейти на начало цикла
В целом, выглядит логично. Можно детализировать дальше.
Детализация №2
Шаг нулевой. Подготовка.
Итак, робот нахоится в начале процесса: у него есть только ссылка на точку входа на сайт, так называемая index page. На этом шаге робот создает очередь, и кладет в нее ссылку на точку входа.
Если говорить абстрактно, то очередь представляет собой источник задач для робота. Сейчас очередь со своим единственным элементом выглядит так.
(Примечание: очередь обработки для небольших сайтов можно хранить в памяти, но для больших лучше сохранить в базе. На случай, если процесс прервется где-то посередине).
Шаг первый. Анализ контента.
Выбрать из очереди ресурс для обработки. (На первой итерации это — точка входа). Скачать страницу и выяснить, на какие ресурсы она ссылается (referred resources).
Здесь, в общем, все просто. Страница находится по адресу site. Робот скачивает ее и анализирует html-контент на предмет ссылок. (О типах ссылок см. «Виды ресурсов» ниже). Несколько ссылок указаны в примере: robots.txt (его робот игнорирует:), ссылка «О нас» — about.html, ссылки на CSS и Javascript файлы, ссылка на Youtube видеоролик.
Шаг второй
Отфильтровать ненужные ресурсы. Для этого робот должен предоставлять очень гибкий настроечный интерфейс (большинство существующих таки делают). Например, робот должен уметь фильтровать файлы по типу, расшрению, размеру, времени обновления. Для исходящих ссылок также нужно проверять глубину вложенности. Очевидно, что если ресурс по какой-то ссылке уже обработан, то трогать его не стоит.
Для оставшихся, нужных ресурсов — создать структуры описания и поставить в очередь. Важно заметить, что структуры на этом этапе заполнены не полностью: в них указаны только оригинальные (онлайн) ссылки. (То есть, точно также, как и первоначальная точка входа на нулевом шаге).
Важно: на этом этапе контент страницы «Index page» все еще содержит оригинальные ссылки, и значит, не может быть использован как офлайн-версия. Для того, чтобы завершить обработку полностью, нужно заменить ссылки: они должны указывать на сохраненные офлайн-версии ресурсов. С использованием очереди реализовать это нетрудно: нужно задачу на обновление ссылок «Index page» поставить в конец очереди. Таким образом гарантируется, что к началу выполнения этой задачи все referred resources будут обработаны.
Шаг третий
В целом, это есть шаг первый, только для каждого referred resources. (То есть в реализации алгоритм будет проще. Здесь шаги цикла развернуты для упрощения картины). На этом шаге робот извлекает очередное задание из очереди (задачи download, добавленные на предыдущем шаге).
Затем выясняет, каким преобразованиям требуется подвергнуть ресурс для использования в офлайне. В указанном примере все ресурсы просто скачиваются, за исключением «встроенного» видео: оно скачивается специальным образом через youtube и сохраняется как avi файл локально.
После этого формируются локальные (офлайн) ссылки для referred resources.
Важно: так же, как и на первом шаге, для каждого их ресурсов необходимо выявить исходящие ссылки и поставить их — правильно — в очередь.
(В этом примере CSS файл ссылается на image.png).
Шаг четвертый
Очередным заданием в очереди после удаления оттуда referred resources (и, разумеется, image.png) будет обновление ссылок в index page. Тут, возможно, для html-страниц придется менять и структуру. Например, офлайн-версию видео «встраивать» через какой-то свой проигрыватель.
Шаг пятый
Перейти на первый шаг и продолжать до тех пор, пока очередь не опустеет.
Масштабируемость
Алгоритм с использованием очереди страдает одним недостатком: ресурсы обрабатываются последовательно, а значит, не так быстро, как это может быть на современном сервере.
Поэтому стоит предусмотреть вохможность параллельной обработки. Есть два варианта параллелизма:
- Макро-уровень: несколько краулеров будут работать параллельно над разными сайтами
- Микро-уровень: один краулер будет обрабатывать ресурсы в параллели
Микро-уровень вызывает вопросы с блокировками. Если вспомнить, то задача «обновление ссылок» ставится в конец очереди именно для консистентности. Мы ожидаем, что к моменту запуска этой задачи все связанные ресурсы уже получили локальные ссылки и обработаны. При параллельной работе это условие нарушается, и нужно будет вводить точки синхронизации. Например, простой вариант: запускать задачи на скачивание асинхронно. При появлении задачи на обновление ссылок — дождаться завершения активных задач на скачивание (так называемые Barrier). Сложный вариант — вводить зависимости между задачами в явном виде через семафоры. Может быть, есть еще варианты, не анализировал глубоко.
Виды ресурсов
Очевидно, что нельзя предусмотреть все виды ресурсов, с которыми приходится иметь дело такому краулеру. Но мы попробуем, как минимум для того, чтобы знать, с какими трудностями придется столкнуться.
- Html-страницы. Самый распространенный вид ресурсов. Довольно простой в обработке (в .net с использованием HtmlAgilityPack и CsQuery). Может ссылаться на ресурсы такого вида:
- Другие страницы или файлы через <a>
- Изображения <img>
- CSS-стили <link rel=«stylesheet»>
- Скрипты <script>
- Флэш-анимации <object>
- «Встроенное» видео <object>, <embed> или <iframe>
- Обратить внимание: система должны быть достаточно гибкой, чтобы компоненты для новых типов ссылок и их обработки добавлялись без изменения основного кода (всем известный Open-Close principle)
- CSS-стили. Скачиваются как есть. Могут ссылаться на другие ресуры (css & images) через функцию «url»
- Скрипты. Скачиваются как есть. Выяснить прямые ссылки не из скриптов не представляется возможным (см. ниже о динамических страницах подробнее)
- Видео. Скачивается с использованием специальных подходов, которые оличаются в зависимости о провайдера. Никуда не ссылаются
- Флэш-анимация. Скачивается как swf файл. Может ссылаться на другие ресурсы и докачивать другие флэш-файлы, но из мы определять не будем.
- Динамические ссылки. Первый вид: возникают как следствие манипуляции DOM, требующие загрузки дополнительных файлов (изображения, скрипты). Второй вид: XHR-запросы.
Динамические страницы
Робот задает мне вопрос: «А все же — что мы решили насчет динамических страниц, скриптов, и проч.»? Отвечаю: на проверку оказывается, что не все страницы одинаково «динамичны». Я определяю следующие уровни «динамичности» страниц:
- Уровень 1. Старые добрые html-страницы. Статический html-контент с CSS-стилями.
- Уровень 2. Html-контент, усиленный скриптом. Здесь скрипт не делает ajax-реквестов, а манипулирует только тем контентом, который пришел с сервера. Например, сортировщик таблиц, или expander.
- Уровень 3. Onload-загрузчик. После загрузки страницы начинает работать скрипт и догружает какой-то дополнительный контент.
- Уровень 4. Клиентское приложение. Пользовательский интерфейс модифицируется по действиям пользователя. Скрипт догружает данные недетерминированно. Так работают одностраничные приложения, «бесконечные» списки, умные expanders
Чем больше уровень динамичности, тем сложнее сохранить офлайн-контент:
- Уровни 1 и 2 — Прекрасно сохраняются, прекрасно работает в офлайне.
- Уровень 3 — есть только один способ правильно обработать такую страницу. Для этого нужно запустить браузер (программно, конечно), и дождаться, пока загрузится основная страница и весь дополнительный контент:
- Инстанциируем встроенный браузер и загружаем в него страницу.
- После оригинального HTML загружаются стили, скрипты, изображения.
- Скрипты делают свои черные дела (модифицируют ДОМ, делают XHR-запросы).
- Система отслеживает все запросы, происходящие во время загрузки страницы. (Простой пример — так делает Firebug).
- Все запросы считаются ссылками на ресурсы. Ответ от сервера записывается в базу, и обрабатывается по алгоритму из прошлой части.
Для POC я пользовался Webkit.Net (git@github.com:webkitdotnet/webkitdotnet.git), который пришлось немного допиливать.Работает примерно как Firebug, с той разницей, что это из .Net.
- Уровень 4. Single-page application. Не при нашем уровне развития технологий. Да и к чему, спрашивается, нужно сохранять офлайн версию такого приложения? Обычно SPA показывают очень суъективный контент, который зависит от пользователя и прочих многочисленных параметров. Мое личное мнение, что это не нужно, да и чрезвычайно трудно — как лететь быстрее света. Все равно, никто не увидит и не оценит.
Но предложить решение — мой долг. Выполняю. У меня есть даже два варианта.
Вариант с оператором.
- Оператор устанавливает специальное расширение для браузера (либо пользуется нашим браузером целиком).
- После первоначальной загрузки страницы делается снэпшот ее ДОМа. Не знаю, как по-русски. «Снимок» как-то странно звучит.
- Пользователь начинает работать с приложением, «кликает и тайпает».
- По любому изменению ДОМа определяется, что изменилось, и сохраняем следуюший «кадр» ДОМа.
- В итоге получается некая ДОМ-анимация. Сохраняем в любом удобном нам формате.
- Архивированная версия получится «только для чтения», но функциональность оригинальной страницы передавать будет.
Вариант с машинным обучением или скриптом. В целом, похож на первый вариант, но шаги оператора явно или неявно запоминаются для дальнейшего автоматизированного воспроизведения. Можно применить автоматические классификаторы и даже нейросети: для исследователей тут необъятное поле возможностей. К сожалению, мы не исследователи, а разработчики. Трудоемкость, если честно, оценить не возьмусь.
Заключение
Помимо робота, в системе веб-архива должны присутствовать хранилище, индексация и интерфейс — я их сознательно исключил из рассмотрения. Во-первых, эти компоненты довольно просты и в интеграции и настройке по сравнению с роботом. Во-вторых, чтобы просто не отвлекали.
Систему веб-архивирования можно построить за приемлимое время, но она будет обладать ограничениями из-за динамической природы современных сайтов. Динамические страницы вплоть до третьего уровня поддаются приемлимой архивации. Для четвертого уровня потребуется довольно много усилий.
В целом, существует два пути развития роботов. Первый — с применением искусственного интеллекта. Необходимо развивать краулер так, чтобы он «понимал», что делает. Второй — технологический. Необходимо создать платформу для сайтов, что-то вроде умного CMS. Работающие на ней сайты могли бы выставлять роботам контент в простом для архивации виде.
Автор: nchaly