пятница, 6 мая 2011 г.

Mouse tracking system своими руками

Что это и зачем оно нам?
Есть много систем, которые предоставляют подробную статистику по использованию сайта. "Всемирно известный" Google Analytics, к примеру, расскажет сколько посетителей было, откуда, на каких страницах, когда, как быстро они ушли. При небольшой настройке можно посчитать какой процент из них нажал на кнопку "Оплатить" и т.п. Опций - великое множество, нет (или пока нет) разве что mouse tracking-а. Это опция отслеживания перемещений курсора. Статистика "закурсоривания" страницы позволяет визуально оценить какие области пользуются наибольшим вниманием среднего посетителя, а какие - наоборот. Работает психологический принцип: курсор чаще всего следует за взглядом.
Из сервисов, предоставляющих такую статистику нельзя не отметить clicktale.com и русскоязычный аналог - webvizor. Оба они записывают действия посетителей на страницах (перемещение курсора, клики и скроллинг) а затем предоставляют "тепловые карты" страниц и видеозаписи. Оба сервиса платные, причём стоимость зависит от числа страниц, которые нужно наблюдать, объёма данных которые нужно собрать и времени, в течении которого эти данные будут доступны для анализа. Если нужен постоянный мониторинг большого портала - это влетит в копейку. И не всегда удобно держать на своих страницах чужой скрипт, который следит за посетителями. То есть нам-то удобно, но посетителям, скорее всего, не понравится. Особенно, если наш сервис, к примеру, платёжная система.
Так может не ждать нам "милостей от природы", а взять и сделать такой сервис самостоятельно?

Оценим требования
  1. Нужно анализировать все страницы сайта (порядка 150). 
  2. Нужно следить за перемещением курсора (с допустимой погрешностью, к примеру, 10 пикселей)
  3. Скроллинг не учитываем: 80% страниц - небольшие формы, которые не требуют прокрутки
  4. Посетителей пишем всех. Это порядка 500 тыс. в сутки
  5. Данные храним все, отчёты будем выбирать за произвольный период.
  6. При проблемах с нагрузкой допускается потеря до 30% данных. Отличная вещь статистика :)
Проблемы, которые прийдётся решить
Обьём данных. 
Хранить каждое перемещение мыши каждого пользователя нереально. Альтернатива - храним матрицы вида {x, y, counter} для каждой страницы. Для удобства анализа и построения карт все "экраны посетителей" приведём к разрешению 800 x 600. Итого получаем максимальное число записей в базе: 800*600*150 = 72 миллиона. В реальности 2/3 площади страниц будет не покрыто "вниманием" посетителей, так что можно ориентироваться на цифру в 24 миллиона. В любом случае объёмы серьёзные. Будем сливать в архив данные за период (допустим за неделю) и очищать рабочую таблицу для нового "отчётного периода".
Трафик.
document.body.onmousemove = function() { ... ajax ... } убьёт наш сервис на первом же тестовом прогоне. Решения два, и используем мы их оба. Первое: очевидное - пишем точки, отстоящие друг от друга на n пикселей. Чем меньше n, тем больше точность и трафик. Сохранение данных также делаем с учётом точности. Ищем точку, у которой |x-x0| < n && |y-y0| < n и наращиваем счётчик у неё вместо создания новой записи. Очевидно, что при наличии нескольких близких точек, берём ближайшую. Второе решение: "собираем" точки на клиентской стороне и отправляем пакетом. Тут мы теряем неполный пакет, когда посетитель уходит со страницы, но это допустимая жертва на алтарь борьбы с трафиком. Понятно, что размер пакета и точность можно подбирать для достижения максимального качества при допустимой нагрузке.


Состав системы
Для сбора данных много усилий не потребуется: js - скрипт на сотню строк и серверная часть. Реализуем её позже, с ней всё просто: получить пакет, слить в базу с учётом точности.
Для отображения постраничных "карт внимания" понадобится закрытая область системы с авторизацией. Поскольку сервис внутренний, особенно изобретать тут ничего не понадобится. В аккаунте менеджер сайта должен видеть скриншоты страниц с наложенными на них матрицами отображающими усреднённый путь курсора клиентов. Где взять скриншоты? Можно, конечно же воспользоваться одном из сторонних сервисов, которые позволяют выгрузить скрин по url любой страницы, но мы так делать не будем. Во-первых страницы наши - закрытые, а во вторых зачем нам опять сторонние сервисы? Пока достаточно чтобы менеджер сам залил скрин страницы в разрешении 800*600. Тем более что вначале все страницы и не понадобятся. Серверную часть реализуем на java (servlet + jsp). Отрисовку карт внимания поверх скринов страниц сделаем javascript-ом.

