Отслеживаем действия пользователя с помощью CSS

  • Tutorial

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


Также здесь мы рассмотрим метод, как получить поведенческую информацию от пользователей используя только HTML и CSS.


Возможно, после прочтения поста, вам покажется что я "изобрел колесо". Так и есть, методы описанные в этом посте не новы, и используют спецификации которые поддерживают практически все браузеры.


Так или иначе, эта информация поможет вам понять один нестандартный метод отслеживания поведения пользователей, который на данный момент нельзя "отключить" (в настройках) или заблокировать (плагинами вроде AdBlock или Ghostery).


Предыстория


Представьте на минуту, что у вас есть:


  • Аудитория с выключенным JavaScript, или установлены плагины вроде Ghostery
  • Желание отслеживать поведение пользователей

Прежде чем пытаться найти решение для этой задачи, давайте рассмотрим какими методами отслеживания мы располагаем на данный момент:


  • JavaScript жучки, такие как Яндекс.Метрика, Google Analytics — действуют на стороне клиента.
  • Анализаторы логов приложения, такие как logstash, awstat — оперируют логами приложения на сервере.
  • Статичные счетчик — как правило, загружают скрытую картинку, или другой ресурс, не требуют выполнения JavaScript кода.

JavaScript жучки не подходят исходя из требований. За исключением таких, которые идут в комплекте с статичным счетчиком. К примеру, жучок для Яндекс.Метрики загружает изображение следующего вида:


<noscript><div><img src="//mc.yandex.ru/watch/XXXXXXXX" style="position:absolute; left:-9999px;" alt="" /></div></noscript>

В случае, если у клиента не выполняется JavaScript, этот подход позволит получить такую информацию, как:


  • хиты
  • хосты
  • ip-адрес
  • время визита
  • … другие данные

Но есть одна проблема: вся информация носит статичный характер, то есть мы не получаем информацию о том, каким образом пользователь вел себя на странице.


Решение — используем CSS


В CSS мы можем загрузить внешний ресурс через инструкцию url(адрес-ресурса). Обычно этот ресурс загружается только тогда, когда он становиться необходим для рендеринга страницы. Почему бы не использовать эту особенность, для того чтобы собрать информацию о поведении пользователя? Мы вполне можем написать специальный CSS, который будет:


  • собирать данные о поведении пользователя
  • определять версии/особенности браузеров с помощью CSS-хаков

Итак, наша задача сводится к формированию HTML + CSS кода, который вынудит браузер, при взаимодействии с пользователем, сделать get запрос на наш сервер.


К примеру, мы желаем отслеживать клики по ссылкам. Для этого, мы можем использовать псевдокласс :active, а именно такой шаблон (jsfiddle):


/* <a class="spycss" href="https://google.com">spycss</a>*/
.spycss:active::after {
    content: url("/backend/click-google");
}

При клике на такую ссылку, мы получим GET на /backend/click-google.


Похожим образом мы можем использовать и другие псевдоклассы:


.spycss1:hover::after {
    content: url("/backend/hover");
}
.spycss2:focus::after {
    content: url("/backend/hover");
}

В обоих случаях мы принимаем GET на нашем сервере.


Ускоряемся вместе с SpyCss


Писать такой CSS вручную и отслеживать каждую ссылку — довольно проблемно и непродуктивно. Именно для этих целей я написал небольшую библиотеку SpyCss. С помощью нее можно генерировать отслеживающий HTML + CSS без особых проблем. К примеру, можно использовать такой код для генерации отслеживаемой ссылки:


<?php
// Идентификатор пользователя, полезен для State-less бэкенда
$userId = 'get_from_cookie--OR--fetch_from_db';

// Адрес бэкенда, куда прийдут GET запросы.
$backendUrl = 'https://spy-css-backend/'; 

$s = new \SpyCss\SpyCss($userId, $backendUrl);

// Создаем ссылку формата
// <a class="scsssXXXX" href="https://hcbogdan.com">hcbogdan.com</a>
echo $s->builder()
    ->tag('a')
    ->content('hcbogdan.com')
    ->attribute('href', 'https://hcbogdan.com')
    ->interactions([
        new SpyCss\Interaction\Active('click_on_hcbogdan_com')
    ])
    ->get();

// Библиотека генерирует CSS с необходимыми инструкциями
echo '<style>'.$s->extractStyles().'</style>';

Теперь мы можем отслеживать клики по ссылкам и наведения мышки на DOM элементы. Давайте посмотрим на HTML5 формы. А именно на валидацию (jsfiddle):


