13 марта 2013 г.

SEO URL важная функциональная часть в программном обеспечении OpenCart, объяснять её востребованность нет смысла, достаточно вспомнить технологии SEO. Режим SEO-ссылок включается в панели администрирования в настройках магазина (закладка Сервер).

Вроде всё понятно, но есть одна проблема - это существенная нагрузка на базу данных! Это, пожалуй, самая затратная часть в плане расхода вычислительных ресурсов сервера. С ростом количества товаров и посетителей замедление в работе магазина становится настоящей проблемой! Можно, конечно, выбрать оборудование получше, но если вы собираетесь серьёзно заниматься онлайн-коммерцией, то в какой-то момент времени посмотрите на счета за услуги хостинга и удивитесь сумме ежемесячных платежей. Именно решением этой проблемы мы и занимаемся.

Чтобы понять, как работает режим SEO URL, необходимо разобрать алгоритм формирования и распознания таких ссылок. Весь процесс происходит в файле ./catalog/controller/common/seo_url.php.

По умолчанию система формирует ссылки в стандартном виде и только в режиме SEO URL они преобразуются функцией rewrite. Выглядит это следующим образом:
  1. Контроллер формирует контент с сылками, которые форматируются в библиотеке ./system/library/url.php. Если режим SEO URL активен (см. config_seo_url), то после каждого форматирования ссылки она разбивается на составные части, которые сверяются с содержимым таблицы базы данных url_alias в поиске значения keyword. Это значение закрепляется пользователем при редактировании категории, продукта, статьи или производителя.
  2. При преходе пользователем по SEO-ссылке со страниц магазина или с результатов выдачи поисковой системы происходит обратное преобразование ссылки в стандартный формат. Она также разбивается на части (разделителем служит прямой слэш), которые сверяются с таблицой  url_alias  в поиске идентификаторов категории, продукта, статьи или производителя.
Независимо от того, присвоили ли вы ключевое слово (keyword) категории, продукту, статье или производителю, обращения к базе в этом режиме будут постоянны. Чем глубже ссылка и чем больше их количество, тем медленнее будет работать ваш магазин.

Последний момент усугубляется наличием циклов foreach, в котором для каждой части ссылки происходит обращение к базе данных. Логично было бы построить один запрос для всех частей ссылки:

