Продолжая тему парсеров. Одна из самых распространенных задач, которые я постоянно встречаю - заполнить Joomla сайт на основе другого сайта. Полный переезд, обычно с более старой версии Joomla, но быть может и с другой CMS, не важно.

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

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

Т.е. в этой статье мы автоматизируем рутинные операции копипаста: копирование текста, создание статьи в материалах, создание ссылки на статью в меню.

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

Писать будем на php. Для начала нам потребуется функция парсер, которая будет загружать страницу сайта.

Тут ничего нового:

function get($url) {  
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_HEADER, 0);
    curl_setopt($ch, CURLOPT_USERAGENT, 'User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36');
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
    curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);      
    //curl_setopt($ch, CURLOPT_PROXY, "111.111.111.111:8000");
    //curl_setopt($ch, CURLOPT_PROXYUSERPWD, 'user:password'); 
    //curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_HTTPS);
    return iconv('windows-1251', 'UTF-8', curl_exec($ch));
}

В моей задаче, сайт источник был в кодировке windows-1251, поэтому в последней строке я конвертирую кодировку. Вы можете возвращать разультат curl_exec($ch) как есть, если у вас кодировка не меняется. Плюс, донор по какой-то причине заблокировал ip моего сервера, и пришлось использовать прокси сервер. Вы также можете раскомментировать 3 предпоследние строки и парсить сайт через прокси сервер.

Далее, нам нужно создать стандартный материал Joomla

function createArticle($title, $text, $category_id) {
    $jarticle 				= new stdClass();
    $jarticle->title			= $title;
    $jarticle->alias			= JFilterOutput::stringURLSafe($jarticle->title);

    $jarticle->introtext		= $text;

    $jarticle->state			= 1;
    $jarticle->catid			= $category_id;

    $jarticle->created		= JFactory::getDate()->toSQL();
    $jarticle->created_by		= JFactory::getUser()->id;

    $jarticle->access		= 1;
    $jarticle->metadata		= '{"page_title":"","author":"Valera","robots":""}';
    $jarticle->language		= '*';
    
    $table = JTable::getInstance('content', 'JTable');
    $data = (array)$jarticle;

    // Bind data
    if (!$table->bind($data)) {
        //Handle the errors here however you like (log, display error message, etc.)
        var_dump($table->getError());
        //return false;
    }

    // Check the data.
    if (!$table->check()) {
        //Handle the errors here however you like (log, display error message, etc.)
        var_dump($table->getError());
        //return false;
    }

    // Store the data.
    if (!$table->store()) {
        //Handle the errors here however you like (log, display error message, etc.)
        var_dump($table->getError());
        //return false;
    }
    return table->id;
}

Это копипаст кода из интернета. Как видите если есть ошибка, она никак не обрабатывается, а просто выводится на экран. Если хотите, обработайте ошибки сами.

Функция возвращает идентификатор созданного материала. Он потребуется нам в следующей функции.

И последняя функция, которая нам потребуется - это функция создания элемента пункта меню:

function createItem($title, $article_id, $parent_id) {
   $menuItem = [
        'menutype' => 'mainmenu',
        'title' => $title,
        'alias' => JFilterOutput::stringURLSafe($title),
        'type' => 'component',
        'component_id' => 22,                  
        'link' => 'index.php?option=com_content&view=article&id='.$id,
        'language' => '*',
        'published' => 1,
        'parent_id' => $parent_id,
        'params' => '{"show_title":"","link_titles":"","show_intro":"","info_block_position":"","info_block_show_title":"","show_category":"","link_category":"","show_parent_category":"","link_parent_category":"","show_author":"","link_author":"","show_create_date":"","show_modify_date":"","show_publish_date":"","show_item_navigation":"","show_vote":"","show_icons":"","show_print_icon":"","show_email_icon":"","show_hits":"","show_tags":"","show_noauth":"","urls_position":"","menu-anchor_title":"","menu-anchor_css":"","menu_image":"","menu_text":1,"menu_show":1,"page_title":"","show_page_heading":"","page_heading":"","pageclass_sfx":"","menu-meta_description":"","menu-meta_keywords":"","robots":"","secure":0}',
        'level' => 1,
    ];

    $menuTable = JTable::getInstance('Menu', 'JTable', array());

    $menuTable->setLocation($parent_id, 'last-child');

    // Bind data
    if (!$menuTable->bind($menuItem))
    {
        var_dump($menuTable->getError());
        //return false;
    }

    // Check the data.
    if (!$menuTable->check())
    {
        var_dump($menuTable->getError());
        // return false;
    }

    // Store the data.
    if (!$menuTable->store())
    {
        var_dump($menuTable->getError());
        //return false;
    }
}

Это тоже, практически, копипаст из интернетов.

Для того, чтобы начать работу, нам потребуется создать категорию в материалах (параметр $category_id для функции createArticle.) И пункт в меню (параметр $parent_id для функции createItem.)

Имея все необходимые функции и идентификаторы раздела меню и категории, напишем наш парсер:

<?php
define('_JEXEC', 1);
define('JPATH_BASE', __DIR__); // replace to valid path
require_once JPATH_BASE . '/includes/defines.php';
require_once JPATH_BASE . '/includes/framework.php';

$app = JFactory::getApplication('site');

$parent_id = 146;
$category_id = 27;

// полный код функций берите выше
function get($url) {...}
function createArticle($title, $text, $category_id) {...}
function createItem($title, $article_id, $parent_id) {...}

$links = [
    'http://sitename.ru/partnerstvo/obshchee-sobranie/',
    'http://sitename.ru/partnerstvo/sovet-partnerstva/',
    'http://sitename.ru/partnerstvo/ispolnitelnaya-direktsiya/',
    'http://sitename.ru/partnerstvo/distsiplinarnyy-komitet/',
];

foreach ($links as $link) {
    $html = get($link);

    preg_match('#<title>(.*)</title>#uis', $html, $stitle);
    $title = $stitle[1];
    preg_match('#<article>(.*)</article>#uis', $html, $sarticle);
    $article = str_replace(['"/upload'], ['"upload'], $sarticle[1]);

    $article_id = createArticle($title, $article, $category_id);
    createItem($title, $article_id, $parent_id);
}

Разберем, что же тут происходит. Сам файл скрипта подключает стандартную точку входа Joomla, и даже инициализирует новое приложение. После этого вы можете использовать все методы и классы Joomla, также как бы вы это делали в компонентах или плагинах.

Далее по циклу, перебираем массив $links и получаем содержимое заданной ссылки, при помощи функции get. Далее идет непонятный кусок кода. Он специфичен для данного сайта, но будет иным для вас. Название статьи я беру просто из тега title. В большинстве сайтов, в этом теге будет всякий мусор - название статьи - название сайта - ключевики. Поэтому этот момент вам надо обработать самостоятельно.

Основной контент сайта, был заключен в тег article и на 29-ой строке, я его оттуда изымаю.

Возможно Ваш сайт донор, окажется более сложным и я бы порекоменлдовал разбирать такие данные при помощи библиотеки SimpleHTMLDom

Вот собственно и все. Желаю вам успехов, пишите ваши вопросы в комментариях. С удовольствием отвечу на них.

Оставлять комментарии могут только зарегистрированные пользователи