вторник, 26 октября 2010 г.

[Blogger.com] Скрипт для отображение списка похожих статей

Для блогспота существует множество реализаций виджета похожих постов, начиная от простого списка ссылок и заканчивая навороченными и стильно оформленными виджетами. Для своего блога я перебрал несколько таких скриптов, но ни один из них меня не устроил по тем или иным причинам. Поэтому, взяв за основу код с hoctro.blogspot.com, я написал свой собственный. Собственно, сам скрипт был написал и опубликован еще полгода назад, но изначальная публикация исчезла вместе с удалённым аккаунтом в ЖЖ.

Что же такого может мой скрипт и чего я не нашел в уже готовых виджетах?
Во-первых, это возможность настраивать заголовок скрипта, чтобы он гармонировал с содержимым вашего блога. Согласитесь, если, например, у вас блог посвящен кулинарии и рецептам, то надпись «Еще похожие статьи на тему "Салаты":» будет выглядеть крайне странно. Гораздо лучше написать что-то вроде «Еще рецепты из рубрики "Cалаты":».

Во-вторых, было бы не плохо, если бы компьютер умел различать случаи, когда похожие материалы найдены только по одному ярлыку и когда они найдены по нескольким ярлыкам, и выводить для этих случаев разный текст. Кроме того, я полагаю, обычно, если похожих статей не найдено, то лучше вообще ничего не выводить, но если вам захочется, чтобы в этом случае писалось что-то вроде «Других материалов на эту тему еще не опубликовано», то данный скрипт позволяет сделать и это. Для каждого из этих трёх случаев можно задать в настройках свой вариант текста.

В-третьих, я добавил возможность исключать из поиска отдельные ярлыки. На примере кулинарного блога: если, скажем, вы используете ярлык «нет фото», чтобы отметить рецепты, для которых вы еще не добавили фотографию готового блюда, то скорее всего, вам захочется, чтобы такой ярлык игнорировался при показе посетителям списка похожих рецептов.

И в-четвертых, возможность изменять имена ярлыков и объединять несколько ярлыков в один.

Данный скрипт не оформлен в виде отдельного виджета, а предназначен для встраивания внутрь основного виджета блога — того, который отображает содержимое сообщений. Сделано это, чтобы список похожих материалов располагался непосредственно после содержимого поста, т.е. был сразу виден посетителю при прочтении поста.

Как добавить скрипт похожих постов в ваш блог:
В шаблоне блога находим строку <data:post.body/> — это то место, где выводится содержимое поста. После неё вставляем мой скрипт:

<!-- Скрипт "Связанные статьи из этой категории." -->
<!-- (c) http://izhurnal.blogspot.com/ -->

        <b:if cond='data:blog.pageType == &quot;item&quot;'>
        <b:if cond='data:post.labels'>
            <div class='simposts'>

                <div class='widget-content'>
                <br/>
                  <div id='simpostsdata350471'></div>
                  <div id='simpostsdata350470'></div><br/>
                    <script type='text/javascript'>

                    /* Настройки скрипта. */

                    var headerN = &quot;Похожие статьи:&quot;; /* Заголовок виджета, когда в нём отображается более чем одна метка. */
                    var header1 = &quot;Еще статьи из категории \&quot;__LABEL__\&quot;:&quot;;  /* Заголовок виджета, когда в нём отображается одна метка. Если в заголовке есть слово __LABEL__, то вместо него будет подставлено имя метки. */
                    var header0 = &quot;&quot;; /* Текст, выводимый если нет ни одной метки и ссылки. */

                    var maxNumberOfPostsPerLabel = 5; /* Максимальное число постов, отображаемых для каждой метки. */
                    var maxNumberOfLabels = 4;      /* Максимальное число отображаемых меток. */
                    var excludeEmptyLabels = true;  /* Скрывать ли метки, для которых нет подходящих постов. true - скрывать, false - отображать. */
                    var excludeLabels = []; /* Метки, которые не следует отображать. */

                    /* Смена отображаемых имён.
                       Здесь перечисляем соответствия реального имени метки и названия, которое будет показано на странице.
                       Если для двух меток указать одно название, они при отображении склеятся в одну метку.
                       Если метка здесь не перечисляется, то отображается под своим настоящим названием.
                    */
                    var renameLabels = {
                    };

                    /* Конец настроек скрипта. */

                    var homeUrl3 = &quot;<data:blog.homepageUrl/>&quot;; /* Адрес блога, откуда берём список постов. */

                    function is_in(item, array) {
                      for (k in array) {
                        if (typeof array[k] == &quot;function&quot;) continue;
                        if (array[k] == item) return true;
                      }
                      return false;
                    }

                    function label_title(label) {
                      if (renameLabels[label])
                        return renameLabels[label];
                      return label;
                    }

                    totalLabels = 0;
                    receivedReplies = 0;
                    var receivedItems = [];

                    var k_label;

                    function labelReceived() {
                      receivedReplies++;

                      if (receivedReplies != totalLabels)
                        return;

                      var labelCount = 0;
                      for (k_label in receivedItems) {
                        if (typeof receivedItems[k_label] == &quot;function&quot;) continue;
                        var items = receivedItems[k_label];
                        if (items.length == 0 &amp;&amp; excludeEmptyLabels)
                          continue;
                        labelCount++;
                      }

                      var labelCount1 = 0;
                      for (k_label in receivedItems) {
                        if (typeof receivedItems[k_label] == &quot;function&quot;) continue;
                        var items = receivedItems[k_label];

                        if (items.length == 0 &amp;&amp; excludeEmptyLabels)
                          continue;

                        var ul = document.createElement(&#39;ul&#39;);
                        var itemsCount = 0;
                        for (var k_item in items) {
                          if (typeof items[k_item] == &quot;function&quot;) continue;
                          var item = items[k_item];
                          var li = document.createElement(&#39;li&#39;);
                          var a = document.createElement(&#39;a&#39;);
                          a.href = item.href;
                          var txt = document.createTextNode(item.title);
                          a.appendChild(txt);
                          li.appendChild(a);
                          ul.appendChild(li);
                          itemsCount++;
                          if (itemsCount == maxNumberOfPostsPerLabel)
                            break;
                        }
                        
                        var txt = document.createTextNode(k_label);
                        var h = document.createElement(&#39;b&#39;);
                        h.appendChild(txt);
                        var div1 = document.createElement(&#39;div&#39;);
                        if (!(labelCount == 1 &amp;&amp; header1.search &amp;&amp; header1.search(/(LABEL)/) != -1))
                          div1.appendChild(h);
                        div1.appendChild(ul);
                        document.getElementById(&#39;simpostsdata350470&#39;).appendChild(div1);

                        labelCount1++;
                        if (labelCount1 == maxNumberOfLabels)
                          break;
                      }

                      var txt;
                      if (labelCount == 1 &amp;&amp; header1)
                        txt = document.createTextNode(header1.replace(&quot;__LABEL__&quot;, k_label));
                      else if (labelCount)
                        txt = document.createTextNode(headerN);
                      else if (header0)
                        txt = document.createTextNode(header0);
                      if (txt)
                      {
                        var h = document.createElement(&quot;h4&quot;);
                        h.appendChild(txt);
                        document.getElementById(&#39;simpostsdata350471&#39;).appendChild(h);
                      }
                    }

                    function receiveReply_123(json) {
                      var label = &quot;&quot;;
                      for (var l = 0; l &lt; json.feed.link.length; l++) {
                        if (typeof json.feed.link[l] == &quot;function&quot;) continue;
                        if (json.feed.link[l].rel == &#39;alternate&#39;) {
                          var raw = json.feed.link[l].href;
                          var label = raw.substr(homeUrl3.length+13);
                          var k;
                          for (k=0; k&lt;20; k++)
                            label = label.replace(&quot;%20&quot;, &quot; &quot;);
                          label = decodeURI(label);
                          break;
                        }
                      }
                      var labelTitle = label_title(label);
                      if (typeof receivedItems[labelTitle] != &quot;object&quot;)
                        receivedItems[labelTitle] = [];

                      for (var k in json.feed.entry) {
                        var entry = json.feed.entry[k];
                        if (typeof entry == &quot;function&quot;) continue;
                        var href = &quot;&quot;;
                        for (var kl in entry.link) {
                          if (typeof entry.link[kl] == &quot;function&quot;) continue;
                          if (entry.link[kl].rel == &quot;alternate&quot;) {
                            href = entry.link[kl].href;
                            break;
                          }
                        }

                        if(href != &quot;&quot; &amp;&amp; href != location.href.split(new RegExp("\\?|\\#"))[0]) {
                          var item = { &quot;href&quot; : href, &quot;title&quot; : entry.title.$t};
                          receivedItems[labelTitle].push(item);
                        }
                      }

                      labelReceived();

                    }
                    function sendQueryForLabel(query, label) {
                      var script = document.createElement(&#39;script&#39;);
                      script.setAttribute(&#39;src&#39;, query + &#39;feeds/posts/default/-/&#39;
                        + label +
                        &#39;?alt=json-in-script&amp;callback=receiveReply_123&amp;max-results=&#39;+maxNumberOfPostsPerLabel);
                      script.setAttribute(&#39;type&#39;, &#39;text/javascript&#39;);
                      document.documentElement.firstChild.appendChild(script);
                    }

                    var parsedlabels = [];
                    var labelsCount = 0;

                    function list_items(textLabel) {
                      if (labelsCount &gt;= maxNumberOfLabels ||
                          is_in(textLabel, excludeLabels) ||
                          is_in(textLabel, parsedlabels)) {
                        labelReceived();
                        return;
                      }
                      labelsCount++;
                      parsedlabels.push(textLabel);
                      sendQueryForLabel(homeUrl3, textLabel);
                    }
                    <b:loop values='data:posts' var='post'>
                      <b:loop values='data:post.labels' var='label'>
                        totalLabels++;
                      </b:loop>
                    </b:loop>
                    <b:loop values='data:posts' var='post'>
                      <b:loop values='data:post.labels' var='label'>
                        list_items(&quot;<data:label.name/>&quot;);
                      </b:loop>
                    </b:loop>
                    </script>

                </div>

            </div>
        </b:if>
        </b:if>

<!-- Конец скрипта "Связанные статьи из этой категории." -->

В начале скрипта находим место, помеченное текстом «/* Настройки скрипта. */». Сюда вписываются настройки:

headerN — текст заголовка виджета, когда похожие статьи найдены более чем по одному ярлыку.
header1 — текст заголовка виджета, когда похожие статьи найдены только по одному ярлыку. Если в тексте есть фрагмент __LABEL__, то вместо него будет подставлено имя данного ярлыка.
header0 — текст, если похожих статей не найдено.

maxNumberOfPostsPerLabel — сколько максимально статей отображать для каждого ярлыка.
maxNumberOfLabels — максимальное число отображаемых ярлыков.

excludeEmptyLabels — скрывать ли ярлыки, для которых нет подходящих статей. true - скрывать, false - отображать.

excludeLabels — список ярлыков, которые следует игнорировать.

renameLabels — Здесь перечисляются соответствия реального имени ярлыка и названия ярлыка, которое будет показано на странице. Если для двух ярлыков указать одно название, они при отображении склеятся в одну метку. Если ярлык здесь не указан, то отображается под своим настоящим названием.

Пример настроек скрипта для блога Флеш-обзор:

var headerN = &quot;Похожие игры:&quot;;
var header1 = &quot;Еще игры из категории «__LABEL__»:&quot;;
var header0 = &quot;&quot;;
var maxNumberOfPostsPerLabel = 5;
var maxNumberOfLabels = 4;
var excludeEmptyLabels = true;
var excludeLabels = [&quot;флеш&quot;, &quot;разное&quot;];
var renameLabels = {
    &quot;логическая&quot; : &quot;логические&quot;,
    &quot;стрелялка&quot;  : &quot;стрелялки&quot;,
    &quot;стратегия&quot;  : &quot;стратегии&quot;,
    &quot;не игры&quot;    : &quot;разное&quot;,
    &quot;развлечения&quot;: &quot;разное&quot;,
    &quot;платформер&quot; : &quot;платформеры&quot;,
    &quot;текстовая&quot;  : &quot;текстовые&quot;
};

16 комментариев

Екатерина Михайлова

Завтра займусь установкой к себе на блог, интересно потестировать. Только один вопрос есть. Скрипт достаточно объемный, не сильно ли он влияет на загрузку страницы. И не думали ли вы о варианте, когда скрипт подгружается из файла?

sdc

По сравнению с общим размером даже пустой страницы блога (не учитывая контент статьи), он совсем не объёмный.
Однако, если интернет медленный, возможна небольшая задержка в работе скрипта, вызваная его обращениями к rss блога (число запросов соответствует числу ярлыков поста). Объём данных, получаемых в этих запросах, в несколько раз превосходит размер самого скрипта.

Другое дело, что вынос в отдельный файл удобнее с практической точки зрения: при исправлении ошибок в скрипте, исправленная версия сразу появится на всех блогах. Надо подумать в этом направлении. :)

FUSESOUND

Классный скрипт!
Только в основном вывод постов не релевантный получается, например когда в посте 2 и более ярлыков и надо для релевантного содержания вывести посты только с 2-го или с 3-го ярлыка...
Жаль, этот скрипт лучший но как у всех эта проблема...


И по поводу дублирования, я вот таким вариантом ещё пользуюсь http://bloggerstop.net/2009/04/duplicate-meta-description-and-titles.html

P.S.
Вы можете сделать что-бы коменты можно было от своего имени отправлять?

sdc

«Только в основном вывод постов не релевантный получается, например когда в посте 2 и более ярлыков и надо для релевантного содержания вывести посты только с 2-го или с 3-го ярлыка...»
Не совсем понимаю суть проблемы. Если нужно подавить вывод для некоторых ярлыков, используйте excludeLabels. Если нужно объединить выдачу 2-х и более ярлыков, используйте renameLabels. В вашем случае нужна более сложная логика обработки ярлыков? Я собирался перерабатывать данный скрипт, так что вполне возможно добавить за одно и дополнительных настроек. Опишите подробнее, какой функциональности в данном случае не хватает — если для задачи найдётся решение в общем виде, я внесу соответствующие изменения в скрипт.


«Вы можете сделать что-бы коменты можно было от своего имени отправлять?»
Тоже не совсем понял, что имеется ввиду.

FUSESOUND

...Я имею ввиду следующее:
Например музыкальный пост, у поста ярлыки: Музыка-80, ABBA, POP,
Если скрипт будет выводить статьи только по ярлыку ABBA то это и будет то что надо!
Но подобные скрипты выводят случайные посты со всех ярлыков!

Ваш скрипт позволяет указать вывод определённого количества ярлыков и например если бы была возможность указывать порядок последовательности ярлыков то можно было бы ярлык ABBA указать первым (ABBA, Музыка-80, POP)а в скрипте просто указать количество выводимых ярлыков "1". То и выводились бы более релевантные сообщения!
Но указать последовательность ярлыков нет возможности, они автоматом идут по алфавиту...

Кстати скрипт очень хорошо работает, не заметил что бы он влиял на загрузку страницы!!!

P.S.
По поводу имени - возможность вписывать своё имя в подпись комментариев... Да бог с ним...

sdc

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

Разрешил анонимные комментарии, теперь вроде можно указывать произвольное имя.

FUSESOUND

:) Надеюсь получится, в сети вроде подобного нет...

FUSESOUND

Не знаю понравится Вам моя идея, можно добавить вывод картинки поста...

FUSESOUND

...Сегодня случайно на joomla сайте увидел виджет похожих сообщений который вроде не с ярлыков выводит а именно по ключевым словам..
Не знаю вроде это не возможно?! Ярлыков в посте не нашёл, о!
Можно Ваш комментарий?!

sdc

На мой взгляд, искать похожие статьи анализом ключевых слов — это, по сути, означает реализовать на сайте кривой аналог поисковой системы для определения релевантности текста («карманный Гугл»). Поскольку аналог именно кривой, то результат будет во многих случаях хуже, чем составленный на основании ярлыков.

Возможен также комбинированный вариант: сначала выбрать посты по ярлыкам, а потом определить из них самые похожие по совпадению слов в заголовках постов. В принципе, такое имеет смысл, но, имхо, это тот случай, когда усилия, затрачееные на разработку скрипта, превышают выгоду от его использования: результат будет практически идентичен обычному поиску по ярлыкам.

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

FUSESOUND

...Ну то есть сделать вывод максимально релевантных сообщений не получится?! А на других движках возможно?!
Извиняюсь за вопросы, боюсь Вам это не надо, но если Вы знаете как ранжирует бот то должны понимать как важен такой скрипт, я поэтому за него и цепляюсь... Тем более у меня контент легальный и висеть на пятой странице как то не очень радует, проекту грош цена получается :D...

sdc

Если я вас правильно понял, вы рассчитываете благодаря перелинковке статей при помощи этого скрипта улучшить внутреннюю оптимизацию сайта для поисковика?
Однако, это не работает: ссылки, сформированные при помощи Javascript, не учитываются поисковиками.
То место страницы, где располагается список похожих постов, для поисковой системы выглядит пустым.

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

На других движках — т.е. на полноценных движках, таких как Wordpress, PHP-скрипты работают на сервере, и браузеру/поисковику отдаётся уже готовый текст с обычными ссылками и т.п. Там возможно что угодно.

Скрипт похожих статей я, кстати, переделал для сортировки ярлыков по приоритету, сейчас как раз пишу пост про его подключение и настройку.

FUSESOUND

Да именно для оптимизации!
По поводу движку думаю на DLE переехать (блогспот крови много выпил)) , но с этим пока заморочки много...
Жду пост!!! Ура!!! :)

Павел

Я наверно покажусь ботом, если буду в каждом посте благодарности говорить вам, на самом деле много полезного у вас нашел, как закончу работу ,и если вам будет интересно, покажу как применил ваши скрипты=)

Александр С. Петров

Здравствуйте. Я установил скрипт к себе в блог (он мне подходит по дизайну). Вот только он отображает ссылку и на ту страницу, на которой находишься (в списке похожих статей указана статья, на странице которой находишься). Не подскажите, как можно исправить? Какое-то условие прописать? И в какой части кода?
Заранее благодарю.

seogrot

Отличный скрипт, спасибо!

Отправить комментарий