В файле seo_url.php часть содержимого функции index() после строки $parts = explode('/', rtrim($this->request->get['_route_'], '/')); и до
  1. if (isset($this->request->get['product_id'])) {
          $this->request->get['route'] = 'product/product';
    ...
    необходимо заменить на следующий код:
if (count($parts)) {
    $parts =  array_map(array($this->db, 'escape'), $parts);
    $db_query = $this->db->query("
    SELECT SUBSTRING_INDEX(query,'=',1) AS name,
    CONVERT(SUBSTRING_INDEX(query,'=',-1),UNSIGNED INTEGER) AS value
    FROM " . DB_PREFIX . "url_alias WHERE keyword IN('" . implode("','", $parts) . "') ORDER BY
    FIND_IN_SET(keyword, '" . implode(",", $parts) . "')
    ");
    if ($db_query->num_rows) {
    foreach($db_query->rows as $row) {
    if($row['name'] == 'category_id') {
    if (!isset($this->request->get['path'])) {
      $this->request->get['path'] = $row['value'];
    } else { 
      $this->request->get['path'] .= '_' . $row['value'];
    }
    } else $this->request->get[$row['name']] = $row['value'];
    }
    } else $this->request->get['route'] = 'error/not_found';  
    }

    В том же файле, но уже для функции rewrite() после строки parse_str($url_data['query'], $data); и до if ($url) {
               unset($data['route']);
               ...
    содержимое необходимо заменить на код ниже:

    $queries = array();
    foreach ($data as $key => $value) {
    if (isset($data['route'])) {
    if (($data['route'] == 'product/product' && $key == 'product_id') || 
      (($data['route'] == 'product/manufacturer/info' || 
        $data['route'] == 'product/product') && $key == 'manufacturer_id') || 
      ($data['route'] == 'information/information' && $key == 'information_id')) {
      $queries[] = $key . '=' . (int)$value; 
      } elseif ($key == 'path') {
    $categories = explode('_', $value);
      foreach ($categories as $category) {
      $queries[] = "category_id=" . (int)$category;
    }
    }
    }
    }

    if (count($queries)) {
    $cache = md5(http_build_query($queries));
    $rows = $this->cache->get('url.alias.' . (int)$this->config->get('config_store_id') . $cache);
    if (!$rows) {
    $db_query = $this->db->query("SELECT * FROM " . DB_PREFIX . "url_alias 
                WHERE `query` IN('" . implode("', '", $queries) . "')");
    $rows = $db_query->rows;
    $this->cache->set('url.alias.' . (int)$this->config->get('config_store_id') . $cache, $rows );
    }
    foreach ($queries as $query) {
    foreach ($rows as $row) {
    if($row['query'] == $query) {
    $url .= '/' . $row['keyword'];
    $query = explode('=', $query);
    $key = array_shift($query);
    $key = ($key=='category_id') ? 'path' : $key;
    unset($data[$key]);
    break;
    }
    }
    }
    }

    Обратите внимание, что в эту функцию добавлено также кэширование запросов к базе данных, это уже комплексное решение, которое сводит влияние режима SEO URL на производительность сервера к существенному минимуму (вместо 30-100 запросов всего 5-10).

    При использовании кеширования не забудьте его сбрасывать после изменения/создания категории, продукта, статьи или производителя. Для этого в модели этих объектов наравне с удалением собственного кэша добавьте строчку кода $this->cache->delete('url.alias');

    * * *

    Дополнительное решение для тех, кто понимает насколько важным является наименование и переименование ссылок.

    Всем известна пословица, как корабль назовешь, так он и поплывет. Это определение можно применить и к ссылкам. Речь идет не только о формате ссылок, но и об их звучании и зрительном восприятии. В первом случае это качественное ранжирование в поисковых системах, а во втором - реклама ресурса при обмене ссылками между пользователями, например, в социальных сетях.

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

    Если просто переименовать ссылку объекта в OpenCart, то по старому адресу страница окажется недоступной (404 Not Found), а это чревато потерей потенциальных клиентов и позиций в поисковой выдаче. Выходом из данной ситуации может стать редирект с кодом 301 на новый адрес страницы.

    В данном решении используется история переименований объектов OpenCart в виде дополнительной таблицы url_alias_archive, представления url_alias_view, двух триггеров и одного события (event). В файл ./catalog/controller/common/seo_url.php вносятся небольшие коррективы. Заказать решение можно у автора статьи.

    Если у вас возникли трудности с освоением данной статьи, то всю работу по оптимизации вашего магазина мы готовы взять на себя.

    32 комментария:

    1. описанное здесть усовершенствование не работает. При включении ЧПУ выдает ошибку
      Notice: Error: Table 'open1551.url_alias' doesn't exist
      Error No: 1146
      SELECT SUBSTRING_INDEX(query,'=',1) AS name, CONVERT(SUBSTRING_INDEX(query,'=',-1),UNSIGNED INTEGER) AS value FROM `url_alias` WHERE keyword IN('desktops','pc') ORDER BY FIND_IN_SET(keyword, 'desktops,pc') in /home/htdocs/open1551.loc/system/database/mysql.php on line 50

      И в 1.5.3.1 и в 1.5.5.1

      ОтветитьУдалить
      Ответы
      1. Чтобы заработало в конструкцию запроса "FROM `url_alias` WHERE keyword IN" надо внести изменения "FROM `oc_url_alias` WHERE keyword IN" где ос_ префикс базы

        Удалить
      2. Да, действительно, в запросе пропущен префикс, исправлено!

        Удалить
    2. Обратите внимание, если будите кэшировать запросы в режиме SEO URL, то Вы должны быть уверены в производительности дисковой системы, т.к. при большом количестве товаров, количество файлов в кэше может привести к падению общей производительности.

      ОтветитьУдалить
      Ответы
      1. Можно ведь кешировать массив, в таком случае у нас будет 1 файл

        Удалить
      2. Не рекомендуется использовать один файл, может возникнуть очередь на чтение/запись. Также не забывайте, что на сереализацию и десериализацию данных затрачиваются ресурсы процессора.

        Удалить
    3. Отличная модификация стандартного seo_url, но вот проблема осталась - дубли как были - так и остались, а для СЕО - это один из важнейших приоритетных задач.

      ОтветитьУдалить
      Ответы
      1. Что подразумевается под дублями? Если для одного товара отмечено несколько категорий, то разумеется будут дубли, но с разными абсолютными путями (для категорий).

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

        Удалить
    4. Вот Вашему вниманию сайт (на правах понимания о чем речь) www.fomka.com.ua, там я исправил сео_урл, с присвоение 1-й уникальной категории товару, отображение же в хитах, рекомендуемых, акциях, поиске, производителях - никак не влияет на URL страницы товара, он уникален на всю систему.

      ОтветитьУдалить
      Ответы
      1. Хиты, акции, производители и прочие разделы закрываются в robots.txt

        Удалить
      2. Хорошо, напишу по другому, товар "iPhone" - добавлен во все категории, и по всюду отображается, найдите 2-ве одинаковые ссылки на товар (ну и для поискового робота так же важен этот момент)

        Удалить
    5. Тоесть сам фокус в том, что создается дополнительное поле в БД, под названием main_category_id, и туда вносится 1-н id выбранной в админке категории из списка всех отмеченных, а выдёргиваю для контроллера таким вот запросом:
      ______________________
      $query = $this->db->query("SELECT pc.category_id pc_id, u.keyword, c.parent_id cc_id FROM " . DB_PREFIX . "product_to_category pc JOIN (" . DB_PREFIX . "url_alias u, " . DB_PREFIX . "category c) ON (u.`query` = CONCAT('category_id=', pc.category_id) AND c.category_id = pc.category_id) WHERE pc.main_category = 1 AND pc.product_id = " . (int)$data['product_id'] . ' LIMIT 1');
      _______________________
      Вот почитал пост, и задался вопросом, как сделать к моей модификации - кеширование или наоборот, к вашему кешированию - уникальность СЕО урла...

      ОтветитьУдалить
    6. В OpenCart путь к товару может быть относительным и абсолютным.

      Пример абсолютного пути:
      http://www.domain.com/product_1.html

      Пример относительного пути:
      http://www.domain.com/category_1/product_1.html
      http://www.domain.com/category_1/sub_category/product_1.html
      http://www.domain.com/category_2/product_1.html
      http://www.domain.com/manufacturer_1/product_1.html

      Как видно, product_1.html продублирован в нескольких разделах, это нормально. Какую бы ссылку робот не открыл, он получит каноническое имя http://www.domain.com/product_1.html и поймёт, где постоянный адрес (абсолютный путь).

      Если каноническое имя (rel="canonical") совпадает с сылкой, тогда это настоящие дубли, которые снижают вес страницы товара. В этом случае лишние разделы закрываются в robots.txt, а дубли переписываются через копирование под новым каноническим именем (SEO URL).

      ОтветитьУдалить
    7. Разные адреса, которые ведут на один контент (в нашей теме - относительный путь) всегда будет плохо сказываться на SEO, по поводу rel="canonical", то в опенкарте (стандартном), с этим сложновато, особенно для наших поисковиков (СНГ), так что абсолютный путь - оптимальное решение в данном контексте (ИМХО).

      ОтветитьУдалить
      Ответы
      1. Тогда используйте SEO URL Pro, там можно найти компромис. В любом случае, SEO не панацея, нужно работать над контентом!

        Удалить
    8. На мой взгляд лучшее решение seo url тут:
      http://opencartforum.ru/topic/27002-процесс-работ-над-релизом-ocstore-15512/page-6#entry213622
      И мультиязычность поддерживается (включая Ajax и iframe), и обращений к базе минимум, и кеширование полное одним файлом

      ОтветитьУдалить
      Ответы
      1. Ajax, iFrame и мультиязычность не имеют к этому отношения, здесь упоминается кеширование канонических имен стандартной модели OpenCart, не более.

        Кеширование SEO URL актуально, прежде всего, на хостинге с поддержкой SSD или когда БД находится на отдельном сервере.

        Удалить
    9. Дополнительное решение для тех, кто понимает насколько важным является наименование и переименование ссылок (см. статью).

      ОтветитьУдалить
    10. Удалось практически полностью пересмотреть файл-контроллер SEO URL. Оптимизирован код и пересмотрен описанный выше алгоритм, что позволило увеличить производительность в среднем на 15%. При этом, кэширование не используется, и согласно последним тестам, только снижает скорость открытия страниц при большом количестве товаров. Не забывайте, что к базе данных применимы свои методы оптимизации.

      Контроллер, как и саму оптимизацию ПО OpenCart, можно заказать у автора блога.

      ОтветитьУдалить
    11. Подойдет ли акое решение для файла сео_про или там не возникает подобных проблем?

      ОтветитьУдалить
      Ответы
      1. Нет, это решение для стандартного контроллера ПО OpenCart. В SEO URL Pro есть аналогичные проблемы (в зависимости от версий), но они здесь не рассматриваются.

        Удалить
    12. )))) эх, обычно сайт грузился 3 сек, сделал все по статье, начал по 40-50 сек грузить страницы

      ОтветитьУдалить
      Ответы
      1. Это решение не предназначено для последних версий OpenCart или для его клонов (любых версий), только немодифицированный OpenCart 1.5+.

        Удалить
    13. Этот комментарий был удален автором.

      ОтветитьУдалить
    14. Подскажите, не могу нигде найти информацию: где в opencart изменить в коде все абсолютные ссылки на относительные. Например, чтобы ссылки формировались не так: http://site.com.ua/categoty/ а так: //site.com.ua/categoty/ или так: /categoty/ ? Нужно для переезда на https

      ОтветитьУдалить
      Ответы
      1. Здравствуйте! Базовые адреса для ссылок задаются в файле config.php, там же можно убрать протокол из ссылки или заменить HTTP на HTTPS.

        Удалить
      2. Благодарю!
        Даже не ожидал такой быстрый ответ!)
        На оф. форуме ответа не дождешься никогда... Все сильно гордые, чтобы помочь.
        Как сделаю - отпишусь о результатах)

        Удалить
    15. Спасибо огромное!
      Вы мне очень помогли! Сам бы 100 лет искал.
      Все получилось!) Исправил в корневой директории файла config.php 3-ю строку, убрав из нее домен. Для http версии работает, на https по ходу еще нужно будет то же самое подправить на 6-й строке, что начинается с define('https... =)

      ОтветитьУдалить
      Ответы
      1. Я попробовал внести правки в этот файл на свежеустановленный движок, но при включенном ЧПУ появляются ошибки, ссылаясь на 156 и 221 строку файла /catalog/controller/common/seo_pro.php
        Как только отключаешь - все ок. Но урлы не ЧПУ. Не подскажите в чем может быть дело?

        Удалить
      2. seo_pro.php не является стандартным контроллером OpenCart. В этом файле обрабатывается протокол ссылки, поэтому и возникают ошибки.

        Удалить
      3. Спасибо, буду думать в этом направлении...

        Удалить

    • RSS
    • Twitter
    • Youtube