В этом посте вы сможете узнать, почему даже с выключенным 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
  • определить ориентацию и тип устройства

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

Комментарии (48)


  1. Akuma
    03.02.2018 02:20

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

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

    Нашли, но не можете ответить? Включите JavaScript


    1. hcbogdan Автор
      03.02.2018 11:51
      +2

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

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

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


      1. ivan386
        03.02.2018 12:21

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


        1. Akuma
          03.02.2018 12:27

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


          1. ivan386
            03.02.2018 13:02
            +1

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


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


            1. Akuma
              03.02.2018 13:04
              -1

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

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


              1. ivan386
                03.02.2018 13:07
                -1

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


                1. Akuma
                  03.02.2018 13:14

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


                  1. ivan386
                    03.02.2018 13:28

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


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


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


                    1. daggert
                      03.02.2018 20:48
                      +1

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

                      Зачем?


                      1. ivan386
                        03.02.2018 23:46
                        +1

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


                      1. Areso
                        04.02.2018 00:10

                        Внезапно некоторые посещают сайты с браузеров, где JS/CSS не поддерживаются by design. К примеру, если у меня сломалась DE, я использую Links (либо Lynx). Причем регулярно. И с серверов тоже пользуюсь. Впрочем, есть и графические браузеры, не поддерживающие JS/CSS.
                        Для меня самое главное на большинстве сайтов — это их наполнение, а не свистелки на JS и украшательства на CSS.
                        Более того, у меня время от времени чешутся руки написать граббер, который будет собирать с интересных мне страниц и сайтов только текст.


                        1. daggert
                          04.02.2018 04:21

                          Согласен с вами, но только отчасти. Я тоже иногда захожу через такие браузеры и тоже граблю контент через скрипты (прокси-скрипты для сайтов где нет rss), однако может вам нужен не сайт без css, а сайт с хорошей семантикой?
                          Ок — можно прожить без JS — я сам ругаюсь на такие сайты, однако мне их приходится делать, но вот CSS… боюсь что это необходимый минимум. Правда я за то чтобы анимации можно было отключать.


                          1. Areso
                            04.02.2018 09:23

                            Анимации? А видео, встроенное в фон страницы?) Ради одного этого я готов уфигачить CSS в ноль или использовать кастомное CSS всегда и везде.


                            1. daggert
                              04.02.2018 20:58

                              Видео вроде как не в css, или я отстал от жизни?


                              1. Areso
                                04.02.2018 21:01

                                Отстали.
                                www.myprovence.fr/snapshots2012/en
                                www.clinicadrmauro.com.br
                                Здесь у нас замечательная смесь CSS3, HTML5, video as background (трафик!).
                                у пэйпола было одно время видео в фоне, было у фейсбука, кажется у Эппл было…


                                1. daggert
                                  04.02.2018 21:06

                                  Боюсь что вы не правы. Это просто контейнер video, а css тут только для выравнивания его по z-index'у и на нужной позиции.


                                  1. Areso
                                    04.02.2018 21:08
                                    -1

                                    Значит, я был неправ. Что не отменяет моего желания найти и покарать)


                        1. tutam
                          04.02.2018 15:03

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


                    1. VolCh
                      04.02.2018 01:29

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


                      1. ivan386
                        04.02.2018 01:42

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


        1. Jokerjar
          04.02.2018 00:23

          Статья 2009 года. В то время, в эпоху табличной верстки, эта «шутка», может, и имела место: сайт становился «голым», но не терял свой, так скажем, скелет. Сейчас же, с блочной версткой, с адаптивом и со слоями, если снять CSS, сайт просто превратится в нечитаемую кашу.


      1. Kicker
        04.02.2018 00:04

        Боты phantomjs как раз обрабатывают js)


        1. hcbogdan Автор
          04.02.2018 01:13

          Конечно, но в таком случае нужно получать hover / focus и другие псевдоклассы — нужно потрудиться чтобы написать такой сценарий.
          Если триггеры не срабатывают — вероятно это бот.


      1. Areso
        04.02.2018 00:06

        Встречаются люди с расширениями User CSS / Stylebot / Stylish / etc, которые могут к чертям сломать такую аналитику. У меня есть знакомые, которые их используют.


        1. Goodkat
          04.02.2018 00:37

          Я использую, чтобы отключать вырвиглазные шрифты, но описанные в статье приёмы я вряд ли замечу и отключу, даже случайно.
          Впрочем, в последнее время я просто отключил левые шрифты в ublocker-e, и стили давно уже не подправлял.


    1. Mihail72
      03.02.2018 12:20
      +1

      тут, как мне кажется, вопрос про блокировку js способов различными плагинами.


    1. Areso
      04.02.2018 00:03

      Старые форумы работают отлично без JS.


    1. Deosis
      05.02.2018 09:05

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


      1. ivan386
        05.02.2018 09:35

        Noscript


    1. ademaro
      05.02.2018 10:28

      1. Akuma
        05.02.2018 12:16

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

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


  1. Vest
    03.02.2018 02:45

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


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

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


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


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


    1. MiXei4
      03.02.2018 04:33

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


    1. hcbogdan Автор
      03.02.2018 11:18

      Как правильно заметил MiXei4 это актуально для отслеживания как внутренних так и внешних ссылок.

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

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


  1. aamonster
    03.02.2018 02:46

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


    1. hcbogdan Автор
      03.02.2018 11:23

      Есть некоторая особенность в обработке таких запросов.
      Прежде всего все загрузки 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


  1. altervision
    03.02.2018 09:57
    -1

    Как думаете, а каким образом можно было бы с помощью CSS вытаскивать данные из форм? Например, из форм заказов на сворованных конкурентами лендингах. Существует ли вообще такая возможность?


    1. hcbogdan Автор
      03.02.2018 12:11

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

      Например, таким образом: jsfiddle.net/hcbogdan/1wdky4t6/1


    1. hcbogdan Автор
      04.02.2018 11:17

      Определенно, существует угроза «утечки» данных. Некоторую информацию вы можете найти в статье: создаем CSS кейлоггер


  1. Methos
    03.02.2018 11:24
    -3

    файл hosts

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

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


    1. MiXei4
      03.02.2018 11:34

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


    1. aamonster
      03.02.2018 11:41

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


  1. devalone
    03.02.2018 14:07

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


    и всё-таки лучше использовать сессию, а не id пользователя.


    1. hcbogdan Автор
      03.02.2018 14:11

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


  1. riky
    03.02.2018 22:39

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


    1. hcbogdan Автор
      03.02.2018 22:46

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


      1. LynXzp
        03.02.2018 23:49

        uBlock блокирует загрузку внешних шрифтов. А про картинки, я подумал, вот про это:

        JavaScript жучки не подходят исходя из требований. За исключением таких, которые идут в комплекте с статичным счетчиком. (пример яндекс счетчика)
        Нормальные блокировщики (ну кроме nojs) отключают и то и то, и речи не идет чтобы оставить что-то одно. Конечно можно свою картинку запилить.

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