<form>
<input type="text" name="name" required />
</form>
<style>
.field:valid {
  background: red;
}
</style>

Получается точно таким же образом мы можем использовать псевдокласс :valid для отслеживания заполнения формы:


// Создаем поле <input type="text"  class="scsssXXXX" required />
echo $s->builder()
->tag('input')
->attributes([
    'name' => 'you_name',
    'value' => '',
    'required' => true,
    'placeholder' => 'Напишите текст',
])
->interactions([
    new \SpyCss\Interaction\Valid('you_fill_input'),
])
->get();

echo '<style>'.$s->extractStyles().'</style>';

Когда пользователь заполнит поле — мы получим свой GET запрос.


Мы также можем отследить как долго пользователь держал курсор над DOM элементом (который получил состояние hover) с помощью css-анимаций. Например:


@keyframes spycss {
  0% {background-image: url("/backend/0")}
  100% {background-image: url("/backend/100")}
}
.spycss:hover::after {
  animation: spycss 20s infinite;
}

Аналогичный пример с помощью библиотеки SpyCss (она добавит префиксы -webkit, -moz):


echo $s->builder()
->tag('a')
->content('hcbogdan.com')
->attributes([
    'href' => 'https://hcbogdan.com',
    'target' => '_blank'
])
->interactions([
    new \SpyCss\Interaction\Online('view_on_hcbogdan_com'),
])
->get();

echo '<style>'.$s->extractStyles().'</style>';

Итоги


Даже с выключенным или недоступным JavaScript жучком, у нас есть с помощью CSS:


  • отслеживать поведение пользователей,
  • определять некоторые версии браузера
  • определять примерные размеры окна и PPI
  • определить ориентацию и тип устройства

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

Поделиться публикацией

Похожие публикации

