‘.htmlspecialchars($this->params[‘rtitle’]).’

RSS из любого сайта средствами PHP

Здравствуйте, хабра-сообщество! В своём первом посте хотелось бы затронуть тему сбора данных с любого сайта, и формирования из них лент RSS.

Предисловие

Когда в нашем городе только провели интернет, и домашний компьютер начал постигать просторы всемирной паутины, в этой паутине начали появляться сайты, которые хочется посещать многократно, чтобы быть в курсе каких то новостей/новинок. Список избранного в браузере стал потихоньку пополняться ссылками на разнообразные ресурсы. Как только появлялась свободная минутка, я быстренько пробегался по этим сайтам и смотрел что там нового. Пока список этот был небольшим, это не вызывало особой сложности. Но время шло, список рос, пробегаться стало сложно, тогда я для себя открыл RSS, который до этого казался мне чем то непонятным/ненужным.
Так началась у меня эпоха использования RSS-клиента (Mozilla Thunderbird). Но и с помощью этого способа остались некоторые неудобства, а точнее появилась проблема синхронизации рабочего и домашнего компьютера: то что прочитал на работе не будет отмечено как прочитанное дома. На помощь пришёл великий Google Reader, мир стал прекраснее, чудеснее и лучше. Но, за исключением одного большого «НО». Есть единичные, но очень нужные сайты, новости с которых очень хочется почитать в Google Reader, но RSS в них разработчики не предусмотрели…

Варианты

Я стал искать решения, для того чтобы побороть эту проблему. И они находились: Feed43, feedfire.com, но по тем или иным соображениям они мне не подходили. Уж больно ленты специфичные мне потребовались.

Задача

Выдернуть новости с этого сайта и с этого форума (на момент, когда я писал этот код, на форуме не было RSS-подписки на темы).
Сложность первого сайта в том, что там нет семантики, и какого то отделения новостей. Новости — на нём текст сплошняком. А второго как раз наоборот, в сложности и излишних данных. Я расчехлил шашечки и ринулся писать код.

Решение
  1. использование CSS-синтаксиса для поиска элементов
  2. использование регулярных выражений для дополнительной фильтрации

Класс CHTML2RSS (файл CHTML2RSS.class.php):