JavaScript-модуль
  1. var tracker = {};
  2.  
  3. tracker.accur = 10;
  4. tracker.pX = 0;
  5. tracker.pY = 0;
  6. tracker.req;
  7. tracker.cashe = new Array();
  8.  
  9. // дожидаемся загрузки страницы и вешаем обработчик перемещения курсора
  10. tracker.initOnLoad = function() {
  11.     if(document.body != null && typeof(document.body) != "undefined") {
  12.         document.onmousemove = tracker.trackEvent;
  13.     }
  14.     else {
  15.         setTimeout(function() {
  16.             tracker.initOnLoad();
  17.         }, 1);
  18.     }    
  19. }
  20.  
  21. // обрабатываем перемещение курсора
  22. tracker.trackEvent = function(e) {
  23.     var e=e || window.event
  24.     var mX=e.x || e.clientX
  25.     var mY=e.y || e.clientY
  26.     
  27.     var wX = document.body.clientWidth || document.body.innerWidth
  28.     var wY = document.body.clientHeight || document.body.innerHeight
  29.  
  30.     mX = (mX/wX*800).toFixed(0);
  31.     mY = (mY/wY*600).toFixed(0);
  32.  
  33.     if ((mX-tracker.pX)>tracker.accur || (mY-tracker.pY)>tracker.accur) {
  34.         tracker.pX = mX;
  35.         tracker.pY = mY;
  36.         tracker.addToCashe(mX, mY);
  37.         tracker.sendCashe();
  38.     }    
  39. }
  40.  
  41. // добавляем точку в пакет
  42. tracker.addToCashe = function(x, y) {
  43.     var ncashe = new Array();
  44.     var found = false;
  45.     for(var i=0; i<tracker.cashe.length; i++) {
  46.         var m = tracker.cashe[i];
  47.         if (m.x==x && m.y==y) {
  48.             ncashe[ncashe.length] = {x:m.x, y:m.y, counter:(m.counter+1)};
  49.             found = true;
  50.         } else {
  51.             ncashe[ncashe.length] = m;
  52.         }
  53.     }
  54.     if (!found) {
  55.         ncashe[ncashe.length] = {x:x, y:y, counter:1};
  56.     }
  57.     tracker.cashe = ncashe;    
  58. }
  59.  
  60. // отправляем пакет на сервер
  61. tracker.sendCashe = function() {
  62.     if(tracker.cashe.length>49) {
  63.         var data = "";
  64.         for(var i=0; i<tracker.cashe.length; i++) {
  65.             var m = tracker.cashe[i];
  66.             data += m.x+"t"+m.y+"t"+m.counter+"n";
  67.         }
  68.         data = data.substr(0, (data.length-1));
  69.         var url = "http://your.server.side/servlet?&a="+tracker.accur+"&data="+data+"&url="+encodeURIComponent(window.location.href.substr(0, 100));
  70.         tracker.sendTrackData(url);
  71.         tracker.cashe = new Array();
  72.     }
  73. }
  74.  
  75. // ajax запрос. Ответ нам не нужен, его не обрабатываем
  76. tracker.sendTrackData = function(url) {
  77.     if (window.XMLHttpRequest) {
  78.         try {
  79.             tracker.req = new XMLHttpRequest();
  80.         } catch (e){}
  81.     } else if (window.ActiveXObject) {
  82.         try {
  83.             tracker.req = new ActiveXObject('Msxml2.XMLHTTP');
  84.         } catch (e){
  85.             try {
  86.                 tracker.req = new ActiveXObject('Microsoft.XMLHTTP');
  87.             } catch (e){}
  88.         }
  89.     }
  90.     if (tracker.req) {     
  91.         tracker.req.open("GET", url, true);
  92.         tracker.req.setRequestHeader("If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT");
  93.         tracker.req.send(null);
  94.     }
  95. }
  96.  
  97. // ну а тут всё начинается :)
  98. tracker.initOnLoad();

Сорри за стиль, давно не брался за Javascript. Но в целом, должно быть понятно. В следующих постах опишу серверную часть и реализацию админки.
По затратам времени: не отвлекаясь от основной работы прототип удалось запустить за два вечера. Под нагрузкой прийдётся "доводить" ещё дня три.  Как видим, трудозатраты совершенно не сопоставимы с выгодами и преимуществами, которые получаем от такого сервиса.

1 комментарий:

  1. Wow. I am impressed with your approach of doing things at your own. You have posted the complete code and that will help all the readers who are familiar with it. Thanks.
    mouse click tracking

    ОтветитьУдалить