В погоне за удобством посетителей или умные ссылки

Здравствуйте.
У многих на сайтах есть блок с похожими статьями, как правило их выбирают из тех же категорий или используют теги. Но не все ссылки (статьи) в этих блоках одинаково полезны для посетителей, и если проверить, то на одни кликают часто, на другие крайне редко.
Почему бы не сделать так, что бы оставались только те ссылки которые максимально релевантны статье, причем пользователи, не замечая того, сами решаю какие ссылки им полезны. Так мы убиваем двух зайцев, пользователю удобно, а у сайта улучшаются показатели глубины просмотра и времени на сайте — вовлеченности пользователей.
Ниже привожу свои потуги на реализацию данного решения. Сразу хочу отметить, что я далеко не программист, поэтому думаю, что многое можно сделать намного лучше и правильнее и в моем решении могут присутствовать косяки.
Если вас так же как и меня интересует данная идея, предлагаю совместно улучшать, а если кто либо способен создать нормальный пакет, это было бы превосходно.
Начну
Потребуется создать:
1. чанк — [[$smartchank?]];
2. сниппет [[!ajaxsmart?]] — обработчик;
3. [[smartsnipetoutput? &smartblname=`smartblock`]] сниппет вывода, где &smartblname=`smartblock` имя tv в которую записываем;
4. одно (или несколько, зависит от числа блоков с ссылками) доп. поле — smartblock (доступно для шаблона со статьями)

Все что приведу ниже, это просто скопировал с своего сайта, в вашем случае возможно что-то придется допиливать.
Структура сайта: категория — подкатегория — статьи
1. Вывод материалов на странице статьи
<div class="post">
		<ol>			   
                [[pdoResources?
			&cache=`1` &cacheTime=`864000`
			&select=`pagetitle,longtitle,uri,id`
                        &parents=`[[pdoField?   &id=`[[*id]]` &top=`2`  &field=`id`]]`
                        &depth=`1`
                        &limit=`3`
                        &tpl=`tpllinkpost`
                        &hideContainers=`1`
                        &resources=`[[smartsnipetoutput? &smartblname=`smartblock`]]-[[*id]]`
                ]]
                </ol>                
            </div>
где
[[pdoField?   &id=`[[*id]]` &top=`2`  &field=`id`]]
— получаем id категории
шаблон tpllinkpost
<a data-action="smartblock,[[+id]],3" class="ajax_link" title="[[+pagetitle]]" href="[[++site_url]][[+uri]]">[[+longtitle]]</a>
основное здесь это
data-action=«smartblock,[[+id]],3» где smartblock имя tv — придуманное название для блока со ссылками (если блоков на странице несколько для каждого свое tv), 3 -число ссылок в блоке (на ваше усмотрение)
[[+id]] — id документа по ссылке
[[smartsnipetoutput? &smartblname=`smartblock`]] — сиппет который из общий счетчик кликов, id1 статьи, счет кликов по id1, id2 статьи, счет кликов по id2, id3 статьи, счет кликов по id3 делает id1,id2,id3,
[[smartsnipetoutput? &smartblname=`smartblock`]] где &smartblname=`smartblock` имя tv из которой читаем.

<?php
$smartblock= $modx->resource->getTVValue("$smartblname");
$strcount = strlen($smartblock);

if($strcount>1) 
  { $smartmas = explode(",", $smartblock);
    $smartmascount=count($smartmas);
     $i=1;
     while ($i<($smartmascount-1)) 
     
     {   echo $smartmas[$i].",";
         $i=$i+2;
     }
  }
2. в шаблоне вызываем чанк и сниппет
[[$smartchank?]]
<script type="text/javascript">
$(document).ready(function() {
	// Вешаем обработчик события "клик" на все ссылки с классом ajax_link
	$('a.ajax_link').click(function() {
	
	// Берем действие из атрибута data-action ссылки
	var action = $(this).data('action');
	
	// Ajax запрос к текущей страницы (а на ней наш сниппет) методом post
	$.post(document.location.href, {action: action}, function(data) {
	
		$('#result').html(data);
	})	
  })
})
</script>
[[!ajaxsmart?]]
<?php
// Откликаться будет ТОЛЬКО на ajax запросы
if ($_SERVER['HTTP_X_REQUESTED_WITH'] != 'XMLHttpRequest') {return;}
// Сниппет будет обрабатывать не один вид запросов, поэтому работать будем по запрашиваемому действию
// Если в массиве POST нет действия - выход
if (empty($_POST['action'])) {return;}
  
