Парсинг HTML в браузерах для смены вёрстки блоков

Рассмотрим задачу парсинга HTML на клиенте (Javascript) с последующим оформлением полученных данных стилями и вёрсткой и выводом их в нужные места страницы просмотра. Применение такой подгрузчик страниц и блоков нашёл в юзерскриптах — когда разработчики подгрузчика никак не связаны с разработчиками сайта. Но есть основания использовать подход и для обычных сайтов для полного отделения View от Model.

Статья получилась теоретической, потому что из-за её объёма я не стал перегружать её практическими результатами. Да и трудно пока представить некие шаги, по которым каждый мог бы подхватить идеи и начать строить подобное. Вначале надо посеять идеи, но пока попытки посева (здесь, на Хабре) не давали всходов, хотя я и не особо старался в этом направлении. Подход развивался последние полгода и был даже анонсирован на Хабре примерно в апреле-мае. В статье рассказано, «как это сделать», и перечислены преимущества подхода. Он требует глубокого и специфического программирования на JS. По результатам работы, скорее всего, имеет смысл выделить библиотеку для аналогичных задач.

Введение

При парсинге страниц и построении из полученных данных новых страниц в одном блоке HTML сплетается ряд алгоритмических задач, из которых вырисовывается отдельный паттерн (шаблон) проектирования. Его цель можно описать как создание динамического шаблона, различные или все части которого на этапе построения могут отсутствовать, но в процессе дополнения данными появляется частично или полностью собранный блок, построенный в нужном порядке элементов и с применением нужных стилей.

Пример: к существующей странице с заголовком статьи подгрузили полную статью и к заголовку добавили недостающие части. Такой пример постоянно работал в HabrAjax — к ленте аннотаций и к другим лентам подгружал полные статьи, и это было основной и самой запутанной частью скрипта (он работал над несколькими типами страниц). Второй пример: имеется только название и ссылка статьи; загружаем всю статью и выкладываем рядом в вёрстке, отличной от исходной. В дальнейшем, можем менять вёрстки и стили — задать параметром блока или всей страницы просмотра требуемое его представление.

Второй пример вначале было желание добавить к работающему скрипту, в котором все роли паттерна были смешаны. Кое-что даже начало получаться, но с массой недоработок — у статьи отсутствовали заголовок, теги, автор и прочие атрибуты. А старый скрипт начал разваливаться — его часть, работающая в вопросах-ответах, перестала понимать изменения. После дня упражнений со спагетти-кодом стало понятно, что лучше это всё переписать, заложив системный подход. Так получился новый код и паттерн.

Структура

Наш обработчик подгружаемых статей имеет такую структуру.

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

Парсер страницы. В текущем виде он извлекает содержательные блоки данных — статью, автора, название, дату, комментарии, оценки статьи. Может как оставить классы блоков, так и удалить их, чтобы погрузить данные в собственные блоки, изменяя стили представления. На отдалённое будущее привязываться к существующим классам страницы как раз вредно — создатели страниц могут в любой момент изменить имя класса (date на published, published на current_date), и если в стилях своей страницы они присутствуют, потребуется менять стили. Если отвязаться от классов исходной страницы, все изменения останутся в парсере. Забота об отслеживании изменений тоже возлагается на парсер.

Подгрузчик стилей. Пока скрипт показа страниц имеет единственный стиль, подгрузчик не нужен. Можно просто задать стили в CSS или подгрузить их скриптом единожды. Но при желании менять стили динамически, это легко сделать, имея подгрузчик. Стили будут находиться в DOM в виде списков правил. Определённый список правил нужно удалить, а другой — загрузить (стандартным методом).

Итератор размещения вёрсток. В зависимости от того, в каком состоянии находится страница просмотра, итератор размещает блоки вёрстки с вложенными в них новыми данными из парсера в нужные места страницы. Часть данных может быть скрыта, чтобы быть показанной по клику пользователя — состояниями показа управляет итератор (потому он по определению — итератор).

С таким распределением обязанностей система подгрузки приобретает управляемый вид и становится не столь запутанной, чем если бы писать её как раньше, всю подряд, без разделения ролей подсистем. В этом есть суть нашего вновь изобретённого паттерна подгрузки блоков.