Комментарии 49
    +6
    Покажите мне современный проект у которого есть «Аудитория с выключенным JavaScript», пожалуйста. Существенная аудитория, а не 2.5 киберманьяка-параноика.

    Нашли? Отлично. А теперь чтобы сайт приэтом еще и не терял функциональности?

    Нашли, но не можете ответить? Включите JavaScript
      +2
      Вы в чем-то правы, как правило JavaScript включен у всех. Но существуют такие решения, которые работают без JS. Да и в целом — вы же не можете отключить или заблокировать CSS верно? Тогда весь сайт превратиться в простую разметку.

      Я не призываю отказываться от JS-жучков, а только заполняю пробел между «JS-жучками» и «статическими счетчиками» (которые до сих пор идут в комплекте с JS-жучками в теге ).

      С помощью методов описанных в статье вы сможете:
      1. Обойти плагины блокирующие JS жучки, или uncaught exception.
      3. Добавить еще один метод сепарации реальных пользователей от ботов phantomjs.
      4. Если, ввиду особенности проекта, у ваших пользователей нет JS — анализировать их поведение.
        0

        9 апреля эта система работать не будет.

          0
          Ни разу не видел это в действии. Выкинуть день из прибыли?
            +1

            Целых два дня. Праздник проходит 48 часов. Он для того чтобы показать чего ваш сайт стоит без CSS. Здесь чуть больше: https://css-naked-day.github.io


            Я периодически вижу сайты без css. Таблица стилей по каким то причинам не подгрузилась и получаем голый HTML. Причём и обновление иногда не помогает.

              0
              Надо будет попробовать найти такие сайты 9 апреля :)

              Правда я думаю, что сейчас они ничего интересного не покажут, т.к. табличной верстки уже нет нигде, а блоки без стилей — прямая линия вниз.
                0

                Потому что надо использовать block и inline элементы правильно.

                  0
                  Есть верстка на CSS-Grid, например.
                  Расскажите пожалуйста какие HTML элементы надо использовать, чтобы без CSS все осталось на своих местах?
                    0

                    Если вам очень нужна сетка даже в случае когда отвалился CSS. То тут только таблицы использовать.


                    Сайт может быть не столь красивым без CSS но должен сохранять необходимую функциональность. Тоже касается и Javascript. Очень раздражают сайты на которых просто пустая страница без Javascript. Что сложного в том чтобы показать мне текст который поисковик нашол на этой странице без Javascript и CSS?


                    Недавно наткнулся на сайт который без аналитики гугла не хотел мне файл давать скачать. Не учли программеры что кто то её заблокировать может и в консоль сыпались ошибки.

                      +2
                      > Сайт может быть не столь красивым без CSS но должен сохранять необходимую функциональность.

                      Зачем?
                        +1

                        Чтоб им могли пользоваться?

                          +1
                          Внезапно некоторые посещают сайты с браузеров, где JS/CSS не поддерживаются by design. К примеру, если у меня сломалась DE, я использую Links (либо Lynx). Причем регулярно. И с серверов тоже пользуюсь. Впрочем, есть и графические браузеры, не поддерживающие JS/CSS.
                          Для меня самое главное на большинстве сайтов — это их наполнение, а не свистелки на JS и украшательства на CSS.
                          Более того, у меня время от времени чешутся руки написать граббер, который будет собирать с интересных мне страниц и сайтов только текст.
                            0
                            Согласен с вами, но только отчасти. Я тоже иногда захожу через такие браузеры и тоже граблю контент через скрипты (прокси-скрипты для сайтов где нет rss), однако может вам нужен не сайт без css, а сайт с хорошей семантикой?
                            Ок — можно прожить без JS — я сам ругаюсь на такие сайты, однако мне их приходится делать, но вот CSS… боюсь что это необходимый минимум. Правда я за то чтобы анимации можно было отключать.
                              0
                              Анимации? А видео, встроенное в фон страницы?) Ради одного этого я готов уфигачить CSS в ноль или использовать кастомное CSS всегда и везде.
                                0
                                Видео вроде как не в css, или я отстал от жизни?
                                  0
                                  Отстали.
                                  www.myprovence.fr/snapshots2012/en
                                  www.clinicadrmauro.com.br
                                  Здесь у нас замечательная смесь CSS3, HTML5, video as background (трафик!).
                                  у пэйпола было одно время видео в фоне, было у фейсбука, кажется у Эппл было…
                                    +1
                                    Боюсь что вы не правы. Это просто контейнер video, а css тут только для выравнивания его по z-index'у и на нужной позиции.
                                      –1
                                      Значит, я был неправ. Что не отменяет моего желания найти и покарать)
                              0
                              чешутся руки написать граббер
                              Зачем изобретать велосипед? в некоторых браузерах (например, яндекс) есть такая функция как режим чтения.
                                0
                                Более того, у меня время от времени чешутся руки написать граббер, который будет собирать с интересных мне страниц и сайтов только текст.
                                Пишите плагины для weboob.
                              –1
                              Поисковики нынче и джс могут выполнять, и цсс анализировать.
                                0

                                Я про то что минимум текст страницы должен быть доступен в любом случае. Даже если поисковики умеют полноценно обрабатывать страницу.

                    0
                    Статья 2009 года. В то время, в эпоху табличной верстки, эта «шутка», может, и имела место: сайт становился «голым», но не терял свой, так скажем, скелет. Сейчас же, с блочной версткой, с адаптивом и со слоями, если снять CSS, сайт просто превратится в нечитаемую кашу.
                    0
                    Боты phantomjs как раз обрабатывают js)
                      0
                      Конечно, но в таком случае нужно получать hover / focus и другие псевдоклассы — нужно потрудиться чтобы написать такой сценарий.
                      Если триггеры не срабатывают — вероятно это бот.
                      0
                      Встречаются люди с расширениями User CSS / Stylebot / Stylish / etc, которые могут к чертям сломать такую аналитику. У меня есть знакомые, которые их используют.
                        0
                        Я использую, чтобы отключать вырвиглазные шрифты, но описанные в статье приёмы я вряд ли замечу и отключу, даже случайно.
                        Впрочем, в последнее время я просто отключил левые шрифты в ublocker-e, и стили давно уже не подправлял.
                      +3
                      тут, как мне кажется, вопрос про блокировку js способов различными плагинами.
                        +1
                        Старые форумы работают отлично без JS.
                          0

                          Есть расширения блокирующие скрипты по доменам.

                            0

                            Noscript

                            0
                              0
                              Да никто ж не спорит. А еще могут свет выключить в момент покупки пользователем Ламборджини, а их сайт этого не предусматривает, вот беда то.

                              Давайте ориентироваться на нормальных пользователей, которых большинство.
                            0

                            У меня к вам вот тут вопрос:


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

                            Вы же обрабатываете запросы на сервере. Уже как минимум отлавливаете переходы по своим ссылкам. События с формами также относятся к серверной активности.


                            Вариант с hover || focus мне не нравится потому, что я (как пользователь) смогу породить вам много паразитных запросов, например, зажав tab. Я думаю, что вашему серверу это не понравится, и мне как пользователю тоже.


                            Простите, писал с телефона, потому не форматировал текст.

                              0
                              Ссылка может быть внешняя и вы хотите знать кликают пользователи по ней или нет.
                              Сомневаюсь, что запрос будет происходить каждый раз при hover/focus. Скорее всего один раз на один элемент. И тут вы уже сами вольны распоряжаться на сколько элементов вешать такой обработчик.
                                0
                                Как правильно заметил MiXei4 это актуально для отслеживания как внутренних так и внешних ссылок.

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

                                По поводу «смогу породить вам много паразитных запросов, например, зажав tab» — насколько я тестировал, вы не сможете такого сделать, запрос приходит лиш один раз за сеанс (после загрузки страницы) повторный фокус или клик — не создает запросов.
                                0

                                Хм. А все эти url будут отрабатывать каждый раз или один раз скачаются и осядут в кэше?
                                Хотя, наверное, можно Cache-Control выставить...

                                  0
                                  Есть некоторая особенность в обработке таких запросов.
                                  Прежде всего все загрузки url(x) будут происходит один раз за сеанс (во множестве браузеров так). Другими словами, вы:
                                  1. заходите на страницу spycss.hcbogdan.com
                                  2. наводите курсор на ссылку google.com
                                  3. на сервер отправляется GET
                                  4. потом повторно наводите курсор на ссылку google.com
                                  5. повторных запросов нет

                                  Только если вы обновите страницу, или откроете в другой вкладке, вы сможете пройти путь снова с пункта №1.

                                  Конечно нужно учитывать кэширование браузера, например можно использовать такие заголовки — github.com/Bogdaan/spycss-demo/blob/master/src/controllers.php#L181
                                  0
                                  Как думаете, а каким образом можно было бы с помощью CSS вытаскивать данные из форм? Например, из форм заказов на сворованных конкурентами лендингах. Существует ли вообще такая возможность?
                                    0
                                    Хм… Ответ зависит от того, в каком состоянии находиться форма.
                                    Допустим, если у вас есть форма в которой сервер заранее устанавливает какие-либо значения (исходя из данных пользователя). В таком случае вы сможете загрузить эти данные к себе используя CSS.

                                    Например, таким образом: jsfiddle.net/hcbogdan/1wdky4t6/1
                                      0
                                      Определенно, существует угроза «утечки» данных. Некоторую информацию вы можете найти в статье: создаем CSS кейлоггер
                                      –3
                                      файл hosts

                                      добавляем 127.0.0.1 mc.yandex.ru

                                      и все ваши правила прекращают работать
                                        0

                                        Причём тут яндекс? Мы же можем все запросы отправлять на свой сервер. На тот же домен на котором расположен собственно сайт.

                                          0

                                          hosts — халявный метод, идеален, если ненужный мусор на отдельном домене. Если он на том же домене, что сайт — опаньки. Но можно подавить с помощью extension. Для Chrome нужное API — chrome.webRequest onBeforeRequest, для Firefox, наверное, то же в browser. Но за всё приходится платить: это лишний код, выполняющийся перед загрузкой каждого файлика.

                                          0
                                          Current url schema:
                                          /<user_id>/<endpoint>/<value>


                                          и всё-таки лучше использовать сессию, а не id пользователя.
                                            0
                                            Не согласен с вами по следующим причинам:
                                            1. Запросы могут идти на другой домен. К примеру у вас сайт a.aa на нем загружены стили с домена b.bb
                                            2. State-less бэкенд прост в реализации
                                            3. Ничто не мешает вам игнорировать UserId, в случае, если ваш бэкенд поддерживает сесии (state-full, как например реализован spycss.hcbogdan.com). Но если у вас сессии не допустимы, данные потеряються.
                                            0

                                            Пользователи отключающие js могут и картинки отключить. А если их отключить то ваше отслеживание уже не работает.

                                              0
                                              Не согласен с вами. Есть методы, которые я не описал в этой статье. К примеру загрузка внешнего шрифта директивой @font-face {src: url()}
                                              Они работают даже тогда, когда картинки и JS отключен.
                                                +1
                                                uBlock блокирует загрузку внешних шрифтов. А про картинки, я подумал, вот про это:
                                                JavaScript жучки не подходят исходя из требований. За исключением таких, которые идут в комплекте с статичным счетчиком. (пример яндекс счетчика)
                                                Нормальные блокировщики (ну кроме nojs) отключают и то и то, и речи не идет чтобы оставить что-то одно. Конечно можно свою картинку запилить.

                                                У меня подход простой: считаете меня на своем сайте — считайте на здоровье. Отправляете мои данные другим (яндексу, гуглу, ...) — обойдетесь. (Точнее обойдется яндекс и гугл).

                                            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                            Самое читаемое