Отслеживание нажатий с помощью PHP: базовые и продвинутые подходы
Трекинг кликов в PHP: обзор подходов
Отслеживание действий пользователя, в частности кликов, решает задачи анализа поведения, конверсии и безопасности. В PHP эта функциональность реализуется как на стороне сервера, так и через взаимодействие с клиентской частью. Ниже рассмотрены основные методы с акцентом на производительность и удобство сопровождения.
Как обеспечить трекинг без перезагрузки страницы?
Наиболее эффективный способ - асинхронный HTTP-запрос (AJAX) к PHP-скрипту, который сохраняет данные в реляционной базе данных (MySQL). Такой подход не нарушает пользовательский опыт и позволяет фиксировать любые события.
Пошаговая реализация
- Создание таблицы для хранения кликов:
CREATE TABLE clicks (
id INT AUTO_INCREMENT PRIMARY KEY,
url VARCHAR(500) NOT NULL,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
user_agent VARCHAR(255),
session_id VARCHAR(64)
);Click track php (трекинг кликов в php)
- PHP-обработчик (track.php):
<?php
$pdo = new PDO('mysql:host=localhost;dbname=analytics', 'user', 'pass');
$stmt = $pdo->prepare('INSERT INTO clicks (url, user_agent, session_id) VALUES (?, ?, ?)');
$stmt->execute([
$_POST['url'] ?? '',
$_SERVER['HTTP_USER_AGENT'] ?? '',
session_id()
]);
header('Content-Type: application/json');
echo json_encode(['status' => 'ok']);- JavaScript на странице:
document.addEventListener('click', function(e) {
fetch('track.php', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'url=' + encodeURIComponent(window.location.href)
});
});Возможные проблемы и решения
- Потеря сессии при параллельных запросах: сессии PHP по умолчанию блокируются. Для асинхронного трекинга следует закрыть сессию сразу после чтения идентификатора: session_write_close().
- CSRF-атаки: если трекер используется для критичных действий, добавьте проверку токена. Внутренний трекинг редко требует защиты, так как запросы только на запись без влияния на данные.
- Пропуск запросов при большом трафике: используйте асинхронное подключение к БД или очереди (см. вариант с очередью).
Как записывать клики в файл для быстрого прототипа?
Самый простой вариант - дописывать данные в текстовый файл. Подходит для отладки или низконагруженных проектов.
<?php
$data = date('Y-m-d H:i:s') . ' | ' . ($_GET['url'] ?? 'unknown') . "\n";
file_put_contents('clicks.log', $data, FILE_APPEND | LOCK_EX);Проблемы:
- При конкурентном доступе возможна потеря данных, если явно не установлена блокировка (LOCK_EX).
- Файл быстро разрастается, анализ затруднён.
Как отследить клик с помощью пиксельного трекера (1x1 GIF)?
Метод используется в email-рассылках и простых счетчиках. Клик по ссылке ведет на PHP-скрипт, который записывает событие и возвращает прозрачное изображение.
<?php
// pixel.php?url=...
$trackUrl = $_GET['url'] ?? '';
file_put_contents('clicks.log', date('c') . ' ' . $trackUrl . PHP_EOL, FILE_APPEND);
header('Content-Type: image/gif');
echo base64_decode('R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7');Ссылка на трекере: <a href="pixel.php?url=target.php">ссылка</a>
Ограничения:
- Только GET-запросы, данные доступны в URL.
- Не подходит для отслеживания произвольных кликов без редиректа.
Как обработать высоконагруженный трекинг с минимальной задержкой?
Для масштабируемых решений используют очередь сообщений (RabbitMQ, Redis). PHP-скрипт отправляет данные в очередь, а отдельный воркер записывает их в базу.
// producer.php
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redis->lPush('clicks', json_encode(['url' => $_POST['url'], 'time' => microtime(true)]));// worker.php (запускается постоянно)
$pdo = new PDO('mysql:...');
$redis = new Redis();
while ($data = $redis->brPop('clicks', 5)) {
$click = json_decode($data[1], true);
$pdo->prepare('INSERT INTO clicks (url, ts) VALUES (?, FROM_UNIXTIME(?))')->execute([$click['url'], $click['time']]);
}Сложность:
- Требуется установка и поддержка Redis/RabbitMQ.
- Воркер должен быть стабильным (супервизор, systemd).
Как внедрить готовое решение без написания кода с нуля?
Системы веб-аналитики (Matomo, Open Web Analytics) предоставляют PHP-библиотеку для трекинга. Установка через Composer:
composer require matomo/matomo-php-trackeruse MatomoTracker;
$matomo = new MatomoTracker($idSite, $matomoUrl);
$matomo->doTrackPageView('Название страницы');
$matomo->doTrackEvent('Category', 'Action', 'Label');Недостатки:
- Зависимость от внешнего сервиса.
- Необходимость настройки конфиденциальности (GDPR).
Расширенные примеры реализации трекинга кликов
Пример 1: трекинг с защитой от дублирования и сессиями
PHP-скрипт проверяет, был ли клик уже зафиксирован за текущую сессию по определённому URL, чтобы не раздувать базу.
<?php
session_start();
$url = $_POST['url'] ?? '';
if (!$url || isset($_SESSION['clicked_urls'][$url])) {
http_response_code(204);
exit;
}
$_SESSION['clicked_urls'][$url] = true;
$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', '');
$stmt = $pdo->prepare('INSERT INTO clicks (url, user_agent, session_id) VALUES (?, ?, ?)');
$stmt->execute([$url, $_SERVER['HTTP_USER_AGENT'], session_id()]);
echo json_encode(['status' => 'recorded']);Результат: В таблицу clicks добавляется запись только при первом клике по данному URL в сессии. Повторные запросы возвращают 204 No Content.
Пример 2: трекинг с использованием JSON-логгера и ротации файлов
Для тех случаев, когда база данных недоступна, можно использовать структурированные логи в формате JSON, которые затем обрабатываются с помощью Logstash или другого парсера.
<?php
function logClick($url, $userId = null) {
$logEntry = [
'timestamp' => date('c'),
'url' => $url,
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
'ip' => $_SERVER['REMOTE_ADDR'] ?? '',
'user_id' => $userId
];
$logFile = '/var/log/clicks/' . date('Y-m-d') . '.jsonl';
$dir = dirname($logFile);
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
file_put_contents($logFile, json_encode($logEntry) . PHP_EOL, FILE_APPEND | LOCK_EX);
}
logClick($_POST['url'] ?? 'direct');Результат: Создаётся файл 2025-03-26.jsonl, содержащий одну строку JSON на событие. Пример строки:
{"timestamp":"2025-03-26T12:00:00+00:00","url":"http://example.com/page","user_agent":"Mozilla/5.0 ...","ip":"192.168.1.1","user_id":null}Пример 3: асинхронная запись через MySQL с использованием INSERT DELAYED (устарело) и альтернативы
В версиях MySQL до 5.6 существовал INSERT DELAYED, который немедленно возвращал управление, а запись производилась в фоне. Сейчас вместо этого можно использовать таблицы с ENGINE=MEMORY и скрипт, периодически сбрасывающий данные на диск. Пример: создание витрины в оперативной памяти и последующее копирование в InnoDB.
CREATE TABLE clicks_memory (
url VARCHAR(500),
ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=MEMORY;
-- PHP-скрипт записи
$pdo->query('INSERT INTO clicks_memory (url) VALUES ("' . addslashes($url) . '")');
-- Фоновый скрипт переноса (cron каждые 5 минут)
$pdo->query('INSERT INTO clicks_innodb SELECT * FROM clicks_memory');
$pdo->query('DELETE FROM clicks_memory');Результат: Запись кликов происходит быстро, так как ENGINE=MEMORY не использует дисковые операции. Однако при сбое сервера данные теряются. Данная техника полезна для временного хранения с последующей агрегацией.
Пример 4: трекинг с использованием Bitwise флагов для подсчета уникальных кликов
Для систем с ограниченными ресурсами можно записывать факт клика в битовую маску, храненную в Redis. Это позволяет быстро подсчитать уникальное количество кликов на страницу без хранения каждого события.
$redis = new Redis();
$redis->connect('127.0.0.1');
$pageId = $_POST['page_id'] ?? 0;
$userId = session_id();
// Используем битовое поле: каждый бит соответствует дню месяца
$bitOffset = (int)date('j'); // день месяца 1-31
$redis->setBit('clicks:'.$pageId, $bitOffset, 1);
$redis->sAdd('users:'.$pageId, $userId); // для подсчета уникальных посетителейРезультат: В Redis создаётся строка clicks:123 с битовой маской, где установлен бит в позиции текущего дня. Поле users:123 хранит set идентификаторов сессий. Подсчёт уникальных дней выполняется через BITCOUNT clicks:123. Этот подход использует минимальное количество памяти.Пример 5: обработка ошибок при недоступности БД – отложенная запись в файл и повторная попытка
<?php
try {
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass', [
PDO::ATTR_TIMEOUT => 2
]);
$pdo->prepare('INSERT INTO clicks (url) VALUES (?)')->execute([$_POST['url']]);
} catch (PDOException $e) {
// Сохраняем в файл-буфер
$buffer = '/tmp/click_buffer.txt';
file_put_contents($buffer, serialize(['url' => $_POST['url'], 'time' => time()]) . PHP_EOL, FILE_APPEND);
http_response_code(202); // запрос принят, но не обработан
}
// Фоновый скрипт восстанавливает данные из буфера
if (file_exists('/tmp/click_buffer.txt')) {
$lines = file('/tmp/click_buffer.txt', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
foreach ($lines as $line) {
$data = unserialize($line);
$pdo->prepare('INSERT INTO clicks (url, ts) VALUES (?, FROM_UNIXTIME(?))')->execute([$data['url'], $data['time']]);
}
unlink('/tmp/click_buffer.txt');
}Результат: При сбое БД запрос получает HTTP 202 и данные не теряются, а помещаются в буфер. Восстановление происходит позже (по крону или при следующем запросе). Это повышает надёжность системы трекинга.