Если посмотреть на код результата и исходный прототип, то, действительно, видно, что из частей, не отвечающих за представления, удалены стили и перенесены в свои части. Каждый блок системы отделён от других, влияние блоков ограничено потоками данных между ними.

Порядок работы, подключение

Чтобы вся замечательно собранная система начала работать, её надо чем-то запустить. Точками запуска служат ссылки и активные области (кнопки) на существующей странице. Это может быть как простая страница, переделанная под запуск подгрузчика, так и своя страница, сгенерированная ранее и имеющая аналогичные кнопки и ссылки запуска.

Запуск с простой страницы состоит в перехвате кликов пользователя. Вместо, например, загрузки новой страницы со статьёй, клик по ссылке (заголовок, кат, комментарии) активизирует AJAX-загрузчик. Далее всё идёт по цепочке передачи управления, и в результате, возле ссылки или кнопки образуется блок с подгруженной статьёй и комментариями.

Всё хорошо до момента смены вёрстки исходной страницы создателями сайта. Если вёрстка, классы и прочие способы привязки нашего скрипта изменились, он просто не найдёт своих точек запуска, не расставит своих обработчиков.

Чтобы лучше контролировать страницу показа статей, её тоже нужно заранее обработать тем же парсером, уйти от показа вёрсток и стилей сайта полностью — тогда задача запуска парсера полностью уходит в него самого — если парсер распознает начальную страницу (со списком заголовков статей), то пользователь будет уже работать со своей страницей, сделанной из шаблона, полностью построенной на клиентских стилях. Снаружи может поступить лишь один аварийный сигнал: страница не распознана.

Если вёрстка сайта изменилась настолько, что парсер не нашёл свои контрольные точки, парсер сообщает об этом обработчику, и например, принимается решение показать нераспознанную страницу в новом окне. Или в существующем окне — главное, что показ данных выполняется, хотя не под контролем системы. А разработчикам парсера идёт сигнал о необходимости доработки его под изменившиеся условия.

Практика показывает, что глубоких изменений вёрстки на сайте не случается годами, в то время как изменения стилей происходят регулярно, раз в 2-несколько месяцев. Если допускать показ стилей сайта, смешанных с собственными стилями (юзерстилями), то править огрехи показа придётся регулярно, с каждым изменением стилей сайта. Если показывать полностью свои стили, пропущенные через обработчик, образуется буфер для чистого показа данных, отображение которого может нарушиться лишь в редких аварийных случаях полной смены вёрстки и дизайна. И даже эти случаи развитый парсер сможет корректно обработать, показав исходные страницы или сообщив о мелких отсутствиях данных на привычных местах.

В идеале, парсер должен контролировать все несущественные части страницы, чтобы обнаружить в них изменения, отличные от привычных — новые блоки, информеры, рекламу, и все нововведения передавать разработчикам. В случае подгружаемых блоков (как рекламы, так и полезных частей), парсер должен работать в скрытом образе живой исходной страницы. Такой режим не всегда возможен (из айфрейма живая страница может убежать в родительское окно) и не всегда нужен — он нужен для периодического контроля необычных нововведений на сайте и интересен по большей части разработчикам. К счастью, летающие баннеры (типичный пример подгружаемых блоков) практически никогда не несут содержательной информации, относящихся к страницам сайта. Хотя можно представить, что таким способом авторы сайта могут иногда сообщать о собственных акциях.

Область применения

В данном виде подгрузчик страниц и блоков создан для работы в юзерскриптах — когда разработчики подгрузчика никак не связаны с разработчиками сайта. Поэтому им приходится узнавать об изменениях фронтенда сайта по факту изменений. Есть ли смысл использовать подгрузчик не как юзерскрипт, а как скрипт сайта?

Скорее, вопрос уже выглядит как риторический, с единственным вариантом ответа. В подгрузчике есть как минимум одна полезная для пользователей система, которая с успехом будет работать и не в юзерскрипте — это показ страниц в нескольких возможных представлениях. Пользователи выбирают свой стиль просмотра. Исходные стили страниц могут стать крайне бедными, потому что от них уже не требуется показа (разве что для поисковиков). Вся сложность показа уходит в подгрузчик, а для работы с сайтом мы (как разработчики сайта) получаем буфер подгрузки. Можем уже отдавать данные не страницами, а блоками, как в обычном аяксе. Не заботиться о представлении, если о нём позаботились в сфере действия подгрузчика. И в качестве бонуса имеем систему контроля изменений фронтенда со стороны подгрузчика, который может проверять целостность подачи данных. Если что-то забудется на уровне блоков, сигнал об отсутствии придёт от подгрузчика, в процессе тестов страниц.

