Всем привет!  Меня зовут Милена Подлевских, я фронтенд-разработчик в команде Топ-100 Rambler&Co. В этой статье  расскажу про наш продукт – Счётчик Топ-100, о том, как он устроен и работает, о некоторых сложностях, с которыми мы столкнулись во время его разработки и поддержки, а также поделюсь дальнейшими планами нашей команды по развитию продукта. Статья написана по мотивам  моего выступления на митапе RamblerMeetup&Frontend, его можно посмотреть по ссылке: Rambler&Co – крупнейший в России медиахолдинг по размеру ежемесячной аудитории (свыше 168 млн пользователей), и мы также активно развиваем направления данных и технологий. Основная экспертиза по данным сосредоточена в UserModel, который входит в департамент рекламных технологий Adtech. В UserModel на основе полученных данных ежедневно строятся около 10 тысяч аудиторных сегментов — групп пользователей по интересам. Каждый день обучается и применяется более 800 моделей машинного обучения, на основе чего UserModel содержит около 500 млн профилей пользователей. Для построения как рекламы, так и технологий на данных нужно уметь их собирать, и эту задачу как раз и решает Топ-100. Из наших продуктов я выделю самые основные: Первое, о чём стоит упомянуть, – это старейший каталог Рунета. Он представляет собой списки сайтов, разделенных на категории, а также их рейтинг по этим категориям и подкатегориям. Самым главным продуктом является Счётчик Топ-100, который собирает и отправляет все необходимые данные. На изображении выше представлен код Счётчика Топ-100, который необходимо установить на сайт для подключения нашей системы аналитики. Для просмотра собранных данных есть интерфейс – Статистика Топ-100. В нем данные сгруппированы по отчётам и строятся по рассчитанным метрикам, для удобства анализа данных имеются также различные графики и фильтры. Топ-100 – это достаточно крупный проект. В Рейтинге Топ-100 участвуют порядка 74 тыс. сайтов. У нас есть около 3,8 млн установленных активных счётчиков, что дает нам 200 млн просмотров за сутки и охват аудитории в 25% от всех пользователей Рунета. Давным-давно, в 1997 году, был выпущен первый Каталог сайтов и Рейтинг Топ-100, с чего и началась история нашего проекта. В 1998 году появился первый счётчик-картинка, позволяющий уже получать кое-какую статистику. Вплоть до 2016 года проект развивался и частично переписывался, а в 2016 году был осуществлён перезапуск проекта, в процессе которого были полностью переписаны основные продукты. В 2017 году было проведено много улучшений и доработок, появился первый отчёт в реальном времени, а уже в 2018 году все основные отчёты стали строиться в режиме реал-тайм. В 2019 году из новых основных фич появилась возможность скачивать отчёт, а в 2020 году Топ-100 вошел в состав департамента рекламных технологий AdTech, где началось более плотное развитие, вследствие чего было произведено много доработок, появился Демо-сайт для новых пользователей, был обновлен Каталог, добавлены фильтры в Статистику и начался большой рефакторинг переписывания всея Статистики на TS. Сейчас, в 2021 году, мы продолжаем расти и развиваться, делаем много внутренних улучшений и новых доработок продукта, в общем, и стали ещё плотнее заниматься Счётчиком Топ-100, про который я подробно и расскажу в своём докладе. На рынке есть разные решения для сбора данных и аналитики сайтов, но для более детального анализа их не всегда хватает. Некоторые крупные компании при реализации своего решения могут столкнуться с разными проблемами и сложностями. Поэтому на примере уже готового решения я расскажу, с чем столкнулись мы при его разработке и поддержке. Когда я пришла в Rambler&Co, честно говоря, долго не понимала, как устроен счётчик и как он работает, старалась не касаться этого продукта и брать другие задачи, потому что он казался мне тёмным, неизведанным лесом со своими тайнами.  На деле же оказалось не всё так страшно, и для такого решения как счётчик можно выделить основные моменты, которым он должен удовлетворять. Потребляет мало памяти: так как счётчик — это стороннее решение, которое пользователи (т.е. владельцы сайтов) устанавливают на свой сайт, то оно должно потреблять мало памяти, чтобы не ухудшать скорость работы ресурса. Мало весит: по этой же причине счётчик должен иметь малый вес, так как вес страниц влияет, например, на ранжирование сайта в поиске. Быстро работает: счётчик должен быстро работать, чтобы оптимально так же быстро собирать и отправлять данные. Не влияет на работу сайта и не мешает другим интерфейсам: такое решение не должно мешать работе сайта или же каким-либо интерфейсам, расположенным на этом сайте. Поддерживается в старых браузерах: это требуется для того, чтобы владельцы сайтов могли узнать, какое количество пользователей со старых сайтов к ним до сих пор приходят и нужно ли поддерживать для них старые браузеры. Поддерживает работу нескольких счётчиков на странице: несколько установленных счётчиков на одной странице должны корректно работать и не аффектить друг друга. Например, для спецпроекта помимо основного ставится отдельный счётчик, чтобы собирать данные только по этому спецпроекту. Если говорить про наш Счётчик Топ-100, то это небольшой кусок JS-кода, который пользователь ставит на свой сайт, и уже при инициализации страницы подгружается основной код Счётчика, осуществляющий  всю магию. Затем, если опустить все тонкости, Счётчик собирает данные со страницы и браузера и отправляет их дальше. В консоли разработчика можно увидеть эти отправляемые события. Наш Счётчик позволяет отправить порядка 1,5 ПБ сырых данных, которые затем хранятся некоторое время в виде логов. А инфраструктура хранит уже около 200 ТБ обработанных данных, собранных в целом за несколько лет. Всего мы получаем около 18 тыс. событий в секунду со всех активных счётчиков. Исторически сложилось так, что у нас существует несколько версий Счётчика: kraken/top100 — самая актуальная и разрабатываемая на текущий момент версия Счётчика, которая регулярно обновляется и поддерживается. Она появилась в 2016 году и используется по сей день. JSN — версия Счётчика на основе актуальной, отличается лишь тем, что генерирует событие просмотра страницы без использования синхронной инициализации. Счётчик-пиксель — по сути является способом отправки просмотра страницы за счёт подгрузки пикселя на сайт, используется при отключенном JS на странице. tesla pack.min — старая версия Счётчика, не разрабатывается, исходники хранятся в репозитории. Версии Счётчика также различаются по синхронной и асинхронной загрузке. Синхронная — это когда сначала грузится скрипт и только потом — инициализация, асинхронная — это когда время инициализации не зависит от времени загрузки скрипта, то есть скрипт может загрузиться и после инициализации. Под инициализацией подразумевается пользовательский код, который содержит вызов конструктора Счётчика. Все версии Счётчика, кроме JCN, могут работать и синхронно, и асинхронно. Помимо различных версий есть ещё версионирование актуального счётчика Kraken. Ниже на скрине мониторинга можно увидеть количество запросов за основным кодом Счётчика по разным его версиям.  Цветами выделены различные версии счётчика Kraken, и можно заметить, что смена цвета обозначает выкатку новой версии и является показателем того, что с течением времени площадки, на которых он установлен, отправляют запросы уже за самой актуальной версией счётчика. Также можно заметить небольшую зелененькую полоску снизу – это как раз запросы старой версии счётчика, указывающие на то, что некоторые сайты до сих пор ходят за ними. Поэтому мы не можем просто спилить и перестать их поддерживать, так как практически невозможно обойти всех владельцев сайтов и попросить их актуализировать версию. Для таких площадок нам необходимо хранить и отдавать код старых счётчиков по запросу. Отправку данных в виде событий со Счётчика можно представить в таком упрощенном виде:  Сам Счётчик со страницы отправляет собранные данные и события на Nginx Kraken, который затем передаёт эти логи в Kafka и в Hadoop для дальнейшей обработки. Расскажу немного про архитектуру Счётчика Топ-100. Он написан с использованием фреймворка Google Closure Library. Код сжимается с помощью Clojure Compiler. Именно такой стек позволяет достичь наименьшего размера, по сравнению с другими решениями, и за счёт этого получить вес в 142 КБ для подгружаемого кода счётчика. Также используется чистый JS с стандартом ES5 и сборщик Gulp. В дальнейшем мы планируем переписать на Docker + Webpack и обновить на стандарт ES6. Основной момент архитектуры – счётчик состоит из модулей. Существует набор базовых модулей, которые всегда присутствуют в счётчике, а также модули, которые подгружаются при необходимости отдельно. В базовые модули входят: модуль инициализации, ядро, транспорт и другие, а из дополнительных подгружаемых можно выделить модуль по аналитике блоков (Usability), модуль коммерции (Ecommerce) и модуль для медиа (Media), который мы сейчас разрабатываем. Модуль инициализации — создает экземпляр счётчика, контролирует синхронный или асинхронный код инициализации, который должен выполняться на странице, а также регулирует работу нескольких счётчиков на странице. Ядро — является центром всех счётчиков на странице. Важно: даже при нескольких счётчиках на странице есть только одно ядро. За счёт этого все данные собираются только один раз и уже затем отправляются на все счётчики на странице. Ядро контролирует и подключает необходимый функционал и модули, проксирует запросы от одного модуля к другому, а также контролирует очередь событий, в которой собираются запросы с весами приоритета. Транспорт — занимается отправкой данных на сервер. Выбор метода зависит от типа события. Запросы могут отправляться тремя способами: Beacon — этим способом отправляются данные, для которых критична скорость отправки. Это могут быть внешние переходы и пользовательская активность. Например, уходя со страницы отправляется запрос через beacon, так как он асинхронный. POST — пост-запросами отправляются показы блоков, клики по ним, e-коммерция. Также если beacon недоступны в браузере, то пост-запросами отправляются и события перехода по внешним ссылкам, собранные на странице ссылки и активность пользователя. Также на POST могут автоматически переходить события pv с очень длинными параметрами, не умещающиеся в стандартную длину GET-пакета (около 2000 символов, зависит от возможностей браузера). GET — всё остальное отправляется гет-запросами. Также всё отправляется гет-запросами, если браузер не поддерживает beacon и кросс-доменные пост-запросы. Какие данные собирает Счётчик? Во-первых, это данные, наиболее полезные для аналитики сайтов. Во-вторых, важно учитывать, что мы наблюдаем только за взаимодействием пользователя с контентом и собираем данные именно об этом, а не о самих действиях пользователя. Для начала нужно рассказать, какие источники данных поддерживает Топ-100: Web — десктопные и мобильные браузеры, именно на них работает наш основной JS-счётчик. В остальных случаях мы собираем статистику с помощью других решений. AMP/Yandex Turbo — на таких страницах не работает JS, и тогда мы живём по правилам Google и Яндекса. Мобилки — на текущий момент можем получать готовые данные через запросы к серверу, но есть планы по более подробному сбору данных с таких устройств. ТВ — также получаем данные через запросы к серверу. Исходя из источников для нашей аналитики мы можем получать уже вот такой набор данных по используемым технологиям: Устройства — мобилки/десктоп/планшеты/другие, даже в разрезе устройств. Браузеры — версии браузеров. ОС — Windows/Mac OS/Android/другие, с версиями. Разрешения экрана. Все эти данные получает сервер из логов запроса, например, из User-Agent, который устанавливается в header’е. Установленные куки позволяют идентифицировать пользователя. При запуске счётчика мы проверяем, есть ли кука, если нет, то ставим её. С её помощью мы можем определить, был пользователь на сайте или нет. Top100_id — наша first party кука. Все собранные данные отправляются на сервер с разными типами событий. Одной из самых важных метрик являются просмотры, т.е. сколько раз была показана страница. Это событие отправляется при инициализации счётчика (для SPA есть дополнительные методы), а сами данные этого события отправляются в query-параметрах запроса. Событие называется pv и поддерживает 24 параметра, некоторые из которых обязательны и должны присутствовать всегда. Подсчёт пользовательской активности делится на две составляющие: подсчёт времени на странице и распределение этого времени по активным скроллам. Подсчётом времени занимается модуль активности. Время учитывается, если пользователь проявлял какую-то активность на странице (клики, движения мышкой, скролл страницы, тачи, нажатия на клавиши), и если страница активна в данный момент (то есть при переключении вкладки время не считаем). Стоит также учесть, что после своей последней активности пользователь ещё 30 секунд считается активным.  Подсчитанные время и скроллы отправляются beacon перед уходом пользователя со страницы с помощью события act. Если нет возможности в текущем браузере следить за видимостью страницы и отправлять beacon, то активность может считаться пингами с увеличенными интервалами до 20 минут.  Активные экраны определяются с помощью разделения документа на старте на 10 частей. Так мы получаем высоту одного нормализированного участка страницы H (H = высота документа / 10). Дальше все размерности страницы (скроллы, высота документа и другие) будут измеряться этой величиной. В итоге мы всю страницу можем представить в виде последовательных блоков высоты H. При подсчёте активности смотрим, какие именно участки страницы были активны, и записываем показатели в них. При этом при увеличении первоначальной страницы логика не меняется, мы лишь расширяем список всех блоков страницы. Если высота экрана больше высоты документа (страница занимает не весь экран), то мы шлём дефолтные 10 блоков. Они позволяют анализировать трафик, пользовательские переходы по ссылкам на сайте, а также позволяют отследить, куда дальше ушёл пользователь. Внешние переходы имеют большой вес в очереди событий, и их обработка модулем происходит только при клике на любую ссылку. Отправляемое событие называется extln. Аналитика блоков является одной из особенных разработок в нашем Счётчике и  позволяет проанализировать показы и клики определённых, размеченных дата-атрибутами, блоков. Показы и клики считаются при инициализации модуля, а также через 1, 2, 4, 8 секунд, для того чтобы учесть блоки, которые подгружаются динамически. При этом уже посчитанные блоки учитываться не должны. Все данные по аналитике блоков отправляются с помощью событий bv и cl. Чтобы лучше понять, как работает аналитика блоков, стоит рассмотреть её принцип работы на примере: В качестве примера рассмотрим вот такую панель навигации. Так как модуль Usability подключается и подгружается по необходимости, в коде для подключения счётчика нужно указать значение родительского параметра дата-атрибута: Разметка дата-атрибутами осуществляется очень просто: указываются значения для родительских и дочерних элементов. Таким образом, при инициализации модуль юзабилити Счётчика пройдет по коду страницы, соберёт все дата-атрибуты с параметром, указанным в коде загрузки Счётчика, в древовидную структуру данных (родитель-ребёнок). Далее Счётчик отправит событие bv со всеми собранными деревьями. Также он будет отправлять всю ветку дерева от ребёнка к его родителю при клике на дочерний элемент.  Нужно учитывать, что если один элемент размечен двумя разными атрибутами, он отправится дважды для каждого атрибута. Отчет по Аналитике блоков можно посмотреть у нас в Статистике Топ-100, где видно уже разобранную структуру вложенных элементов, и отображением количества показов и кликов на блоки, а также со значением CTR – отношением количества показов блока к кликам – это важный параметр для аналитики блоков. Данные на этом скрине с тестовой странички, специально разработанной для этого доклада, поэтому они могут быть не совсем точными. Еще одной фичей Статистики Топ-100 является поддержка разбиения отчётов по сплитам. Сплиты – это разделение всего трафика пользователей на сайте или страницах на части для возможности проверки какой-либо гипотезы. Самый простой пример – синяя и красная кнопки. 50% видят синюю, 50% – красную. Чтобы проанализировать, какая кнопка лучше для страницы, можно использовать аналитику Топ-100. Для этого нужно передать в параметр split кода загрузки Счётчика название сплита.  Далее этот параметр будет отправляться при инициализации Счётчика, и затем в отчетах Статистики Топ-100 можно выбрать интересующие сплиты и сравнить статистику по ним, чтобы выбрать лучший вариант. Я рассказала лишь про несколько событий и параметров, отправляемых со Счётчика. Всего же он поддерживает в сумме 63 параметра для различных событий и 12 различных отправляемых событий. На основе этих отправляемых данных рассчитываются метрики и строятся отчёты в Статистике Топ-100. Некоторые данные напрямую преобразуются в метрики, например, показы – из pv, а некоторые рассчитываются на бэкенде, например, время на сайте или глубина просмотра. Всего из наших данных мы получаем 15 метрик, которые используются в 32 готовых отчётах в Статистике Топ-100. Так как Счётчик Топ-100 стоит на огромном количестве сайтов и выкатывать какие-то серьезные доработки и изменения на всех пользователей сразу весьма рискованно, мы разработали А/Б-тестирование на самом Cчётчике. Для этого мы используем конфиг, в котором указаны параметры эксперимента. Для каждого эксперимента выделяется количество сплитов в процентах. При инициализации Cчётчика идет распределение пользователя на все эксперименты, то есть он участвует везде. Но сами сплиты будут разные. Распределение пользователя по сплитам в эксперименте происходит на основании хэш-функции и должно быть одинаковым для одних и тех же project_id, exp_id, top100_id. Также распределение сплита идет на все Cчётчики Топ-100, установленные на странице. Значение сплита отправляется в параметре exp при отправке события со счётчика. Разработка и поддержка любого проекта и продукта никогда не проходит без препятствий и сложностей. Мы с командой тоже натолкнулись на несколько подводных камней. AMP/Yandex Turbo: как уже говорилось выше, на таких страницах нам приходится подстраиваться под Google и Яндекс для сбора и передачи данных. Мы улучшили покрытие передаваемых параметров из AMP и Turbo. До этого данные были неполными, например, в Turbo не передавался url, и мы не могли понять, на какой странице произошло событие. В AMP мы сами разрабатывали подключение, а в Turbo реализовывали сотрудники Яндекса без нашего участия, но по нашему запросу.Кроме этого, cookies не выставляются на страницах AMP и Yandex Turbo. Для некоторых сайтов это больший процент от общего числа просмотров. Adblock: блокировщики являются болью для многих продуктов, и мы не исключение. Они блокируют загрузку JS, и основной код счётчика не подгружается, из-за чего мы можем получать не все данные. В планах научить счётчик отправлять информацию о том, есть ли блокировщик на странице. Боты: генерят много трафика, мы учимся их распознавать, на текущий момент уже есть некоторые фильтры на бэке. Offline и потеря данных: самый частый кейс — это поездка в метро, когда сеть может пропадать, и нужно научится работать с этими моментами. Сейчас мы реализуем несколько подходов для решения этой проблемы, в числе которых дополнительные retry для событий и отправка данных асинхронно через beacon. ITP и cookies: Intelligent Tracking Prevention (ITP) по умолчанию блокирует все сторонние файлы cookie. Счётчик устанавливает нашу first_party cookie top100_id, а также мы ещё устанавливаем third_party cookie, которые скоро уже везде будут блокироваться :(  В команде Топ-100 мы активно развиваем Счётчик и Статистику Топ-100. У нас большие планы, множество задач и целей. Вот некоторые из них: Кастомные отчеты.  Планируем создать более кастомизированные отчеты, чтобы наши пользователи (владельцы сайтов) могли настраивать аналитику так, как им удобно, и собирать нужный отчёт из готовых кусочков, как Lego. Улучшение идентификации пользователей. Мы работаем над новыми способами идентификации пользователей и постепенно внедряем их, чтобы улучшить нашу аналитику. Inapp-аналитика. Есть цель — более подробно собирать аналитику из мобильных приложений и мобильных устройств в целом. Развитие инфраструктуры. Сейчас наиболее приоритетная цель. Чтобы снизить time to market, внедряем различные подходы, например такие, как ci/cd и feature подходы к выкаткам в прод, shift-right тестирование, a/b-тесты функционала и многое другое! Мой доклад и подходит к концу, что же можно выделить из всего вышесказанного? Какой вывод я сделала лично для себя: Счётчик – это не страшный тёмный лес с монстрами и тайнами. На деле всё оказалось не так сложно, все данные собираются с сайта или браузера и просто отправляются на сервер. Технологии и их выбор понятен, архитектура оптимальна и расширяема.  Но важные моменты есть у любого проекта и продукта. Для Счётчика – это параметры веса, быстродействия, возможности поддержки старых технологий и другие. Но это скорее вызов для разработчика: как оптимизировать ту или иную задачу. Также разработка такого решения даёт много путей для развития нашего продукта, так как есть огромное количество параметров, которые можно собрать, рассчитать и дать нашим пользователям новую аналитику. У нас есть большое количество задач, интересных идей и больших планов, так что если вам интересно, у нас открыто несколько вакансий в Топ-100, как и в других продуктах департамента рекламных технологий и Rambler&Co в целом! На этом у меня всё, спасибо огромное, что прочитали эту статью :) Подробнее с проектом Топ-100 можно ознакомиться на этом лендинге, где есть все необходимые ссылки. Новости по нашим продуктам Топ-100 вы можете читать в нашем телеграм канале. Ведущий инженер по разработке