Продолжая тему парсеров. Одна из самых распространенных задач, которые я постоянно встречаю - заполнить 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
Вот собственно и все. Желаю вам успехов, пишите ваши вопросы в комментариях. С удовольствием отвечу на них.