Наконец, можно устроить показ тестового представления страниц, когда подгрузчик отображает не только пользовательские данные, но и все служебные, идущие на фронтенд — скрытые блоки, скрытые параметры, которые когда-то сработают, но при обычных фронтенд-тестах их легко пропустить или о них забыть. Впрочем, Selenium тоже умеет контролировать их, если тесты написаны, разница лишь в том, где писать тесты — в подгрузчике или в тестовой оболочке.

В результате, видим, что подгрузчик может работать как подсистема сайта, упрощая смену представлений страниц и лучше отделяя представление от данных, чем это делается при традиционной вёрстке и оформлении стилями. Слой данных сможем оформлять намного проще, без вёрстки вообще, если работать напрямую и только с AJAX-подгрузчиком. В этом да, ничего нового — все аякс-сайты так работают. Особенное в том, что вся забота о подгрузке собрана в единый центр, и весь вывод идёт через него. Прочие потоки данных будут нарушением и отклонением от паттерна.

Как же тогда с MVC-синхронизациями? Они могут жить как обычно, независимо от паттерна подгрузки, будучи теми самыми «нарушениями». Выглядеть это будет так. Паттерн подгрузки создал страницу представления. В ней некий «бекбон» (нарицательно) создал свой блок синхронизации, наладив связь с бекендом. Получается, что в конкретном представлении, одном из нескольких, создалась View-надстройка. Если меняем представление, нужно как-то пересаживать эту надстройку в новое View или возобновлять её работу. В самом деле, это — конфликт паттернов, который необходимо учитывать. Но это неизбежно, потому что оба паттерна работают с представлением, ничего не зная друг о друге.

В остальном, для лёгкости смены представлений и лучшего отделения вёрстки от данных, стоит пробовать этот паттерн как подсистему сайта.

Как же верстальщики?

Случаи, когда скрипты активно вторгаются в фронтенд, всегда сопровождаются усложнением работ по вёрстке. Если используют JS-шаблонизаторы, то некоторая, меньшая часть разработчиков гордо заявляет: «нам верстальщики не нужны», остальная часть молчит, понимая, что прибавили себе работы.

Для упрощения вёрстки паттерн подгрузки страниц сохранил традиционные HTML+CSS оформления блоков шаблонов. Верстальщик может работать с блоками и стилями для них, не касаясь остальной джаваскриптовой начинки паттерна. Если нужно сделать новое представление страниц (новую вёрстку, «натянуть» новый дизайн), берём все имеющиеся у нас в проекте блоки, смотрим, какие из них относятся к данной странице. По очереди меняем шаблоны и стили каждого блока. Все блоки проектируются так, чтобы их вёрстка не зависела (или почти не зависела) от верстки внешних блоков, за исключением управляющих общих классов CSS. Обычно, для этого достаточно не включать чужих классов в стили и описывать стили своих внешних элементов HTML в каждом блоке. «Концепция независимых блоков», в терминах Яндекса.

Для наблюдения результатов надо иметь ряд тестовых страниц. Они формируются из блоков данных, похожих на реальные, и включающие разные тестовые случаи показа. В случае юзерскриптов такие данные будут специальными тестовыми блоками, на которых верстальщик отрабатывает представление страниц. Смотреть такие блоки удобно на тестовом домене или через подмену реального домена локальным (файл hosts в Windows, /etc/hosts в Linux). Данные блоков оформляются как простые статические файлы.

Преимущество тестовых страниц для юзерскриптов в том, что если сайт временно недоступен или сменил дизайн, работа с тестовыми страницами не прерывается, а в случае смены дизайна примеры прежних собственных дизайнов и вёрсток остаются доступными.

Для сайтового скрипта паттерна-подгрузчика тестовые страницы позволяют хранить коллекцию дизайнов также независимо от состояния бекенда. В бекенде для этого нужно предусмотреть (лучше средствами фронтенда) переключение на статические тестовые страницы и их версии.

Что, если нам нужен JS-шаблон?

