Практические решения для обработки кликов по URL на PHP
Основные подходы к обработке кликов
Как реализовать универсальный трекер кликов с мгновенным перенаправлением и логированием?
Основное и наиболее надёжное решение - использование PHP-скрипта в качестве посредника. Все ссылки на сайте заменяются на адрес вида /track.php?url=.... При переходе скрипт фиксирует событие (в файл, базу данных или сторонний сервис) и сразу выполняет редирект. Такой подход работает без JavaScript и не зависит от браузера пользователя.
Пример простого скрипта track.php:
<?php
$url = $_GET['url'] ?? null;
if (!$url) {
header('HTTP/1.1 400 Bad Request');
exit('URL not provided');
}
// Логирование в текстовый файл (добавление строки)
$logEntry = date('Y-m-d H:i:s') . ' | ' . $_SERVER['REMOTE_ADDR'] . ' | ' . $url . "\n";
file_put_contents('clicks.log', $logEntry, FILE_APPEND | LOCK_EX);
// Перенаправление
header('Location: ' . $url, true, 302);
exit;
?>
Результат: при переходе на http://site.com/track.php?url=https://example.com в файл clicks.log добавляется запись, а пользователь перенаправляется на https://example.com.
Типичные проблемы: открытая редиректы (можно перенаправить на любой сайт) - риск для безопасности. Рекомендуется проверять url через белый список доменов или как минимум фильтровать протокол (разрешать только http/https). Также возможна потеря меток UTM при кодировании - используйте urlencode() при формировании ссылок.
Как использовать базу данных для более глубокой аналитики кликов?
Вместо текстового лога можно сохранять данные в MySQL или SQLite. Это позволяет выполнять сложные запросы - количество кликов по дням, источники (реферер), браузеры и т.д. Пример структуры таблицы:
CREATE TABLE clicks (
id INT AUTO_INCREMENT PRIMARY KEY,
url VARCHAR(2048) NOT NULL,
ip VARCHAR(45) NOT NULL,
user_agent TEXT,
referer TEXT,
clicked_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
Вставка данных через PDO:
$stmt = $pdo->prepare('INSERT INTO clicks (url, ip, user_agent, referer) VALUES (:url, :ip, :ua, :ref)');
$stmt->execute([
':url' => $url,
':ip' => $_SERVER['REMOTE_ADDR'],
':ua' => $_SERVER['HTTP_USER_AGENT'] ?? '',
':ref' => $_SERVER['HTTP_REFERER'] ?? ''
]);
Проблемы: при большом количестве кликов база может стать узким местом. Рекомендуется использовать очереди (RabbitMQ) или асинхронную запись. Также нельзя полагаться на HTTP_REFERER - он может отсутствовать или быть подделан.
Как отслеживать клики без изменения ссылок с помощью JavaScript?
Если изменить все URL на сайте невозможно, можно добавить слушатель события click на все ссылки и отправить асинхронный запрос к PHP скрипту до перехода. Это решение требует JavaScript и не работает при выключенном JS.
// Пример с использованием fetch
document.querySelectorAll('a').forEach(function(link) {
link.addEventListener('click', function(e) {
e.preventDefault();
var url = this.href;
fetch('/log_click.php?url=' + encodeURIComponent(url), { method: 'GET', keepalive: true })
.then(function() {
window.location.href = url;
});
});
});
PHP-скрипт log_click.php может быть таким же, как в базовом решении, но без редиректа (он уже выполняется на стороне браузера).
Ошибки: при быстром уходе со страницы запрос может не успеть завершиться. Используйте параметр keepalive: true или отправляйте через navigator.sendBeacon(). Также возможны блокировки CORS при кросс-доменных ссылках.
Как обрабатывать клики по URL через .htaccess и PHP?
С помощью mod_rewrite можно перенаправить все запросы на определённый PHP-скрипт, который в свою очередь обработает URL из строки запроса. Это позволяет скрыть существование скрипта-посредника и сохранить оригинальный вид ссылок (например, /go/example.com).
RewriteEngine On
RewriteRule ^go/(.+)$ track.php?url=$1 [L,QSA]
В track.php принимаем $_GET['url'] и обрабатываем как обычно. Для безопасности декодируем URL (urldecode) и проверяем.
Проблемы: если в URL содержатся слэши, правило может не сработать. Используйте RewriteRule ^go/(.*)$ и кодируйте слэши при передаче. Также нужно аккуратно настраивать исключения для статических файлов.
Как отслеживать клики с помощью изображения-пикселя (tracking pixel)?
Если необходимо отследить открытие письма или посещение страницы, можно использовать невидимое изображение размером 1x1, src которого ведёт на PHP-скрипт. Сам скрипт ничего не выводит, кроме пустого GIF, и логирует запрос.
// pixel.php
<?php
// Логирование (можно записать, что был запрос с таким-то IP)
file_put_contents('pixel.log', date('Y-m-d H:i:s') . "\n", FILE_APPEND);
// Возвращаем 1x1 прозрачный GIF
header('Content-Type: image/gif');
echo base64_decode('R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7');
?>
В HTML: <img src="pixel.php" width="1" height="1" alt="" />
Недостатки: пиксель может быть заблокирован почтовыми клиентами или расширениями, блокирующими трекинг. Не передаётся информация о URL, на котором размещён пиксель (можно добавить параметры в src: pixel.php?page=home).
Как добавить защиту от CSRF и повторного использования ссылок?
Для предотвращения подделки запросов можно генерировать одноразовый токен, который добавляется в URL вместе с целевым адресом. Токен проверяется в скрипте и после первого использования удаляется из базы. Пример генерации:
$token = bin2hex(random_bytes(16));
Сохраните токен вместе с URL и временем истечения. При обработке проверьте существование и срок действия.
Ошибки: при кэшировании страницы токен может стать недействительным. Используйте JavaScript для динамической подстановки токена или короткий срок жизни (несколько минут).
Расширенные примеры и пояснения
1. Комплексный трекер с фильтрацией и геолокацией
Используем базу данных (SQLite), определяем страну по IP (через сторонний API) и блокируем нежелательные домены.
<?php
$allowedDomains = ['example.com', 'mysite.org'];
$url = $_GET['url'] ?? '';
$parsed = parse_url($url);
$host = $parsed['host'] ?? '';
if (!in_array($host, $allowedDomains)) {
header('HTTP/1.1 403 Forbidden');
exit('Domain not allowed');
}
// Получение страны (через http://ip-api.com/json/)
$ip = $_SERVER['REMOTE_ADDR'];
$geo = json_decode(file_get_contents("http://ip-api.com/json/{$ip}"));
$country = $geo->country ?? 'Unknown';
// Запись в SQLite
$db = new SQLite3('clicks.db');
$stmt = $db->prepare('INSERT INTO clicks (url, ip, country, clicked_at) VALUES (:url, :ip, :country, datetime("now"))');
$stmt->bindValue(':url', $url, SQLITE3_TEXT);
$stmt->bindValue(':ip', $ip, SQLITE3_TEXT);
$stmt->bindValue(':country', $country, SQLITE3_TEXT);
$stmt->execute();
header('Location: ' . $url, true, 302);
?>
Результат: при переходе по /track.php?url=https://example.com выполняется проверка домена, запрос к API геолокации (может замедлить редирект), запись в SQLite, затем редирект. Если домен не разрешён - ошибка 403.
2. Асинхронная отправка через navigator.sendBeacon (без потери данных при закрытии страницы)
// JavaScript (вставляется на все страницы)
document.addEventListener('click', function(e) {
var target = e.target.closest('a');
if (target && target.href) {
e.preventDefault();
var data = new FormData();
data.append('url', target.href);
data.append('page', window.location.href);
navigator.sendBeacon('/beacon.php', data);
window.location.href = target.href;
}
});
// beacon.php (PHP)
<?php
$url = $_POST['url'] ?? '';
$page = $_POST['page'] ?? '';
$line = date('Y-m-d H:i:s') . " | $url | $page | " . $_SERVER['REMOTE_ADDR'] . "\n";
file_put_contents('beacon.log', $line, FILE_APPEND);
http_response_code(204); // No Content
?>
Результат: при клике на любую ссылку данные отправляются асинхронно через sendBeacon (гарантированная доставка даже при закрытии вкладки), затем происходит навигация. В лог записывается время, целевой URL, страница, с которой произошёл клик, и IP.
3. Использование очереди Redis для высоконагруженных проектов
Вместо записи непосредственно в базу данных, PHP-скрипт помещает задачу в очередь Redis, а отдельный worker асинхронно обрабатывает и сохраняет в MySQL.
// track.php (ставит задачу)
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$job = json_encode([
'url' => $_GET['url'],
'ip' => $_SERVER['REMOTE_ADDR'],
'time' => time()
]);
$redis->lPush('click_queue', $job);
header('Location: ' . $_GET['url']);
// worker.php (запускается как демон)
while (true) {
$job = $redis->brPop('click_queue', 5);
if ($job) {
$data = json_decode($job[1], true);
// сохраняем в MySQL
$stmt = $pdo->prepare('INSERT INTO clicks (url, ip, clicked_at) VALUES (?, ?, FROM_UNIXTIME(?))');
$stmt->execute([$data['url'], $data['ip'], $data['time']]);
}
}
Результат: скрипт-перенаправитель почти мгновенно возвращает редирект, а запись в БД выполняется асинхронно, что снижает нагрузку на PHP-FPM и предотвращает задержки.
4. Динамическая генерация ссылок с подписью (HMAC) для предотвращения подмены
// Генерация ссылки на стороне шаблона:
$secret = 'my_secret_key';
$targetUrl = 'https://partner.com/offer';
$expires = time() + 3600; // 1 час
$signature = hash_hmac('sha256', $targetUrl . $expires, $secret);
$trackLink = '/go?url=' . urlencode($targetUrl) . '&exp=' . $expires . '&sig=' . $signature;
// track.php - проверка подписи
$url = $_GET['url'] ?? '';
$expires = (int)($_GET['exp'] ?? 0);
$sig = $_GET['sig'] ?? '';
$expectedSig = hash_hmac('sha256', $url . $expires, $secret);
if (!hash_equals($expectedSig, $sig) || time() > $expires) {
header('HTTP/1.1 403 Forbidden');
exit('Invalid or expired link');
}
// Логирование и редирект
Результат: только ссылки, сгенерированные на сервере с правильным секретом, будут работать. Срок действия ограничен, подделать подпись злоумышленнику практически невозможно.