Счётчик посещений

  1. Введение
  2. Постановка задачи
  3. Решение задачи
  4. Пример работы
  5. Что можно добавить

Введение

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

Среди большого количества бесплатных счётчиков, которые предоставляют сервис статистики, я выбрал HotLog. Этот выбор был обусловлен тем, что HotLog, в общем, неплохой сервис и предоставляет то самое, чего не хватает WebAlizer. Их графический счётчик показывает общее число посещений и прибавку посетителей за текущий день.

Однако, после некоторого времени использования счётчика, наблюдалась следующая ситуация - сервер статистики, который выдаёт картинку, был недоступен огромное количество времени. Это и понятно - бесплатный сервис работает по остаточному принципу. Но разница, которая была между хотлоговским счётчиком и WebAlizer, меня не устраивала. Так родилась идея написать очень простой и быстрый счётчик, который бы учитывал общее число посетителей, прибавку за день и количество посещённых страниц.

Постановка задачи

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

От постановки задачи уже можно перейти к условностям :-). Решение программных задач очень схоже с решением уравнений. Итак, пускай посетитель у нас будет хостом, просмотр страницы хитом. Пускай хостом будет считаться браузер, который не передал секретную куку (cookie - печенье), или в переданной куке содержится вчерашняя дата. Вообще, в куке будет храниться дата, когда она (кука) была установлена счётчиком. Это нужно, чтобы разделять посетителей по дням, то есть, если пользователь зашёл вчера и посетил 10 страниц, то должны насчитаться 1 хост и 10 хитов. Если этот же пользователь зашёл уже сегодня и посетил 5 страниц, то это 1 хост и 5 хитов. То есть, статистика пользователей должна быть посуточной. Хитом в этом случае будет считаться любое посещение пользователя.

Для хранения статистики я выбрал обычный текстовый файл, где будет храниться всего одна строка. Это будет самый быстрый вариант хранения данных. Конечно, много информации так не сохранишь, но мне это и не требуется.

Решение задачи

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

Второй момент - графический фон счётчика. Всё-таки намного эстетичней, когда счётчик сам по себе не портит дизайн сайта и вписывается в него органично. Поэтому нужно предусмотреть два варианта, когда есть подложка и когда файл-подложка недоступен. Если файла с фоном нет, то его (фон) нужно нарисовать.

И третье, что нужно учесть - блокировка файла на время обработки. Блокировать нужно на тот случай, если будет несколько обращений к файлу статистики одновременно. Если же файл не блокировать, то вполне вероятно, что будет ситуация, когда либо сам файл повредится, либо некоторые данные будут теряться.

Помня обо всём этом, можно приступать к написанию скрипта.

Графический счётчик - index.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
<?
/**
 * Графический счётчик посещений
 * Схема работы:
 *  1. устанавливаем куку
 *  2. считываем файл с данными посещений
 *  3. прибавляем хиты
 *  4. если кука не установлена или в ней вчерашняя дата, то прибавляем хосты
 *  5. записываем данные в файл
 *  6. выводим картинку со статистикой
 *
 * @author zg (//anton-pribora.ru)
 * @package imageCounter
 */

// Выключаем вывод ошибок
error_reporting(0);

// Запрет кэширования
header('Expires: Mon, 11 Jul 1991 03:00:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-cache, must-revalidate');
header('Pragma: no-cache');

// Объявляем некоторые константы
define('COOKIE_NAME', '__count_date');
define('SPLITTER'   , ' ');
define('STAT_FILE'  , 'stat.txt');
define('TODAY_TIME' , time());
define('TODAY_DATE' , date('Y-m-d') );

define('COUNTER_IMAGE_FILE', 'counter.gif');
define('COUNTER_IMAGE_TYPE', 'gif');

// Получение куки
$userTime = isset($_COOKIE[ COOKIE_NAME ]) ? (int) $_COOKIE[ COOKIE_NAME ] : null;

// Установка куки
setcookie(COOKIE_NAME, TODAY_TIME, TODAY_TIME + 60 * 60 * 24);

// Проверка на хост (хостом пусть будет браузер без куки)
if ( $userTime )
    $isNewHost = date('Y-m-d', $userTime) !== TODAY_DATE;