params = $params; > // загружаем страницу и немного обрабатываем function getDocument($url) < $cnt = $this->params['charset'] ? '' : ''; $cnt.=file_get_contents($url); $cnt = preg_replace('/(?s)/', '', $cnt); // bug with CDATA $this->dom = new DomDocument(); @$this->dom->loadHTML($cnt); > // callback для обхода DOM function domWalk($root, $callback, &$args) < if($root->childNodes) foreach($root->childNodes as $i => $elem) < call_user_func_array($callback, array_merge(array($elem), $args)); $this->domWalk($elem, $callback, $args); > > // callback для фильтрации в стиле CSS function walkCallback($elem, &$attr, $result, $idx) < $add=true; $filter = true; foreach($attr as $sel) < if(!$add) break; switch($sel[1]) < case '': if($elem->nodeName!=$sel[2]) $add=false; break; case '.': if(!$elem->attributes) < $add = false; break; >$node=$elem->attributes->getNamedItem('class'); if(!$node || $node->nodeValue != $sel[2]) $add=false; break; case '#': if(!$elem->attributes) < $add = false; break; >$node=$elem->attributes->getNamedItem('id'); if(!$node || $node->nodeValue != $sel[2]) $add=false; break; case ':': switch($sel[2]) < case 'eq': if($idx!=$sel[3]) $filter = false; break; case 'lt': if($idx>=$sel[3]) $filter = false; break; case 'gt': if($idx <=$sel[3]) $filter = false; break; >break; > > if($add) $idx++; if($add && $filter) $result[]=$elem; > function parseDom($selector, $parent = false) < $result=array(); $arr=explode(' ',$selector); $root = $parent ? $parent : array($this->dom); foreach($arr as $item) < preg_match_all('/(^|\.|#|:)(\w+)(?:\((\d+)\))*/', $item, $attr, PREG_SET_ORDER); $newRoot=array(); $idx = 0; $attr=array($attr, &$newRoot, &$idx); foreach($root as $elem) $this->domWalk($elem, array($this, 'walkCallback'), $attr); $root=$newRoot; > return $root; > // получение outerHTML для элемента function outerHTML($element) < $d = new DomDocument(); $d->appendChild($d->importNode($element, true)); return html_entity_decode($d->saveHTML(), ENT_COMPAT, 'UTF-8'); > // получение innerHTML для элемента function innerHTML($element) < $d = new DomDocument(); foreach($element->childNodes as $node) $d->appendChild($d->importNode($node, true)); return html_entity_decode($d->saveHTML(), ENT_COMPAT, 'UTF-8'); > // основная функция парсинга function parseHTML() < $this->getDocument($this->params['url']); $v = array(); $elems = $this->parseDom($this->params['elems'][0]); if($this->params['elems'][1]) foreach($elems as $elem) < preg_match_all($this->params['elems'][1], $this->outerHTML($elem), $vi, PREG_SET_ORDER); $v = array_merge($v, $vi); > else < // удаляем if($this->params['remove']) foreach($this->params['remove'] as $sel) < $delElems = $this->parseDom($sel, $elems); foreach($delElems as $elem) $elem->parentNode->removeChild($elem); > // ищем совпадения foreach($elems as $i => $elem) < $v[$i] = array(); foreach($this->params['search'] as $srch) < if($srch[0]) < $ei = $this->parseDom($srch[0], array($elem)); if(!count($ei)) continue; $ei = $ei[0]; > else $ei = $elem; if($srch[1]) < preg_match($srch[1], $this->outerHTML($ei), $vi); $v[$i] = array_merge($v[$i], $vi); > else $v[$i][] = $this->innerHTML($ei); > > > return $v; > // вывод RSS function showRSS() < $v = $this->parseHTML(); echo '    '.htmlspecialchars($this->params['rdesc']).' '.htmlspecialchars($this->params['rlink']).''; if($this->params['reverse']) $v = array_reverse($v); foreach($v as $item) < echo " \n"; echo " \n"; echo " ".htmlspecialchars(vsprintf($this->params['ilink'], $item))."\n"; echo " params['idesc'], $item)."]]>\n"; if($this->params['idate']) echo " ".htmlspecialchars(vsprintf($this->params['idate'], $item))."\n"; if($this->params['guid']) echo " ".htmlspecialchars(vsprintf($this->params['guid'], $item))."\n"; echo " \n"; > echo   END; > > ?> 