$res2 = $_POST['action'];
$input = explode(",", $res2);
$nblock = $input[0]; //получаем имя tv 
$idlink = $input[1]; // получаем id (документа) ссылки по которой кликнули
$lenblok = $input[2]; // получаем число ссылок в блоке (должно быть одинаковое со значением  &limit=`3` в pdoResources)
  

        $smartblock= $modx->resource->getTVValue($nblock);
       
        $strcount = strlen($smartblock);
        
        if($strcount<=1)  {
              $resource = $modx->resource;//получили объект ресурс
              $idpar = $modx->resource->get('parent');//получили его родителя
              
              $res = $modx->getObject('modResource',$idpar);//получили объект родитель
              
              
              
                $parentId= $modx->getParentIds($idpar, 1);
                $childIds = $modx->getChildIds($parentId[0], 1);
                $result = array();
                
                $q = $modx->newQuery('modResource', array('parent:IN' => $childIds));
                $q->select('id');
                
                if ($q->prepare() && $q->stmt->execute()) {
                	while ($row = $q->stmt->fetch(PDO::FETCH_ASSOC)) {
                		$result[] = $row['id'];
                	}
                }
                $idallmas = $result;
                
                $len = count($idallmas)-1;
               
                function RSUI($n, $min, $max) {
                    $numbers = array();
                    while (count($numbers) < $n) {
                        $number = mt_rand($min, $max);
                        if (!in_array($number, $numbers)) {
                            $numbers[] = $number;
                        }
                    }
                return $numbers;
                }
                
                $rndmas=RSUI($lenblok, 0, $len); //&lenblok=`3` параметр сниппета
                $x=1;
                $idmasout[0]=1;
                for ($i=0; $i<$lenblok; $i++) {
                   $idmasout[$x]=$idallmas[$rndmas[$i]];
                   $x=$x+1;
                   $idmasout[$x]=0;
                   $x=$x+1;
                }  
                $strtv = implode(",", $idmasout);
               
                $resource->setTVValue($nblock, $strtv);
                $resource->save();
                $resource->clearCache();
                
          }
	    
	    else {
        		$resource = $modx->resource;//получили объект ресурс
                $id = $modx->resource->get('id');//id текущего ресурса
                $massmart = explode(",", $smartblock);
                $len = count($massmart);
              
                $min=$massmart[1];  $minid=1; 
                $massmart[0]=$massmart[0]+1;
                
                for ($i=1; $i<$len; $i++) {
                    
                     if(($i % 2) == 0){
                         
                        if ($min>$massmart[$i]) {$min=$massmart[$i]; $minid=$i;}
                     }
                     
                     else { 
                             if ($massmart[$i]==$idlink) {$massmart[$i+1]=($massmart[$i+1])+1;}
                    }
                     
                       
                } 
                
              
                  if ($massmart[0]>9) {
                    
                          $resource = $modx->resource;//получили объект ресурс
                          $idpar = $modx->resource->get('parent');//получили его родителя
                          
                          $res = $modx->getObject('modResource',$idpar);//получили объект родитель
                          $idparentglav =$res->get('parent');//получили родителя-родителя 
                          
                          
                            $parentId= $modx->getParentIds($idpar, 1);
                            $childIds = $modx->getChildIds($parentId[0], 1);
                            $idallmas = array();
                            
                            $q = $modx->newQuery('modResource', array('parent:IN' => $childIds));
                            $q->select('id');
                            
                            if ($q->prepare() && $q->stmt->execute()) {
                            	while ($row = $q->stmt->fetch(PDO::FETCH_ASSOC)) {
                            		$idallmas[] = $row['id'];
                            	}
                            }
                           
                       
                        $len1 = count($idallmas)-1;
                        $number = mt_rand(0, $len1);
                        $massmart[$minid-1]=$idallmas[$number];
                        for ($i=2; $i<$len; $i=$i+2) {$massmart[$i]=0;} 
                        $massmart[0]=1;
                }
                 
                $strtv = implode(",", $massmart);
                echo $strtv;
                $resource->setTVValue($nblock,$strtv); //записать в tv 
                $resource->save();
                $resource->clearCache();
		
		$uri= $modx->resource->get('uri');
                $uri="http://".$_SERVER['SERVER_NAME']."/".$uri;
                file_get_contents($uri);
        }
 
  
 // Если у нас есть, что отдать на запрос - отдаем и прерываем работу парсера MODX
if (!empty($res2)) {
	die();
}

Логика
1. Пользователь кликает по ссылке с похожими статьями
2. id материала и придуманное название блока со ссылками (нужно для размещения нескольких блоков на странице) передаем в сниппет ajaxsmart, через ajax (спасибо Василию за статью)
3. проверяем, если доп. поле пустое, записываем в него случайные статьи (в моем случае 3 шт)
формат доп. поля
общий счетчик кликов, id1 статьи, счет кликов по id1, id2 статьи, счет кликов по id2, id3 статьи, счет кликов по id3,…
4. если поле уже не пустое, находим id (статьи) переданной по ссылке и прибавляем для нее счетчик (+1)
5. Если было более 9 кликов (общий счетчик) (так мало установил для теста)
if ($massmart[0]>9)
, то находим id документа с минимальным числом кликов (счетчиком кликов по ссылке) и заменяем ее на другой id полученный из категории (а не из подкатегории) (обратите внимание — это в моем случае, в ваше случае может быть иначе, зависит от структуры сайта)
Косяки которые нашел, но не устранил
1. в наше id, после случайной выборки может попасть id которое уже есть в списке
2. в наше id, после случайной выборки может попасть id самой статьи

