Продолжая тему парсеров. Одна из самых распространенных задач, которые я постоянно встречаю - заполнить Joomla сайт на основе другого сайта. Полный переезд, обычно с более старой версии Joomla, но быть может и с другой CMS, не важно.
Обычно, проще в ручную скопировать все тексты с сайта, чем писать отдельный граббер под это дело. Когда же дело касается нескольких сотен статей, мой внутренний прагматик запрещает мне работать руками и в дело вступает мозг.
В идеале парсер должен съедать лишь одну страницу сайта на входе и обходить все найденные страницы сам. В статье Как написать универсальный парсер сайтов за 1 час я уже рассказывал, как это сделать. Когда дело касается переезда на Joomla, то необходимо точно воссоздать структуру сайта донора, посему немного упростим задачу, и будем парсить лишь заданный список ссылок, который будет определенным образом привязан к конкретному разделу из сайта источника. А этот самый раздел на нашем сайте, создадим руками.
Т.е. в этой статье мы автоматизируем рутинные операции копипаста: копирование текста, создание статьи в материалах, создание ссылки на статью в меню.
К этим трем операциям можно еще добавить - автоматическое создание разделов сайта, на основе сайта донора. Однако, эту задачу я оставлю вам. Она не сложная, поверьте. Пишите в комментариях, как вы ее решаете.
Писать будем на php. Для начала нам потребуется функция парсер, которая будет загружать страницу сайта.
Тут ничего нового:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | 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; } |
Это копипаст кода из интернета. Как видите если есть ошибка, она никак не обрабатывается, а просто выводится на экран. Если хотите, обработайте ошибки сами.
Функция возвращает идентификатор созданного материала. Он потребуется нам в следующей функции.
И последняя функция, которая нам потребуется - это функция создания элемента пункта меню:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | 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
.)
Имея все необходимые функции и идентификаторы раздела меню и категории, напишем наш парсер:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | <?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
Вот собственно и все. Желаю вам успехов, пишите ваши вопросы в комментариях. С удовольствием отвечу на них.