Если HTML-шаблона для парсера недостаточно (например, шаблон нужен в JSON, так как он модифицируется параметрами настроек), то верстальщик будто бы теряет возможность вёрстки в привычном окружении. Конечно, его можно немного научить работать с JSON вместо HTML, но вполне возможно, что у каждого человека от этого увеличится количество ошибок вёрстки. Лучший вариант (не проверенный в данном приложении), вероятно — делать конвертор шаблонов JSON-HTML на JS, чтобы работать с тем же привычным окружением.

Как это работает?

Попробуем описать принципы работы паттерна, показывая главные части кодов.

Построим шаблоны HTML для парсинга и переписывания статьи в новое оформление — в собственную вёрстку статьи, похожую на вёрстку сайта, но не обязанную быть точно такой же. Она содержит некоторые управляющие атрибуты, поэтому она — не просто шаблон для вывода блока (в данном случае — статьи с комментариями), а имеет ряд пользовательских функций.

Одним шаблоном дело не ограничится, потому что нам нужна отвязка своего шаблона от страницы сайта. Шаблон для парсинга — повторяет элементы сайта и служит для контроля и извлечения информации, а шаблон для показа — для создания HTML-блоков представления данных (в данном случае, статьи с комментраниями). Но для простоты покажем примерный вид шаблона, работающий для того и другого. Мы даже можем сделать его единым, но это — нарушение подхода, так как прибавит работы в погоне за изменениями сайта.

Шаблон отображения, следовательно, иногда может требовать коррекции — исключения ненужных для данного представления тегов. Но можно делать такие блоки невидимыми с помощью стилей или прятать данные в атрибуты тегов. Для иного варианта вёрстки можем построить иной шаблон при необходимости.

Показать код (комментарии не обязательны, приведены для пояснений)
<div class="post">
    <div class="published">
        <!--дата публикации-->
    </div>
    <h1 class="title">
        <!--заголовок (и некоторые признаки статьи - тип, ссылка)-->
    </h1>
    <div class="hubs">
        <!--разделы-->
    </div>
    <div class="content">
        <!--тело аннотации-->
        <div class="clear"></div>
    </div>
    <div class="btnBack" style="display: block;">
        <i>← Свернуть</i>
        <div class="percent">
            <div class="gPercent"><div style="width:<!--графическая доля аннотации-->px"></div></div>
             <!--доля аннотации по отношению ко всей статье-->,
            <i><!--дата парсинга--></i>
        </div>
    </div>
    <div class="content c2">
        <!--тело статьи-->
        <div class="clear"></div>
    </div>
    <div class="btnBack n2" style="display: block;">
        <i>← Свернуть</i>
    </div>
    <ul class="tags">
        <!--теги-->
    </ul>
    <div class="infopanel">
        <!--панель данных статьи (автор,избранное, оценки, ссылка на комментарии, ...)-->
        <div class="voting">
            <a href="#plus" class="plus" title="Нравится"></a>
                <div style="position: relative;" class="mark">
                    <a class="score" title="Посмотреть результат" href="#">—</a>
                </div>
                <a href="#minus" class="minus" title="Не нравится"></a>
            </div>
            <div class="pageviews" title="Просмотры"><!--число просмотров--></div>
            <div class="favorite">
                <a class="add" title="Добавить в избранное" href="#"> </a>
            </div>
            <div class="favs_count" title="Количество пользователей, добавивших пост в избранное"><!--число избранного--></div>
            <div class="author">
                <a title="Автор текста">
                    <!--Автор-->
                </a>
                <span class="rating" title="рейтинг пользователя"><!--рейтинг--></span>
            </div>
            <div class="informative">
                <a title="подгрузка комментариев">☑</a>
            </div>
            <div class="showComm btnBack inln">→</div>
            <div class="published"><!--дата--></div>
    </div>
    <div class="showComm btnBack" style="display: block;">
        <i>← Свернуть</i>
    </div>
    <div class="comments_list">
        <h2 class="title">
            <!--Заголовок комментариев-->
        </h2>
        <!--Комментарии-->
    </div>
    <div class="showComm btnBack n2" style="display: block;">
        <i>← Свернуть</i>
    </div>
</div>

Используя такой шаблон везде, избавляемся от волатильности обычных похожих друг на друга шаблонов сайта:
*) статья,
*) вопрос-ответы,
*) статья Песочницы,
*) подгрузка статьи из поиска,
*) подгрузка из списка избранного.

