Когда я добавлял число просмотров рядом с датой публикации и временем чтения, задача выглядела простой: открылась статья — прибавить единицу.
Но запрос к странице не всегда означает, что её открыл читатель. За содержимым приходят поисковые роботы, инструменты проверки и сервисы предпросмотра ссылок. Браузер тоже может заранее загрузить страницу, на которую человек ещё не перешёл.
Если учитывать всё подряд, число будет расти, но его смысл окажется довольно расплывчатым.
Я сделал небольшой счётчик для своего журнала: он считает подходящие открытия опубликованных статей и отбрасывает часть служебных обращений. При этом я сразу отказался от попытки называть полученное число количеством людей.
Что именно я решил считать
Мой счётчик учитывает открытия отдельных публикаций. Посещение главной страницы, просмотр списка материалов или RSS-ленты его не увеличивает.
Для учёта должны выполняться несколько условий:
- открыта отдельная опубликованная статья;
- запрос выполнен методом
GET; - это не предпросмотр записи;
- статья не защищена паролем;
- запрос не распознан как служебный или автоматический.
Отдельно я исключил просмотры пользователей, у которых есть право редактировать записи. Когда я проверяю оформление после публикации, эти открытия не должны увеличивать показатель.
Но если я зайду как обычный посетитель, счётчик не сможет отличить меня от другого читателя. Такого механизма в нём нет.
Почему я исключил HEAD-запросы
При обычном открытии страницы браузер запрашивает её содержимое методом GET.
Метод HEAD позволяет получить заголовки ответа без тела страницы. Такие запросы могут использоваться для проверки доступности и других технических задач.
Я не стал считать их просмотрами. В реализации оставил простое условие: если метод запроса отличается от GET, число не меняется.
Сам по себе GET тоже не доказывает присутствие человека. Это только первый фильтр.
Как я убрал часть обращений роботов
Для следующей проверки я использовал заголовок User-Agent. Он сообщает, каким клиентом представляется отправитель запроса.
Счётчик пропускает обращения с пустым User-Agent и не учитывает ряд характерных обозначений: bot, crawler, spider, headless, Lighthouse и другие.
Так удалось исключить часть поисковых обходов, автоматических проверок и предпросмотров.
Защиты от намеренной накрутки это не даёт. Робот может представиться обычным браузером, а необычный клиент человека — попасть под фильтр.
Поэтому я воспринимаю такую проверку как практическое отсечение очевидных служебных запросов. Определять по одной строке, кто находится по другую сторону соединения, было бы слишком смелым обещанием.
Предварительная загрузка — ещё не чтение
Браузер может получить страницу заранее, чтобы быстрее показать её при возможном переходе. Но сам переход после этого может и не произойти.
В счётчике я проверяю заголовки назначения запроса: Sec-Purpose, Purpose и X-Purpose. Если в них указаны prefetch или prerender, открытие не учитывается.
У этого подхода тоже есть граница: он работает, когда клиент сообщил о предварительной загрузке соответствующим заголовком. Если такого признака нет, счётчик не может уверенно определить назначение запроса.
Мне было важно сделать это ограничение явным. Иначе легко назвать результат точным, хотя реализация лишь отбрасывает известные случаи.
Что я храню в базе
Для счётчика я создал отдельную таблицу WordPress. В ней всего два поля:
post_id — идентификатор статьи
views — число учтённых открытий
Сам счётчик не записывает IP-адреса, идентификаторы посетителей или историю их переходов и не устанавливает cookies.
Заголовки запроса используются для решения, прибавлять единицу или нет. В таблицу просмотров они не сохраняются.
Это описание именно данного механизма. Из него нельзя делать вывод, что на всём сайте отсутствуют другие средства аналитики или серверные журналы.
Почему я не считаю уникальных посетителей
Без различения посетителей нельзя надёжно определить, сколько разных людей открыло статью.
Если один человек дважды загрузит страницу и оба запроса пройдут фильтры, счётчик прибавит две единицы. В моих проверках это предусмотренное поведение.
Я не добавлял запоминание посетителя ради исключения повторных открытий. Поэтому рядом со статьёй написано «просмотры», а не «уникальные читатели».
Даже слово «просмотр» здесь нужно понимать с оговоркой: сервер получил подходящий запрос к статье. Сколько текста после этого прочитали и прочитали ли вообще, этот механизм не измеряет.
Как я учёл одновременные открытия
Сохранить число просмотров можно неудачным способом: сначала прочитать текущее значение, прибавить единицу в PHP, затем записать результат.
Если два запроса одновременно прочитают одно и то же число, оба могут сохранить одинаковый результат. Одно из открытий потеряется.
Я использовал увеличение непосредственно в базе данных одним SQL-запросом. В упрощённом виде логика такая:
INSERT INTO article_views (post_id, views)
VALUES (123, 1)
ON DUPLICATE KEY UPDATE views = views + 1;
Здесь article_views — условное имя таблицы, а 123 — пример идентификатора записи.
Если строки для статьи ещё нет, она создаётся со значением один. Если уже есть — база увеличивает существующее число.
Идентификатор статьи является первичным ключом. Отдельного чтения старого значения перед увеличением не требуется.
Почему я вынес счётчик из темы
Логику учёта я оформил как небольшой MU-плагин WordPress. Такие плагины загружаются автоматически.
Тема отвечает за показ результата: значок и число в строке с датой публикации и временем чтения. Само увеличение счётчика находится отдельно.
Для меня это удобнее, чем смешивать хранение данных с оформлением страницы. Изменение внешнего вида не требует переписывать механизм подсчёта.
Я также добавил правильные формы подписи: «1 просмотр», «2 просмотра», «5 просмотров». Небольшая деталь, но без неё даже работающая функция выглядит недоделанной.
Как я проверил результат
Я проверил несколько сценариев с известным ожидаемым результатом.
Два обычных открытия увеличивали счётчик на две единицы. HEAD-запросы, распознанный робот и запрос с признаком предварительной загрузки число не меняли.
Открытие главной страницы тоже не учитывалось как просмотр статьи. Отдельно я проверил независимые значения для разных публикаций и увеличение счётчика в базе одним запросом.
После технической проверки открыл статью в браузере: число отображалось рядом с датой и временем чтения, а строка не вызывала горизонтального переполнения.
Искусственные открытия, сделанные для теста, я убрал из итоговых значений.
Где остаются ограничения
Счётчик не распознаёт всех роботов и не защищает от человека, который намеренно перезагружает страницу. Он не измеряет время чтения и не определяет уникальных посетителей.
Есть и архитектурная граница: увеличение выполняется в PHP при обработке страницы WordPress. Если в дальнейшем я добавлю полное кеширование HTML с выдачей страницы в обход PHP, учёт придётся пересмотреть.
Поэтому этот показатель полезен как простое число учтённых открытий, а не как доказательство размера аудитории.
Что получилось
Теперь у каждой статьи есть собственный счётчик. Он увеличивается при подходящих открытиях, исключает проверенные служебные сценарии и хранит только общий итог для публикации.
Для моей задачи этого оказалось достаточно. Я получил понятный показатель рядом со статьёй, не добавляя в сам механизм идентификаторы посетителей.
А главный вывод этой доработки — сначала решить, что означает число, и только потом выводить его на страницу. Иначе аккуратная цифра легко создаёт больше уверенности, чем заслуживает способ её подсчёта.