Использование (файл html2rss.php):

 array ( 'url' => 'http://news.metro.ru/', 'charset' => 'windows-1251', 'elems' => array('tr#mnews td', '/

([^<>]*)(.+)/'), 'search' => false, 'reverse' => false, 'rtitle' => 'news.metro.ru', 'rdesc' => 'Moscow Subway - Metro de Moscou - Москва, метрополитен', 'rlink' => 'http://news.metro.ru/', 'ititle' => 'новость от %2$s', 'idesc' => '%3$s', 'ilink' => 'http://news.metro.ru/', 'idate' => false, 'guid' => false ), '4pda' => array ( 'url' => 'http://4pda.ru/forum/index.php?showtopic=116079&st=9999', 'charset' => false, 'elems' => array('.borderwrap .ipbtable:gt(0)', false), 'remove' => array('span.edit'), 'search' => array ( array('.postdetails a', false), array('.postdetails a', '/href="([^"]+)"/'), array('.postcolor', false) ), 'reverse' => true, 'rtitle' => '4pda.ru - HTC MAX 4G - обсуждение', 'rdesc' => '4pda.ru - HTC MAX 4G - обсуждение', 'rlink' => 'http://4pda.ru/forum/index.php?showtopic=116079&st=9999', 'ititle' => 'пост %1$s', 'idesc' => '%4$s', 'ilink' => '%3$s', 'idate' => false, 'guid' => false ) ); header('Content-Type: text/xml; charset=utf-8'); if(!isset($_GET['source'])) exit('Укажите источник новостей!'); if(!isset($config[$_GET['source']])) exit('Источника не существует!'); //file_put_contents(dirname(__FILE__).'/update.log', date('Y-m-d h:i:s')." \r\n", FILE_APPEND); $h2r = new CHTML2RSS($config[$_GET['source']]); $h2r->showRSS(); ?>

Использование

В файле html2rss.php пример использования, для ресурсов которые я привёл. Создаётся экземпляр класса CHTML2RSS с необходимыми параметрами, где:

url — адрес страницы для парсинга
charset — кодировка страницы, если не UTF-8
elems — массив из CSS-запроса и регулярного выражения для отделения новостей
remove — какие элементы нужно удалить из новостей
search — здесь можно осуществить поиск дополнительных параметров, для выставления заголовков/ссылок на новость/id новости
reverse — перевернуть порядок записей на обратный
rtitle — заголовок ленты
rdesc — описание ленты
rlink — ссылка на сайт ленты
ititle — шаблон заголовка записи, здесь можно применять переменные которые мы нашли с использованием параметра search, вывод происходит с помощью функции PHP sprintf, в которую передаётся массив найденных значений, которые мы ищем с помоoью параметра search
idesc — текст записи, аналогично с помощью sprintf
ilink — ссылка на запись
idate — дата записи
guid — уникальный идентификатор записи

Недостатки и сложности

При разработке мне пришлось столкнуться с одним багом в функции php loadHTML, суть в том что мета-тэг с заданием нужной кодировки должен быть указан ДО того, как в тексте встретится любой текст. Из-за этой проблемы не парсилась страница из news.metro.ru, т. к. тэг был до тэга .
Так же можно в коде не реализовано, хотя было бы лучше, если применять нативный xpath, нежели реализацию фильтрации CSS (хотя так более понятнее).

Источник

Чтение rss лент с помощью php

Для одного проекта появилась задача — читать ленты rss, для последующего постинга статей из этих лент. Погуглил, посмотрел свои старые работы — нашел много интересных решений, в том числе и с помощью функии simplexml_load_file , но все не подходило по ряду причин. Тот же simplexml_load_file не все rss ленты читает, проблема у него с кодировкой windows-1251, на некоторых сайтах. Потому и пришлось искать другие варианты.

В итоге была найдена библиотека, состоящая из нескольких функций для чтения и разбора rss лент. Всю библиотеку мне не нужно было использовать, потому я оставил только 2 необходимые функции, немного подправил под себя и все работает отлично!

getElementsByTagName("title"); $tnl = $tnl->item(0); $title = $tnl->firstChild->textContent; $tnl = $item->getElementsByTagName("link"); $tnl = $tnl->item(0); $link = $tnl->firstChild->textContent; $tnl = $item->getElementsByTagName("pubDate"); $tnl = $tnl->item(0); $date = $tnl->firstChild->textContent; $tnl = $item->getElementsByTagName("description"); $tnl = $tnl->item(0); $description = $tnl->firstChild->textContent; $y["title"] = $title; $y["link"] = $link; $y["date"] = $date; $y["description"] = $description; return $y; > function RSS_Read($url) < $doc = new DOMDocument(); $doc->load($url); $items = $doc->getElementsByTagName("item"); $RSS_Content = array(); foreach($items as $item) < $y = RSS_Tags($item); // get description of article, type 1 array_push($RSS_Content, $y); >return $RSS_Content; > ?>

Как видно по коду, функция RSS_Tags возвращает нам заголовок, ссылку, дату публикации и краткое описание конкретной записи, а функция RSS_Read проходит по ленте в цикле, выбирает все записи, вызывает функцию RSS_Tags для каждой записи, записывает ответ в массив $RSS_Content и отдает его нам! Все просто 🙂 А ниже пример использования этой библиотеки:

А далее уже можно использовать полученные записи по своему усмотрению.

Источник

Читайте также:  Php curl delete запрос
Оцените статью