Например, формат вопросов и ответов будет соответствовать формату статьи. Правда, для полного соответствия надо перестроить сами ответы, это — другой этап парсинга, а можно оставить как есть и зависеть от текущей вёрстки.

Когда требуется зависимость от представления? В случае длинных статей блоки статьи требуют показа заголовка, даты и автора как минимум в 2 местах — вверху и внизу, а лучше — вообще отслеживать положение длинных текстов, и когда автор не виден, показывать его во всплывающем окошке в пределах окна, как и название статьи, и дату. Удобство возрастёт на порядок, а его часто не делают на сайтах — ведь это — дополнительные расходы на программирование. Со встроенным шаблоном представления можем сделать нужную логику на скрипте и стилях один раз на все похожие блоки сайта и даже на другие похожие сайты. Если статью подгружаем в страницу с несколькими блоками-статьями, то содержимое всплывающего окошка меняется в зависимости от контекста.

Для начальной работы с таким скриптом в шаблон загружаются данные об авторе и дате в 2 местах — перед и после статьи. Если статья короткая, меньше размера окна, то два поля не нужны, скрипт скроет одно из них. Если статья больше, то скрипт заносит данные в плавающее окно на моменты невидимости их в основных местах.

А иногда в формате вопросов-ответов встречается уточнение вопроса. Если мы парсим уточнение вместе с вопросом — нет будущих проблем, кроме того, что опять же, зависим от вёрстки сайта. Если парсим отдельно, в нашем шаблоне появляется блок «Уточнение вопроса», который всегда пустой, кроме случаев вопросов с уточнением. Сейчас нам лекго сделать его невидимым, нулевой высоты. Но если это было бы невозможно, то потребовалось бы удаление этого блока или специальный класс qa для правил отображения.

Парсер

Впрочем, синтез текстов на клиенте никогда не бывает сложным и требующим разъяснений. Гораздо интереснее пояснить, как проводится анализ страниц, парсинг. Уже есть где-то написанные библиотеки, но интересно и самому сделать велосипед, а затем сравнить сложность ядра парсера с имеющимися аналогами. При том, что в собственном ядре не будет мусора, остальной уровень качества определяется временем на написание и глубиной анализа сущностей задачи.

Что нам нужно от парсинга? В простом случае, из страницы надо выделить несколько текстовых объектов. Если бы это были наши страницы с сервера, лучше вообще обмениваться по JSON и передавать все необходимые сущности (объекты). Поскольку рассматриваем парсинг простых чужих HTML-страниц, то анализировать придётся текст (HTML с возможными ошибками, т.е. не XHTML).

Например, в старом имеющемся парсере выделялось 2 объекта — текст статьи и список комментариев. Это делалось 2 регулярными выражениями (теоретически, можно и одним) и за более чем год использования править парсинг пришлось один раз, когда изменились строки, участвующие в парсинге за счёт авторов сайта. Чуть позже понадобилось делать парсинг тегов — ключевых слов при статье. Вот пример первого регулярного выражения с парсингом статьи и тегов.

var conte = this.responseText.match( // ====== парсинг страницы, шаблон ======
    /<div class="content html_format">([\s\S]*?)<div class="clear"><\/div>\s+?<\/div>[\s\S]+?(<ul class="tags">|<div class="tags">)\s*([\s\S]*?)\s*(<\/div>|<\/ul>)[\s\S]*?<div class="infopanel"/m) //вся статья (до тегов или подписи)

Таким способом за несколько распознаваний исходного текста, не очень быстро, но можно выделить несколько необходимых нам обычно объектов. И этого обычно достаточно — не надо делать полного анализа DOM, чтобы затем взять его кусочек.

Если будет желание ускорить распознавание, общий блок текста можно резать на куски и затем анализировать куски. Способ дедовский и медленный, но не требует продвинутых библиотек и может работать годами на одном сайте.

Если понадобится ввести лучший уровень парсинга, придётся поработать ещё над этой частью или поискать сторонние решения. Например, здесь следующее, что захочется (после вытаскивания оценок и параметров из инфопанели, что быстро) — разобрать дерево комментариев. Причем, чтобы не делать разбор каждый раз заново для разных сайтов, неплохо бы написать общую процедуру. Затем мы сможем или хранить комментарии компактно, или оформить их все в дерево со своей вёрсткой, что лучше для оформления.