Отказ от ответственности
Звучит мощно :)
Так как по моему коду сразу видно уровень моих знаний, прошу строго не судить, при реализации могут быть проблемы которые я не смогу решить.
Надеюсь будут грамотные специалисты которых заинтересует такая задача и будет реализован готовый пакет, так же с радостью принимаются любые доработки и улучшения.
Надеюсь пригодится не только мне и спасибо сообществу за помощь и куски кода которые подсказывали.
Борис И
06 июня 2016, 17:03
modx.pro
20
3 331
+12

Комментарии: 15

Богдан
07 июня 2016, 10:41
0
Очень полезная вещь! Давно думал сделать что-то подобное, никак руки не доходили. Применять можно не только на статейных сайтах или блогах. В интернет-магазинах часто есть блок что-то вроде «С этим товаром также просматривают», и такое решение подошло бы идеально, что уверен, повысило бы конверсию.
    Павел Левин
    08 июня 2016, 12:51
    0
    Молодец, отличные решения для данной задачи, ранее задавался тем же вопросом в итоге пришел к решению прописывать id документов — рутина.
      Борис И
      08 июня 2016, 13:35
      0
      Все бы хорошо, но вот то, что приходится чистить кеш страницы $resource->clearCache(); после каждого клика, по ссылкам в блоке, это плохо. Как это обойти я не нашел.
      Может быть можно как то страницу, после всех манипуляций, запихнуть обратно в кеш, но как я не додумался.
        Павел Левин
        08 июня 2016, 13:46
        0
        Ответить не смогу т.к. не знаю, но вот есть наводка. Там Василий делится опытом.
      Борис И
      08 июня 2016, 13:53
      0
      Кстати, а почему на modx.pro нет блока с похожими статьями. База знаний здесь накопилась уже солидная, сам если что-то нужно по MODX, сначала ищу здесь и только потом обращаюсь к яндексу (гуглу).
      Конечно с поиском по modx.pro все отлично, но и блок бы, на мой взгляд, не помешал.
        Василий Наумкин
        08 июня 2016, 14:19
        +1
        Кстати, а почему на modx.pro нет блока с похожими статьями
        Потому что я не знаю, как определить их «похожесть».
          Борис И
          08 июня 2016, 15:10
          0
          Хотел было ответить, что выводить можно схожим образом, из категории и далее заменой наименее кликабельных статей на другие.
          Но посмотрел и задумался, даже в категории вопросы, из-за их разнообразия, нужно на основе чего-то делать первичную выборку (иначе кликать толком не будут — первичные материалы будут полностью нерелевантны).
          В общем, жаль.
            Павел Левин
            08 июня 2016, 17:33
            0
            Как вариант заполнять поле «ключевые слова», тогда там можно найти некую схожесть + учитывать клики и получится довольно релевантные результаты. Как мне кажется.
              Борис И
              08 июня 2016, 18:12
              0
              Как вариант, но с ключевыми придется определять разные словоформы, а это уже дополнительная проблема, плюс, если это делают сами пользователи, ошибок не избежать, а один админ надорвется. Самое простое это учет кликов на большом промежутке времени, так сказать набрать статистику. Рано или поздно должны будут попасть релевантные статьи, но до этого времени будет не очень красиво (статья об одном, а в блоке совсем другое).
              На modx.pro при написании вопроса есть графа «Вопрос связан с работой дополнения:», может быть можно играть и от этого, но опять же, его заполняют сами пользователи и не всегда.

                Павел Левин
                08 июня 2016, 18:47
                0
                Еще есть вариант сделать расширение для платного дополнения msearch2 по сути, выдавая первые варианты результатов поиска и как-то туда пропихивать приоритеты, но тут не знаю. М.б. все тяжело работать будет.
            Василий Столейков
            08 июня 2016, 18:25
            0
            Всегда использовал для таких целей сниппет getRelated — настраивал его так, чтобы он подставлял нужные id-шки getTickets например. Не помню как он определяет похожесть, но кажется определяет по ключевым словам из полей (например названия).
            Павел Левин
            08 июня 2016, 14:23
            0
            Резонный вопрос =)
            Это было бы зверски удобно. Помню на некоторых форумах, бот сразу отвечает ссылками с заголовками тем форума.
            На самом деле подобное расширение достойно быть платным — яб купил.
            Борис И
            08 июня 2016, 19:26
            0
            Для борьбы с проблемой
            3. приходится чистить кеш
            $resource->clearCache();
            добавил после $resource->clearCache();

            //получаем страницу, для отправки в кеш (пользователь этого не видит он переходит на другую страницу)
                            $uri= $modx->resource->get('uri');
                            $uri="http://".$_SERVER['SERVER_NAME']."/".$uri;
                            file_get_contents($uri);
            потестил, страница отправляется в кеш
              Борис И
              11 июня 2016, 17:08
              0
              Сегодня попробовал добавить еще один такой блок, пришлось решение чуть переделать.
              При изменение статьи, мог где-то накосячить, старался проверить, но кто его знает.
              Перед внедрением, тестируйте и еще раз тестируйте, чем и сам продолжаю заниматься.
              Удачи.
                Борис И
                02 сентября 2016, 21:53
                0
                Мой пост уже не актуален, уже есть нормальная реализация, без моих костылей: seetoo
                  Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
                  15