else 
    $isNewHost = true;

// Хитом будем называть просто показ страницы
$isHit = true;

// Обнуляем суммарные значения счтёчика
$totalHosts = (int) $isNewHost;
$totalHits  = (int) $isHit;

$todayHosts = (int) $isNewHost;
$todayHits  = (int) $isHit;

// Открываем файл статистики для чтения и записи
if ( $fp = fopen(STAT_FILE, 'a+b') )
{
    // Блокируем файл, чтобы не дать другим процессам переписать файл до его обработки
    if ( flock($fp, LOCK_EX) )
    {
        // Файл успешно блокирован, выполняем его обработку
        
        // Переводим указатель на начало файла
        fseek($fp, 0, SEEK_SET);
        
        // Подготавливаем переменные для подсчёта хитов и хостов
        $totalHostsTemp = 0;
        $todayHostsTemp = 0;
        
        $totalHitsTemp  = 0;
        $todayHitsTemp  = 0;
        
        $todayTemp = null;
        
        // Будем думать, что в файле первая строка содержит нужные данные
        $line = fgets($fp);
        
        // Пускай в первой строке содержатся: хосты, хиты, хосты за сегодня, 
        // хиты за сегодня, дата записи
        if ( $line ) @list($totalHostsTemp, $totalHitsTemp, $todayHostsTemp, 
$todayHitsTemp, $todayTemp) = split(SPLITTER, $line);
        
        // Проверка даты
        if ( $todayTemp !== TODAY_DATE )
        {
            // Дата в файле ститистики устарела, обнуляем сегодняшие хосты и хиты
            $todayHostsTemp = 0;
            $todayHitsTemp  = 0;
        }
        
        // Прибавляем данные
        $totalHosts += $totalHostsTemp;
        $todayHosts += $todayHostsTemp;
        
        $totalHits  += $totalHitsTemp;
        $todayHits  += $todayHitsTemp;
        
        // Переводим указатель на начало файла
        fseek($fp, 0, SEEK_SET);
        
        // Урезаем файл до нулевой длины
        ftruncate($fp, 0);
        
        // Записываем данные - сначало хосты, хиты, хосты за сегодня, 
        // хиты за сегодня, дата
        fputs($fp, join(SPLITTER, 
            array($totalHosts, $totalHits, $todayHosts, $todayHits, TODAY_DATE)));
        
        // Снимаем блокировку, но можно и не снимать, если верить мануалу
        flock($fp, LOCK_UN);
    }
    
    // Обработка файла завершена, закрываем файловый указатель
    fclose($fp);
}

// Функция для создания картинки
$createImageFunction = 'imagecreatefrom'. COUNTER_IMAGE_TYPE;
$outputImageFunction = 'image'          . COUNTER_IMAGE_TYPE;

// Теперь необходимо вывести данные статистики
if ( function_exists($createImageFunction) && function_exists('imagecreatetruecolor') )
{
    $yOffset = 2;
    
    // Если фоновая картинка есть, то используем её
    if ( !($im = $createImageFunction(COUNTER_IMAGE_FILE)) )
    {
        // Если фоновой картинки нет, то создаём её
        $imageWidth  = 88;
        $imageHeight = 31;
        $yOffset     = 8;
        
        $im = imagecreatetruecolor($imageWidth, $imageHeight);
        
        $bgColor     = imagecolorallocate($im, 220, 220, 220);
        $borderColor = imagecolorallocate($im, 120, 120, 120);
        
        // Рисуем фоновую картинку
        imagefill($im, 0, 0, $bgColor);
        imagerectangle($im, 0, 0, $imageWidth - 1, $imageHeight - 1, $borderColor);
        imageline($im, $imageWidth - 8, 0, $imageWidth, 8, $borderColor);
        imagefill($im, $imageWidth - 2, 2, $borderColor);
    }
    
    // Если изображение успешно создано, то выводим счётчик
    if ( $im )
    {
        header('Content-Type: image/'. COUNTER_IMAGE_TYPE);
        
        // Устанавливаем цвет, шрифт и размер
        $fontColor  = imagecolorallocate($im, 50, 50, 50);
        $fontFamily = 'Verdana';
        $fontSize   = '2';
        
        imagestring($im, $fontSize, 8 , $yOffset, $totalHosts, $fontColor);
        imagestring($im, $fontSize, 55, $yOffset, '+'. $todayHosts, $fontColor);
        
        $outputImageFunction($im);
        imagedestroy($im);
    }
}
else
{
    // Увы, функций для работы с картинками нет
?>
Нет функций для работы с картинками
<?
}