Когда встанет задача полного контроля элементов блока (чтобы сразу узнать, не появилось ли что новое в верстке), тогда понадобится полный парсер тегов в строке. Но пока её благополучно отложим на будущее.

Разные шаблоны для парсинга и синтеза

При отображении собственного представления блока мы решаем очень похожую задачу по сравнению с парсингом. Но, всё же, это разные задачи, если учитывать правило независимости своего представления от исходного. Будет иметься один шаблон для парсинга — и другой (или другие) — для построения представления. В этом — принципиальное отличие от подхода типа «юзерстили+ немного юзерскрипта», в котором стараются совместить оба довольно похожих шаблона, настолько, что показ самих шаблонов в коде становится не нужен — исходный шаблон — это блок с сайта, конечный шаблон — он же, немного модифицированный скриптами, а все визуальные отличия накладываются стилями.

Шаблон при таком подходе (юзерстили) оказывается невидимым в коде вообще. Он, как бы, растворён в приходящих данных, незримо присутствует и довольно очевиден лишь создателю скрипта, и то, если с ним идёт постоянная работа. Поэтому всякое мелкое изменение исходного (чужого) кода страницы сильно сказывается на всех последующих результатах — скрипты могут перестать работать; чтобы они не создавали ошибок, нужно пользоваться техникой безопасного анализа данных, с предусмотрением любых ошибок в них; стили исказятся и страница будет выглядеть ошибочной. Если учесть, что есть разные браузеры, нормальность отображения в них тоже трудно поддерживать — каждое изменение исходной страницы может сломать отображение в любом месте.

Если мы разделяем представления для анализа и синтеза блоков, разделяются и сложности, не связанные между собой. Задача структурируется, сложность решения уменьшается. Ошибки анализа (парсинга) остаются при себе, и самое большее, что они могут сделать — отсутствие части или всех данных, ранее извлекаемых со страниц. Отображение блоков становится зависимым только от своей вёрстки и, возможно, от наполнения её данными.

Поэтому не будем удивляться наличию 2 или более похожих шаблонов в получающемся скрипте. Один — для парсинга и верификации (анализ блока сайта), другой — для построения собственного представления блока. Третий и далее — это возможные другие представления тех же самых данных, которым нужны свои шаблоны HTML и правила CSS.

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

Технически, есть смысл подключать разные вёрстки как модули основного скрипта. Если основной скрипт видит модуль вёрстки, он включает его как альтернативный или как основной, в зависимости от своих настроек.

Настройка блока в случае юзерскрипта

Если у нас нет инструмента работы с сервером для отладки скрипта, используем в полной мере возможности джаваскипта в режиме проектирования и отладки. Хотим поместить заготовку шаблона внутрь готовой страницы? Просто пишем скрипт вставки шаблона в нужное место DOM страницы.

var tpArtic ='...шаблон...';
var rotPosts ='...блок перед футером...';
if(rotPosts)
    ...поместить шаблон перед блоком перед футером...;

//(Здесь намеренно не указан код, чтобы не объяснять долго внутреннее строение его процедур)

Увидев шаблон в странице, наполняем его тестовым содержимым вручную и верстаем так, чтобы он не зависел от стилей обрамляющей страницы. Затем скорректированный шаблон и стили используем по прямому назначению.

Если стили и шаблон предполагается заменять на другие, устраиваем внедрение и удаление правил CSS по настройкам скрипта.

Действующие блоки на новостном сайте

Так уж получается, что основной шаблон, который нас интересует для отображения данных на новостных сайтах, описывается одним шаблоном, который можно назвать «статья с комментариями». Есть ещё данные о пользователях и специальные настройки сайта, но эти шаблоны значительно менее интересны для наблюдения. Поэтому, сделав хотя бы один шаблон такого рода, мы сможем читать через него любые новости и статьи, укладывающиеся в него и преобладающие на порядка 90% сайтов. Сбывается мрачное пророчество А.Лебедева в том, что «сайты потеряют лицо», начнут терять хлеб тысячи дизайнеров, вся их надежда остаётся на пенсионеров, которые ещё не научились парсить новости скриптами. Если специально не позаботиться в шаблоне о поле «Источник новости», то сама новость потеряет лицо, точнее, ту часть, которая отвечает за показ её посредника.

