Еще одна статья про индексацию ajax-сайтов поисковиками

    image

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

    Самым правильным решением является использовать обычные ссылки, но подгружать контент ajax-ом, оставляя возможность получить контент по обычной ссылке для пользователей c отключенным JS (мало ли) и роботов. То есть инзачально нужно разрабатывать по старинке, с обычными ссылками, layout-ом и view-шками, затем можно обработать все ссылки javascript-ом, навесить на них подгрузку контента через ajax используя ссылку из аттрибута href, тега a, в очень упрощенном виде это должно выглядеть примерно так:

    $(document).on('click', 'a.ajaxlinks', 'function(e) {
      e.stopPropagation();
      e.preventDefault();
    
      var pageurl = $(this).attr('href');
    
      $.ajax({
        url: pageurl,
        data: {
          ajax: 1
        },
        success: function( resp ) {
          $('#content').html(resp);
        }
      });
    });
    

    Здесь мы просто подгружаем те же страницы, но ajax-ом, при этом на бэкэнде нужно обработать специальный GET-параметр ajax и при его наличии отдавать страницу без layout-а, ну если грубо.

    Но не всегда архитектура на это расчитана, к тому же сайты на angularjs, и ему подобным, работают несколько сложнее, и подставляют контент на подгруженный html-шаблон с переменными. Для таких сайтов (или можно уже назвать их приложениями) поисковые системы придумали технологию HashBang, в кратце — это ссылка вида example.com/#!/cats/grumpy-cat, когда поисковый робот видит #! он делает запрос на сервер по адресу example.com/?_escaped_fragment_=/cats/grumpy-cat, т.е. заменяет «#!» на «?_escaped_fragment_=», и сервер должен отдать сгенерированный html поисковику, идентичный тому, который увидел бы по изначальной ссылке пользователь. Но если в приложении используется HTML5 History API, и не применяются ссылки вида #!, нужно добавить в секцию head специальный мета тег:
    <meta name="fragment" content="!" />
    

    При виде этого тега, поисковый робот будет понимать, что сайт работает на ajax, и будет переадресовывать все запросы на получение контента сайта на ссылку: example.com/?_escaped_fragment_=/cats/grumpy-cat вместо example.com/cats/grumpy-cat.

    Можно обрабатывать эти запросы средствами используемого фреймворка, но в сложном приложении с angularjs — это куча лишнего кода.

    Путь которым мы пойдем описан на следующей схеме от гугл:
    image

    Для этого мы будем ловить все запросы с _escaped_fragment_ и отправлять их на phantom.js на сервере, который средствами серверного webkit будет генерировать html-слепок запрашиваемой страницы и отдавать его краулеру. Пользователи же останутся работать на прямую.

    Для начала установим необходимый софт, если не установлен еще, примерно так:
    yum install screen
    npm instamm phantomjs
    ln -s /usr/local/node_modules/phantomjs/lib/phantom/bin/phantomjs /usr/local/bin/phantomjs
    

    Далее напишем (ну или возьмем готовый) серверный js-скрипт (server.js), который будет делать html-слепки:
    var system = require('system');
    
    if (system.args.length < 3) {
        console.log("Missing arguments.");
        phantom.exit();
    }
    
    var server = require('webserver').create();
    var port = parseInt(system.args[1]);
    var urlPrefix = system.args[2];
    
    var parse_qs = function(s) {
        var queryString = {};
        var a = document.createElement("a");
        a.href = s;
        a.search.replace(
            new RegExp("([^?=&]+)(=([^&]*))?", "g"),
            function($0, $1, $2, $3) { queryString[$1] = $3; }
        );
        return queryString;
    };
    
    var renderHtml = function(url, cb) {
        var page = require('webpage').create();
        page.settings.loadImages = false;
        page.settings.localToRemoteUrlAccessEnabled = true;
        page.onCallback = function() {
            cb(page.content);
            page.close();
        };
    //    page.onConsoleMessage = function(msg, lineNum, sourceId) {
    //        console.log('CONSOLE: ' + msg + ' (from line #' + lineNum + ' in "' + sourceId + '")');
    //    };
        page.onInitialized = function() {
           page.evaluate(function() {
                setTimeout(function() {
                    window.callPhantom();
                }, 10000);
            });
        };
        page.open(url);
    };
    
    server.listen(port, function (request, response) {
        var route = parse_qs(request.url)._escaped_fragment_;
        // var url = urlPrefix
        //   + '/' + request.url.slice(1, request.url.indexOf('?'))
        //   + (route ? decodeURIComponent(route) : '');
    
        var url = urlPrefix + '/' + request.url;
    
        renderHtml(url, function(html) {
            response.statusCode = 200;
            response.write(html);
            response.close();
        });
    });
    
    console.log('Listening on ' + port + '...');
    console.log('Press Ctrl+C to stop.');
    

    И запустим его в скрине с помощью phantomjs:
    screen -d -m phantomjs --disk-cache=no server.js 8888 http://example.com
    

    Далее сконфигурируем nginx (apache аналогично) на проксирование запросов на запущенный демон:
    server {
      ...
    
      if ($args ~ "_escaped_fragment_=(.+)") {
        set $real_url $1;
        rewrite ^ /crawler$real_url;
      }
    
      location ^~ /crawler {
        proxy_pass http://127.0.0.1:8888/$real_url;
      }
     
      ...
    }
    

    Теперь при запросе example.com/cats/grumpy-cat поисковые роботы будут обращаться по ссылке example.com/?_escaped_fragment_=cats/grumpy-cat, которая перехватится nginx-ом, отправится phantomjs-у, который на сервере через браузерный движок сгенерирует html и отдаст его роботу.

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

    Ссылки:
    https://developers.google.com/webmasters/ajax-crawling/docs/getting-started
    https://help.yandex.ru/webmaster/robot-workings/ajax-indexing.xml

    UPD (2.12.16):
    Конфиги для apache2 от kot-ezhva:

    В случае если используется html5mode:
    RewriteEngine on
    RewriteCond %{QUERY_STRING} (.*)_escaped_fragment_=
    RewriteRule ^(.*) 127.0.0.1:8888/$1 [P]
    ProxyPassReverse / 127.0.0.1:8888/
    

    Если урлы с решеткой:
    RewriteEngine on
    RewriteCond %{QUERY_STRING} _escaped_fragment_=(.*)
    RewriteRule ^(.*) 127.0.0.1:8888/$1 [P]
    ProxyPassReverse / 127.0.0.1:8888/
    
    Поделиться публикацией

    Комментарии 60

      +3
      Уже 2015 год и мне не понятно в какой ситуации у пользователя может быть отключен javascript.
        +5
        Ну тут больше для ботов, но первый способ и для пользователей без js «в вакууме» подойдет.
          +7
          Параноиков с NoScript'ом и в 2015м году хватает, другое дело, что всучить средства для увеличения или уменьшения какой-нибудь части тела им тяжело, так что коммерческой ценности у них, конечно, немного…
            +3
            Дело даже не в этом, настоящую ссылку можно скопировать, перетащить или открыть колесиком мыши в новой вкладке. Да и сам результат перехода (даже если не было перехода) должен иметь уникальную ссылку, чтобы можно было ей с кем-нибудь поделиться.
            Ну и отказоустойчивость. Внешний сайт с JavaScript-библиотекой упал, что выведет большинство сайтов? А ничего. Ладно хоть просьбу/напоминание включить JavaScript иногда все же не забывают. А если что-то заблокировано (здравствуйте, современные реалии) или просто сломалось? Очень редко пользователь получит уведомление, что что-то не в порядке (мол, попробуйте обновить страничку или там проверьте свой блокировщик рекламы), все больше тупо не работает.
            А по поводу отключения – так это из-за сайтов, перенасыщенных тормозящими рюшечками, которые иногда вообще только мешают собственно читать полезную информацию.
              0
              Уже 2015 год, и уж пару лет не иметь включенного JS при использовании TOR считается best practice. Вот в 2013 вопрос был более уместен.
                0
                Хмм… У меня он, например, запрещен, так как:
                1. Все «доверенные» сайты находятся в белом списке, банк-клиент работает
                2. Снижается вероятность заражения от случайных переходов по ссылке, от загрузок непонятных iframe'ов и т.д. Например, при открытии интернет-магазинов из яндекс-маркета.
                3. В большинстве случаев мне от сайта нужна только текстовая информация, а не прыгающее нечто (в стиле «Я менеджер N, у Вас есть вопросы?»).
                4. Социальные сети в бане, так что не работают умные загрузчики кнопочек и пр.

                +1
                  0
                  Да, их много разных есть, я только один из описал.
                    +2
                    Ну собственно, prerender(кстати его автор — хабровчанин) делает то же самое, что описано в вашем топике, если я всё правильно понял, конечно.
                      0
                      Да, но с дополнительными плюшками, на сколько я вижу, типа кеширования у них на сервере.
                        +1
                        Не обязательно у них — проект открытый, можно настроить на своём сервере.
                          0
                          Мы тоже им пользовались почти полгода. Для проекта с 150 000 страниц он стал работать на редкость не стабильно — часто 5xx ошибки, проблемы с чисткой ошибочно отрисованных.

                          Взяли исходники, которые лежат на github в упрощённом виде, без веб-интерфейса и очередей, изучили и сделали свой github.com/icons8/impresser
                          Документация — не пинайте ногами, пожалуйста :( — нет вообще, даже в коде комментариями
                          Сейчас отрисовывает полмиллиона страниц, по 3-5 секунд на каждую, но делает это регулярно и поисковым ботам выдаёт результат за миллисекунды.
                          Требует выделенный сервер, потому что фантом очень жрёт процессор и на VDS упирается именно в производительность виртуальных процессоров. Мы взяли один в США, чтобы было максимально близко от серверов, на которых работают гуглоботы.

                          Зачем prerender/impresser, если гуглобот умеет исполнять Javascript?
                          Во-первых, гуглобот не понимает, что страница ждёт данных через AJAX — в результате в поисковый индекс попало много пустых страниц с крутящийся картинкой-загрузчиком. У prerender есть специальный флаг в javascript, чтобы подождать, пока страница не готова.
                          Во-вторых, Yandex, Bing, Yahoo и много других поисковых систем не умеют исполнять JS и видят вообще пустые страницы.
                          В-третьих, боты социальных сетей не умеют исполнять Javascript. В результате, например, Facebook не видит название страницы и иллюстрацию, если теги Open Graph динамически заполняются через Angular.

                          Попробуйте сравнить результаты:
                          Инструкция take.ms/BzPOr черех Google Chrome.
                          Откройте страницу ic8.link/very_basic в одной обычной вкладке браузера и в другой вкладке в режиме эмуляции устройств с UserAgent = «googlebot».
                          HTML страницы загружаются быстрее и вообще не содержат никакого JS. Их можно шарить через соц сети (пример take.ms/hURcN для страницы ic8.link/12243)
                          +3
                          Меньше всего при индексировании поисковиками мне нужно кеширование HTML-слепка на сервере.
                    +1
                    Минус не ставлю, но честно говоря гугл и яша описали данный материал гораздо лучше хотя и без привязки с какому-либо языку.
                    Так же могу сказать — да, это работает, я примерно так комменты из disqus показываю. прямо без форматирования. Все равно их только поисковик увидит. Ищется норм.
                      0
                      А зачем так каменты ботам отдавать? Dusqus и без этого прекрасно индексируется.
                        0
                        интересно каким это образом? я когда сделал так как я сказал у меня сайт за неделю посещалки раза в 3 больше набрал
                      0
                      Такой вопрос: после загрузки веб приложения делается несколько ajax запросов для получения контента. То есть пока эти запросы не выполнены и полученный контент не отображен, нельзя отдавать поисковику страницу. Как вы ловите этот момент?
                        +1
                        В данном конкретном случае стоит простой таймер, а в идеале там коллбек есть специальный, который нужно вызывать после окончания всех загрузок, см. github.com/steeve/angular-seo

                        Еще важный ньюанс с инфинити скроллом есть, там уже костыли нужны.
                        –3
                        Для поисковиков вообще верстка не нужна, им нужна информация, желательно семантически размеченная, для чего schema.org и JSON-LD и существуют, а если объединить с веб-компонентами (во избежание перегруженных view, которые в итоге заменяются несколькими строчками) то получается вообще всё хорошо.
                        Делал недавно перевод, который был практически проигнорирован (по моему мнению не заслужено) хабром, там как раз об этом написано.
                          +6
                          Кажется гугл не очень хорошо относится к тому, что ему подсовывают не то, что пользователям (другую верстку).
                            0
                            По-моему Google:

                            1. активно разрабатывает и продвигает веб-компоненты
                            2. активно разрабатывает и продвигает семантическую разметку
                            3. при использовании семантической разметки лучше понимает что находится на сайте
                            4. при использовании семантической разметки показывает расширенные сниппеты в поисковой выдаче (возможно и позиции самой страницы выше)

                            Кроме Google над семантикой работает множество других компаний, включая Yandex, Yahoo, Bing и другие. Им даже проще читать семантическую разметку чем миллион тупых бесполезных блочных элементов, которые имею непонятные классы, 20 уровней вложенности и вообще сверстаны непонятно как.

                            А какие у вас доказательства?
                              +1
                              К тому же пользователю мы подсовываем ровно то же самое, та же страница, те же семантически размеченные данные подставляются в веб-компоненты для рендеринга пользователю. Нет дублирования — все всё видят (в том числе читалки и другие гаджеты/машины), все счастливы.
                                +1
                                Это да, все верно, если и пользователю, и гуглу одинаково хорошая и правильная верстка отдается, но где-то я, не раз читал, что гугл подозрительно относится, если ему подают именно другую верстку, пусть даже более правильную, без лишнего.
                                  0
                                  Так в том и фишка, что в предложенном варианте нет другой верстки, она одна и та же для всех.
                                    +1
                                    Ну значит я неверно интерпритировал вашу фразу:)
                                    Для поисковиков вообще верстка не нужна, им нужна информация, желательно семантически размеченная

                                      0
                                      Имелось ввиду, что страница одна, но поисковик обращает внимание на сухие данные, верстка ему не важна в принципе, потому чем проще получить данные — тем лучше, с другой стороны пользователю важно видеть удобную страницу, но так как данные те же, семантическая разметка используется и как входные данные для пользовательского интерфейса. Вот и получается, что одной специально составленной страницей оставляем довольными и поисковиков, и пользователей.
                              0
                              Гугль стили тоже использует и проверяет удобочитаемость страниц, особенно для мобильных.
                              +6
                              Ндааа… Знатный изврат. Вам это действительно нравится?
                                0
                                Это самый быстрый способ.
                                +1
                                Гугл уже несколько лет спокойно
                                парсит ajax сайты о чем можно убедится используя их Webmaster Tools.
                                Отсюда реально куча статей, в том числе и из недр самого google давно устарели.
                                За яндекс не знаю.
                                  0
                                  Есть нюансы:)
                                    0
                                    Будьте добры, в студию ньюансы,
                                    реально последнее время не наблюдаю ни одного.
                                    Буду признателен узнать хоть один сохранившийся по наше время.
                                      +1
                                      Facebook Share например без этого не понимает, гугл тоже я как-то ранее проверял контент, конечно, получал, но не весь, то есть не все аяксы вызывались, не знаю с чем было связано.
                                    0
                                    чуть выше: habrahabr.ru/post/254213/#comment_8546193
                                    Во-первых, гуглобот не понимает, что страница ждёт данных через AJAX — в результате в поисковый индекс попало много пустых страниц с крутящийся картинкой-загрузчиком. У prerender есть специальный флаг в javascript, чтобы подождать, пока страница не готова.
                                    Во-вторых, Yandex, Bing, Yahoo и много других поисковых систем не умеют исполнять JS и видят вообще пустые страницы.
                                    В-третьих, боты социальных сетей не умеют исполнять Javascript. В результате, например, Facebook не видит название страницы и иллюстрацию, если теги Open Graph динамически заполняются через Angular.
                                  • НЛО прилетело и опубликовало эту надпись здесь
                                      0
                                      Проект уже написан так до меня, не расчитан был на это.
                                        0
                                        примеры в студию, пожалуйста
                                        • НЛО прилетело и опубликовало эту надпись здесь
                                        –6
                                        Какие есть еще аргументы в пользу таких архитектур, кроме «Стильно, модно, молодежно»?
                                          0
                                          Стильно, модно и молодежно — это я о самом аяксе, а это решение — быстро, это часто серьезный аргумент.
                                            0
                                            Сделать два интерфейса, для роботов и для не роботов — это быстрее чем один? Это вряд ли. Получить проблемы от их рассинронизации — наверняка. Терять лояльность посетителей из-за неадекватных ссылок в поисковой выдаче — себе во вред. Минусов может появиться много, я же спрашиваю аргументы за плюсы, в чем они? Аякс понятное дело помогает с контентом на странице, но в чем плюсы полной подмены события клика на ссылку?
                                              0
                                              так просто добавился посредник, гугл и пользователь получат идентичный html, но у пользователя сгенерится все в его барузере, а для гугла сгенерится все в серверном браузере.
                                              плюс — простота и скорость реализации, надо было простое и быстрое решение, можно было сделать иначе и хорошо, но проект не был на это расчитан, не спрашивайте почему, не я писал. А про подмену клика на ссылку — например сайт lovi.fm, где я это делал, играет радио, страницу перезагружать нельзя, а страницы перелистывать надо.
                                          +1
                                          Никого ни к чему не принуждаю, но вот вам почитать одну интересную мысль.
                                            0
                                            Знаете, я согласен, лично я не люблю перегруженные js-ом сайты, я вообще поклонник старых ламповый сайтов и старого не-флат дизайна, но заказчики — это заказчики, всем нужны рюшечки:)
                                            0
                                            Такой вопрос, какая нагрузка была на данный проект? Часто слышно про проблемы утечки памяти nodejs
                                              0
                                              Проект еще в бэте, только тестируется, пока сказать нечего. С явными утечками ранее не стыкался, было залипало, но тот же апач залипал чаще.
                                              0
                                              Использовать такой подход для индексации сайта Google-ом — согласен. С Яндекс-ом — сложнее. Чтобы не повторяться, оставлю ссылку на мой вопрос на toster-е.
                                                0
                                                Что-то я всех этих экзерсисов не понял вовсе, особенно про escaped_fragment и hashbang… В чем проблема сделать целиком неаяксовый сайт, и на меню повесить тупейший JS, который грузит в content содержимое со своей ссылки и меняет урл на полный, а для тех, у кого History Api не поддерживается(а такие есть вообще) — пусть по ссылкам ходят, да и все. Серверный вебкит — очень сильно
                                                  0
                                                  Это хорошо, но с тем же angular есть ньюансы, например в js передается html шаблон с переменными и js подставляет туда значения, в таком случае генерация полноценного html для роботов уже усложняется.
                                                    0
                                                    Я, к сожалению, не могу понять о чем вы вообще ведете речь — какая генерация полноценного html для роботов, о чем вы? А то, что движок отдает контент по конкретному запросу — это не HTML разве?
                                                      0
                                                      angular так сделан, что отдает html-шаблон с переменными и отдельно получает данные в json, потом совмещает на клиенте.
                                                        0
                                                        И что же мешает на конкретной странице вписать нужные данные в json и отобразить их?
                                                          0
                                                          Много дополнительного кода, плюс из текущей архитектуры выкрутиться не так просто уже, увы.
                                                            0
                                                            Нет, я до сих пор решительно не понимаю, какой может быть дополнительный код в проставке текущих значений в плейсхолдеры. Что-то с ног на голову все встало, как может быть статичная версия реализована тяжелее, чем аяксовая? Либо вы меня не понимаете, либо я — вас
                                                              0
                                                              Вся статья, собственно, повествует о случае, когда статическая версия никак не реализована. Код на JS для подстановки «текущих значений в плейсхолдеры», написанный для браузера — есть, написанного для исполнения на сервере — нет. И «количество кода» и всё остальное меряется вот от этого вот состояния, когда AJAX-версия есть, а статической — нет. Чего тут непонятного?
                                                                0
                                                                Действительно, когда статической версии нет — это полнейшее «с ног на голову». Хотите сказать, ее вообще никогда не было и вся навигация была даже не на хэшбенге а тупо внутри скрипта? Весьма «интересная» реализация. Точнее, весьма странная
                                                                  0
                                                                  сайт работает через api, и да, вся навигация внутри, а урлы только переадресовываются на апи.
                                                                  0
                                                                  да, правильно описали, спасибо:)
                                                    0
                                                    Спасибо большое за отличный способ. Правда у меня фантом 2.0.0 не скомпилировался, пришлось 1.9 ставить. Заработало.

                                                    Скажите пожалуйста, можно ли вместо домена example.com, использовать localhost, или 127.0.0.1 или локальный IP, чтобы не привязывать демона к домену.

                                                    И как поставить эту команду screen -d -m phantomjs --disk-cache=no server.js 8888 example.com в автозагрузку? Т.к. после перезугрузки сервера, приходится ее снова набирать.
                                                      0
                                                      Можно

                                                      Просто добавить в rc.local (например) команду:
                                                      phantomjs --disk-cache=no server.js 8888 example.com

                                                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                                    Самое читаемое