?>

Этот скрипт я назвал index.php и поместил в папку counter.

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

Графический счётчик - .htaccess
1
2
3
4
5
6
7
8
9
10
11
12
13
14
## ------------------------------------------------
## Описание: Файл настроек счётчика
## Автор   : zg (//anton-pribora.ru), 2008 год
## ------------------------------------------------

RewriteEngine On
Options FollowSymlinks

## Текущее расположение данного файла относительно корня сайта
#RewriteBase /

## Favicon
RewriteCond %{REQUEST_URI} /cnt.gif$
RewriteRule .* index.php [L]

Теперь можно обращаться к счётчику по ссылке counter/cnt.gif.

Последним штрихом, я написал файл, который отображает краткую статистику счётчика в виде html-страницы.

Графический счётчик - stat.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
<?
/**
 * Файл для показа статистики счётчика
 *
 * @author zg
 * @package imageCounter
 */

// Выключаем вывод ошибок
//error_reporting(0);

// Запрет кэширования
header('Expires: Mon, 11 Jul 1991 03:00:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-cache, must-revalidate');
header('Pragma: no-cache');

// Объявляем некоторые константы
define('SPLITTER'   , ' ');
define('STAT_FILE'  , 'stat.txt');

// Переменные статистики
$totalHosts = 0;
$totalHits  = 0;

$todayHosts = 0;
$todayHits  = 0;

if ( file_exists(STAT_FILE) && ($fp = fopen(STAT_FILE, 'rb')) )
{
    // Файл статистики существует и доступен для чтения
    
    // Считываем первую строку
    $line = fgets($fp);
    fclose($fp);
    
    // Пускай в первой строке содержатся: хосты, хиты, 
    // хосты за сегодня, хиты за сегодня, дата записи
    if ( $line ) @list($totalHosts, $totalHits, $todayHosts, $todayHits) = split(SPLITTER, $line);
}

?>
<html>
<head>
<title>Статистика посещений anton-pribora.ru</title>
<style>
body {
    font-family: Verdana;
    font-size: 0.8em;
}
table {
    font-size: 1em;
}

th {
    background-color: #ddd;
    font-weight: normal;
}

td.stat {
    border-bottom: 1px #aaa solid;
    border-right: 1px #aaa solid;
}

td.today {
    font-weight: bold;
}

</style>
</head>
<body>
<table cellpadding="4" border="0" align="center">
<caption>Статистика посещений</caption>
<colgroup>
    <col align="left" />
    <col align="center" />
    <col align="center" />
</colgroup>
<tr>
    <td></td>
    <th>Cегодня</th>
    <th>Всего</th>
</tr>
<tr>
    <th>Посетителей</th>
    <td class="stat hosts today"><?=$todayHosts ? '+' : ''?><?=myNumeric($todayHosts)?></td>
    <td class="stat hosts total"><?=myNumeric($totalHosts)?></td>
</tr>
<tr>
    <th>Страниц</th>
    <td class="stat hits today"><?=$todayHits ? '+' : ''?><?=myNumeric($todayHits)?></td>
    <td class="stat hits total"><?=myNumeric($totalHits)?></td>
</tr>
</table>
<center>
<a href="javascript:window.close()">Закрыть окно</a>
</center>
</body>
</html>
<?
function myNumeric($num)
{
    return number_format($num, null, null, ' ');
}
?>

Пример работы

После размещения файлов на сервере, я вставил такой код в шаблон:

Графический счётчик - ссылка на статистику
<a href="javascript:;" 
onclick="window.open('/counter/stat.php','stat','height=180,width=300').focus()" 
title="Статистика посещений">
<img border="0" src="/counter/cnt.gif" width="88" height="31" alt="ZG's counter" /></a>

В результате получил счётчик, почти как у HotLog :-), а может и лучше, ведь он доступен всегда, когда доступен сайт!

Что можно добавить

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