Но пока мы находимся на одном сайте, этот ужасный апокалиптический сценарий ещё не действует, и можно спокойно искажать чистое дизайнерское вИдение сайта-источника — пользователь-то знает, что находится на одном сайте.

Скрытые силы

Сделав парсер-синтезатор блоков, мы получаем ещё один источник силы собственного клиентского приложения. Мы отвязались от монолита вёрстки, которым кормят обычных посетителей сайта. Обычно в вёрстку сайта вмешиваются лишь резальщики рекламы. Здесь же мы получаем структуру полезных нам блоков в деталях. Можем сохранить её в машинном JSON-формате, отправить на сервер архивирования и поддержки на период неактивности основного сервера. В общем, получаем всё, что делают обычные RSS-парсеры или обычные, но обычно гонимые парсеры страниц. С рекламой сайта — тут мы, конечно, касаемся другого вопроса, не чтения информации, а модели заработка сайтов через показ рекламы. Перестроив модель, можно решить такие вопросы. Скорее всего, это дело не очень отдалённого будущего, когда реклама в простых страницах станет ещё хуже работать. Но сейчас не будем заниматься придумыванием или поддержкой модели монетизации, а просто подумаем об информации, которая из единственного сидера (сайта-источника) может прерваться.

Тут на помощь приходят многочисленные трудолюбивые копировщики, которые сами не прочь нажиться на сторонней рекламе, показав в центре свежий и полезный контент, и кеши поисковиков, которые на период 1-2 недели сохраняют полезное содержание страницы, делая это с некоторой периодичностью — час-часы. Поэтому запоминают они не самое свежее последнее увиденное состояние до того как страница перестала быть доступной (сайт «лёг», страницу удалили, переписали, сократили) и всегда, естественно, смотрят на сайт глазами гостя, неавторизованного пользователя.

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

Если начальный сидер работает, запасные сидеры просто накапливают контент (без вёрстки и рекламы). Их роль начинается в те редкие моменты, когда начальный сидер перестал работать. Вот такая скрытая сила лежит в механизме клиентского парсинга исходных страниц. чтобы она начала проявляться, нужна, конечно, доработка модели и передача полезных данных на дублирующий сервер.

Распарсить собственную страницу

Представим, что мы уже скачали страницу, на которой находимся и хотим её распарсить. Для того, чтобы переписать её в собственной вёрстке или извлечь данные, чтобы получить полное собственное представление о страницах сайта, не нужно повторно скачивать собственную страницу (по URL текущей страницы), если достаточно использовать document.body.innerHTML или более глубоко лежащую содержательную часть страницы. Надо помнить лишь о том, что почти все браузеры урезают innerHTML в пределах своей компетенции, переставляют местами атрибуты тегов и добавляют иногда свои атрибуты. Но в результате получается достаточно читабельный для парсера текст, который мы уже научились парсить на прежнем шаге решения.

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

Результаты

Теперь этот шаблон и способ его заполнения позволяет размещать экземпляр блока «статья-комментарии» где угодно, даже на пустом месте своей страницы. Если раньше экземпляры размещались только в 5 случаях наличия заготовок блока (на месте аннотаций), то теперь под 1 из 3 статей футера тоже можно подгружать статьи. Конечно, с некоторой подготовкой кода — поместить кнопку активации подгрузки, указать место разворачивания статьи. Со старыми скриптами это тоже можно, но старые скрипты хуже читались, хуже приспособлены для развития, неструктурированы (труднее для чтения и развития), требовали бы большего кода для подготовки.

Наконец, этот код, названный нами паттерном подгрузки блоков, может быть использован в иных местах, как описано в первой половине статьи. Его нельзя взять как процедуру, это в прямом смысле паттерн, т.е. шаблон, по которому реализуются похожие решения. Он немного похож на очень сложный сниппет — кусок кода, который можно перенести и приспособить для работы в другом месте, где требуется та же сумма задач.

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

Теперь он занимает больше строк кода, но функции расширены, код стал прозрачным (для автора) и управляемым. Часть данных вынесена в шаблон, а до этого они генерировались из кода джаваскрипта.

Большее ожидается от всей смены подхода — вместо мелкого кодинга и сражения с ветряными мельницами различных состояний страницы получаем иную систему и отвязку от образа мышления кодировщиков исходного сайта.