Программизм
 
Программизм
На главную | Графомания | Программизм | Книги | Всячина | Скачать | Ъ?  

Статистика посещений сайта на PHP

С чего всё начиналось

Начиналось всё с широко известного в узких кругах скрипта от Spectator’а. В одной из статей Дмитрий предупреждал, что в скрипте имеется «дырка», поэтому перед использованием он был немного модифицирован и обезжучен.

Реализация некоторых вещей показалась неоптимальной, потом захотелось чего-то, чего изначально не было... В результате получилась система более точная, чем бесплатный счётчик типа Mail.ru и, на мой вкус, более удобная для чтения. Собственно, XPEHOMETP™ (читается «кспехометп») — это всего лишь набор достаточно универсальных функций, а внешний вид отчётов легко настраивается под любые требования.

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

Откуда брать исходные данные?

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

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

«Позвольте, — скажет пытливый ум, — но ведь если так, то проще взять готовые логи web-сервера!» Сложно не согласиться с этим утверждением, если, конечно, провайдер даёт доступ к логам. Net.ru такой доступ даёт, причём логи разбиты по дням, а старые автоматически удаляются, что очень удобно. Отчёты на этом сайте формируются именно из логов сервера, но функциональность, позволяющая вести свой лог, в библиотеке оставлена.

Использование логов web-сервера порождает несколько проблем:

  1. время может быть в разных форматах, а функции, преобразующей строку произвольного формата во время, в PHP нет;
  2. порядок полей в логе может быть произвольным;
  3. в лог пишутся как удачные, так и неудачные обращения;
  4. в лог пишутся обращения ко всем сайтам, обслуживаемым в рамках одного контракта;
  5. в лог пишутся запросы на картинки, которые не могут считаться хитами.

Для решения проблемы 1 была написана функция strtotime(). Здесь, кстати, следует сказать ещё одно доброе слово в адрес net.ru — они пишут в лог время в виде unix timestamp, что существенно ускоряет обработку.

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

Проблема 5 решается передачей ещё одного регулярного выражения, например, /\.(js|css|gif|jpg)$/ — запросы, соответствующие этому выражению, не учитываются.

Есть и ещё одна проблема — многие провайдеры имеют развитую систему зеркал и прокси-серверов, поэтому часть обращений до web-сервера не доходят. Для того, чтобы документы в кэше не залёживались, существуют специальные заголовки протокола http — cache-control и pragma, добавить которые в документ можно при помощи следующего кода:

<?
  header("Cache-control: no-cache");
  header("Pragma: no-cache");
?>

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

<?
  header("Expires: ".gmdate("D, d M Y H:i:s",time()+60)." GMT");
?>

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

Итак, мы получили массив, содержащий все обращения к сайту за период. Что делать дальше?

Дальше — больше

Видать, в предыдущем абзаце я перехвалил Net.ru, и аккурат в момент написания статьи случился у них переход на новый алгоритм генерации логов. Из-за этого на экран полезли warning’и, и пришлось при чтении файла сначала проверить его наличие при помощи file_exists().

Мало того, оказалось, что дата в названии файла вовсе не говорит о том, что в нём данные только за указанный день. Приходится читать лог-файлы с некоторым запасом в обе стороны, а записи дополнительно фильтровать по времени. После этого информация, выводимая на экран, стала более-менее согласованной.

Кроме того, оказалось, что округлив timestamp до 86400, мы не получим полночь — сказывается удалённость от Гринвича. Поэтому пришлось наворачивать getdate() и mktime(). Но разве способны такие мелочи отравить радость от создания Системы?

Формирование общей статистики

Для начала следует понять, какие «общие» вопросы нас интересуют:

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

Количество обращений считается элементарно — это просто количество элементов в массиве. Количество же хостов — это количество различных IP-адресов, с которых происходили обращения к сайту. Число не совсем точное, например, несколько человек из корпоративной сети могут читать сайт через один и тот же прокси-сервер. Для разрешения данной ситуации можно передавать клиенту cookie, но эта проблема стоит перед посещаемыми ресурсами вроде новостных лент, домашней же страничке такие сложности ни к чему.

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

Количество визитов по ссылкам и визитов с поисковых серверов тоже определяется просто — по полю HTTP_REFERER. Функция, определяющая, является ли реферер поисковым сервером, может не анализировать текст запроса — это существенно ускоряет вычисления, если мы не собираемся выводить все поисковые запросы на экран.

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

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

Ну, и визитами в корень сайта считаются обращения, где REQUEST_URI равен "/".

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

Анализ рефереров

В принципе, анализ реферера мало чем отличается от того, что описан в вышеупомянутой статье на Spectator’е. Правда, длинный набор из if и elseif заменены циклом, но это дело вкуса. Добавлено несколько новых поисковых серверов, но количество их столь велико, что никогда нельзя будет сказать, что скрипт знает все сервера.

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

Ещё один интересный момент — кодировка запроса. Сейчас многие сервера, рассчитанные на работу с разными языками, выдают текст в Unicode, а для кодирования этого текста используют UTF-8.

UTF-8 — это один из вариантов передачи Unicode. Он хорош тем, что символы латиницы с кодами от 0x00 до 0x7F кодируются одним байтом, в точности как в ASCII. Символы с кодами от 0x0080 до 0x07FF кодируются двумя байтами, а остальные — тремя. Если в тексте не используются китайские иероглифы, то такой способ хранения эффективнее, чем двухбайтный Unicode. Схема кодирования приведена в таблице:
0x0000 - 0x007F0xxxxxxx
0x0080 - 0x07FF110xxxxx 10xxxxxx
0x0800 - 0xFFFF1110xxxx 10xxxxxx 10xxxxxx

Теоретически можно перекодировать текст прямо из UTF-8 в нужную однобайтовую кодировку, но проще и универсальнее (а возможно, и эффективнее) оттранслировать UTF-8 в двухбайтный Unicode, а оттуда — в windows-1251. Если вам надо транслировать Unicode в другую кодировку, мэппинг можно позаимствовать у Голубого Гиганта.

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

Ну, а если реферер не является поисковым сервером, то мы просто разбираем его адрес на составные части, на экран выводим только название хоста, а в атрибуте href тэга a указываем полную ссылку. Время от времени среди таких ссылок попадаются поисковые сервера — в этом случае код распознавания дописывается, и при следующем выводе статистики всё становится на место.

Вывод результатов

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

Что в планах?

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

Во-вторых, надо научиться рисовать какие-нибудь графики. Не то, чтобы они кому-нибудь нужны, просто «так положено».

Ну, и в-третьих, надо нарисовать какую-нибудь модную кнопку. А пока есть вот что:

Кнопка

Ну, а если есть какие-то идеи или желание приобрести скрипт — пишите письма.

Удачи!

31.07.2003

Поиск
См. также

Пользователи всех клонов *nix давно уже могут писать командные файлы на любом языке. Для этого надо лишь указать в первой строке имя интерпретатора... »»»

каждые три восьмибитных байта кодируются четырьмя шестибитными байтами. Вопрос в том, как выбрать диапазон печатных символов, чтобы отобразить множество полученных шестибитных чисел... »»»

как правило обрабатывать надо не сегодняшние файлы, а файлы за несколько дней назад... »»»

Рекомендую

e.g.Orius’
Игорь Иртеньев
Вячеслав Шевченко

Copyright notice

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

Удивительное рядом

lj userhardsign
Закладки Карта Королёва

Пишите письма

Счётчики

XPEHOMETP™ Рейтинг@Mail.ru