Czytnik RSS i Atom w PHP

RSS (Really Simple Syndication) i Atom to dwa standardy wykorzystywane do dystrybucji treści w sieci, zwłaszcza wiadomości i aktualizacji z blogów, serwisów informacyjnych i innych źródeł online. Oba mają na celu umożliwienie użytkownikom subskrybowania treści ze stron internetowych aby byli na bieżąco z najnowszymi aktualizacjami bez konieczności odwiedzania tych stron.

RSS (Really Simple Syndication)

  • Został stworzony w oparciu o język XML.
  • Istnieje kilka wersji RSS z których najpopularniejsze to RSS 2.0.
  • Struktura RSS jest stosunkowo prosta co sprawia, że jest łatwy w implementacji.
  • Standard RSS umożliwia udostępnianie podstawowych informacji o treści takich jak tytuł, link i krótki opis.

Atom

  • Również oparty na języku XML, ale jest bardziej rozbudowany i elastyczny niż RSS.
  • Stworzony w odpowiedzi na różne wersje i niejasności w specyfikacjach RSS.
  • Atom zawiera bardziej szczegółowe informacje takie jak autor, identyfikator (unikatowy dla każdego wpisu) oraz informacje o aktualizacjach.
  • Dzięki swojej bogatej strukturze Atom jest bardziej wszechstronny, ale może być też nieco bardziej skomplikowany w implementacji niż RSS.

Chociaż oba standardy mają podobne cele różnią się strukturą i możliwościami. Wybór między nimi zależy od konkretnych potrzeb i wymagań projektu. Wielu twórców treści udostępnia kanały zarówno w formacie RSS jak i Atom, aby zaspokoić potrzeby różnych użytkowników i czytników.

Popularne czytniki

  • Feedly – Jedna z najpopularniejszych aplikacji do czytania kanałów RSS. Działa na przeglądarkach internetowych oraz posiada aplikacje mobilne.
  • Inoreader – Kolejny popularny czytnik, który oferuje wiele funkcji oraz aplikacje na różne platformy.
  • The Old Reader – Prosty i intuicyjny czytnik dla tych, którzy szukają podstawowego rozwiązania.

Aby korzystać z czytnika

  1. Znajdź link do kanału RSS/Atom na stronie, którą chcesz subskrybować (często jest to ikonka w postaci fal radiowych lub napis „RSS”).
  2. Skopiuj link.
  3. Otwórz swój czytnik RSS/Atom i dodaj nowy kanał wklejając skopiowany link.

Teraz gdy strona doda nową treść będzie ona automatycznie pojawiać się w Twoim czytniku. Dzięki temu możesz śledzić wiele stron w jednym miejscu bez konieczności przeszukiwania Internetu w poszukiwaniu najnowszych informacji. To nie tylko oszczędza czas, ale również pozwala na bardziej efektywne zarządzanie treścią, gdyż wszystko jest gromadzone w jednym, uporządkowanym interfejsie.

Dodatkowo, większość czytników RSS/Atom pozwala na organizację kanałów w kategorie, co ułatwia segregację informacji według interesujących Cię tematów. Możesz także oznaczać poszczególne wpisy jako przeczytane lub nieprzeczytane, co jest szczególnie przydatne, gdy śledzisz wiele kanałów i chcesz wrócić do pewnych artykułów później.

Niektóre czytniki oferują również funkcje społecznościowe pozwalając użytkownikom na udostępnianie interesujących ich artykułów z innymi czy też komentowanie wpisów bezpośrednio w aplikacji. Dzięki temu czytniki RSS/Atom nie tylko upraszczają konsumpcję treści, ale również mogą stać się platformą interakcji i wymiany informacji między użytkownikami.

Własny czytnik RSS w PHP

Pełna specyfikacja formatu RSS 2.0 znajduje się na stronie rssboard.org. Dla celów niniejszego kursu nie będziemy opracowywać pełnej obsługi formatu, a skupimy się na odczytaniu kanału RSS Google News.

Google udostępnia kanał RSS o lemurach jako przykład kanału wyników wyszukiwania

plik XML w standardzie RSS 2.0 – wynik wyszukiwania frazy „lemury”

Przyjrzyjmy się budowie tego pliku

<rss xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
  <channel>
    <generator>NFE/5.0</generator>
    <title>"lemurs" - Wiadomości Google</title>
    <link>https://news.google.com/search?q=lemurs&hl=pl&gl=PL&ceid=PL:pl</link>
    <language>pl</language>
    <webMaster>news-webmaster@google.com</webMaster>
    <copyright>2023 Google Inc.</copyright>
    <lastBuildDate>Sun, 10 Sep 2023 00:58:12 GMT</lastBuildDate>
    <description>Wiadomości Google</description>
    <item>
      <title>Lemur – opis, występowanie i zdjęcia. Zwierzę lemur ciekawostki - Portal ekologiczny</title>
      <link>https://news.google.com/rss/articles/...</link>
      <guid isPermaLink="false">...</guid>
      <pubDate>Thu, 13 Oct 2022 01:30:50 GMT</pubDate>
      <description>opis</description>
      <source url="https://www.ekologia.pl">Portal ekologiczny</source>
    </item>
    <item>
      ...
    </item>
  </channel>
</rss>

Wszystkie informacje zawierają się w tagach rss i channel wewnątrz którego są elementy item z konkretnymi wiadomościami.

Wyciąganie danych

Wyciąganie danych z pliku możemy spróbować zrealizować w podobny sposób jak to robiliśmy z parsowaniem strony w HTML, albo skorzystać z dedykowanej, wbudowanej i popularnej biblioteki SimpleXML.

$url = 'https://news.google.com/news?ned=pl_pl&q=lemurs&output=rss';
$data = new SimpleXMLElement($url, 0, true);

W pierwszym parametrze podaliśmy URL i dlatego trzeci parametr powinien być ustawiony na true. Drugi natomiast zawiera dodatkowe opcje i często jest używany jak musimy np. pobrać dane z zewnętrzego webservice (np. LIBXML_PARSEHUGE jeżeli dokument jest duży, LIBXML_NOCDATA jeżeli zawiera dane w formie CDATA). Do tego konkretnego pliku nie musimy ustawiać żadnych dodatkowych parametrów.

Informacje o wersji RSS wyciągniemy z atrybutu version w tagu głównym rss (nie musimy go podawać podczas wyciągania danych). Zanim rozpoczniemy parsowanie, sprawdzimy jeszcze czy deklarowana w pliku wersja RSS się zgadza z obsługiwana. Zrobimy to za pomocą wbudowanej funkcji version_compare. Do wyciągania danych z taga służy metoda attributes().

$rss_version = (string) $data->attributes()->version;
if (version_compare ($rss_version, "2.0") != 0) die('Nieobsługiwana wersja RSS');

Zwróć uwagę, że atrybut musimy zawsze zamieniać na oczekiwany format danych (najczęściej string).

Informacje o kanale znajdziemy w tagu channel.

$channel = [];
$channel['generator'] = (string) $data->channel->generator;
$channel['title'] = (string) $data->channel->title;
$channel['link'] = (string) $data->channel->link;
$channel['language'] = (string) $data->channel->language;
$channel['webMaster'] = (string) $data->channel->webMaster;
$channel['copyright'] = (string) $data->channel->copyright;
$channel['lastBuildDate'] = (string) $data->channel->lastBuildDate;
$channel['description'] = (string) $data->channel->description;

Informacje o poszczególnych elementach są zaś przekazywane w item. Aby je wyciągnąć wystarczy skorzystać z pętli.

$items = [];
foreach ($data->channel->item as $item) {

  $itemData = [];
  $itemData ['title'] = (string) $item->title;
  $itemData ['link'] = (string) $item->link;
  $itemData ['guid'] = (string) $item->guid;
  $itemData ['isPermalink'] = (bool) $item->guid->attributes()->isPermaLink;
  $itemData ['pubDate'] = (string) $item->pubDate;
  $itemData ['description'] = (string) $item->description;
  $itemData ['source'] = (string) $item->source;
  $itemData ['url'] = (string) $item->source->attributes()->url;
  $items[] = $itemData;
}

Prezentacja danych

Pozostaje tylko już wyświetlić pobrane dane. Skorzystamy przy tym z biblioteki bootstrap.

<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/js/bootstrap.bundle.min.js"></script>

<div class="container">

	<div class="card">

		<div class="card-header">Czytnik RSS Google News</div>
		<div class="card-body">

			<dl class="row">

				<?php

				foreach ($channel as $name => $value) {
					
					echo '<dt class="col-sm-3">'.$name.'</dt>';
					echo '<dd class="col-sm-9">'.$value.'</dd>';	
				}

				?>
			</dl>
		
			<div class="accordion" id="news">
			
				<?php
				
				$id = 1;
				foreach ($items as $item) { 
				
					$itemName = 'item_'.$id;
					$id++;
				?>
					<div class="accordion-item">
						<h2 class="accordion-header">
							<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#<?php echo $itemName?>" aria-expanded="false" aria-controls="#<?php echo $itemName?>">
							<?php echo $item['title']; ?>
							</button>
						</h2>
						<div id="<?php echo $itemName?>" class="accordion-collapse collapse" data-bs-parent="#news">
							<div class="accordion-body">
								<?php echo $item['description']; ?>
							</div>
						</div>
					</div>
				<?php } ?>

			</div>
		</div>
	</div>
</div>

Dla przykładu podpiąłem kanał z wiadomościami. Jak widać na poniższym zrzucie ekranu, Google zagregował podobne wiadomości z wielu różnych portali i wyświetlił linki do stosownych artykułów.

Z linków możemy wyciągnąć informacje z poszczególnych serwisów. Nie będziemy się tym tutaj zajmować, ale jak jesteś zainteresowany do odsyłam do artykułu o pobieraniu strony offline w PHP.

Własny czytnik Atom w PHP

Jak już wspomniałem – Atom jest nowszym standardem. Oficjalna specyfikacja powszechnie używanej wersji Atom 1.0 znajduje się na stronie jetf.org.

Nie będziemy dodawać obsługi wszystkiego, a podobnie jak poprzednio skupimy się na kanale o lemurach:

plik XML w standardzie Atom 1.0 – wynik wyszukiwania frazy „lemury”

Ponownie przyjrzyjmy się budowie pliku

<feed xmlns="http://www.w3.org/2005/Atom">
<id>https://news.google.com/atom/search?q=lemurs&hl=pl&gl=PL&ceid=PL:pl</id>
<generator>NFE/5.0</generator>
<title type="html">"lemurs" - Wiadomości Google</title>
<subtitle>Wiadomości Google</subtitle>
<updated>2023-09-10T12:13:20.000000000Z</updated>
<author>
<name>Google Inc.</name>
<email>news-webmaster@google.com</email>
<uri>https://news.google.com/</uri>
</author>
<link href="https://news.google.com/atom/search?q=lemurs&hl=pl&gl=PL&ceid=PL:pl" rel="self" type="application/atom+xml"/>
<link href="https://news.google.com/search?q=lemurs&hl=pl&gl=PL&ceid=PL:pl" rel="alternate" type="text/html"/>
<rights>2023 Google Inc.</rights>
<entry>
<id>https://news.google.com/articles/CBMicmh0dHBzOi8vd3d3LmVrb2xvZ2lhLnBsL3Nyb2Rvd2lza28vcHJ6eXJvZGEvbGVtdXItb3Bpcy13eXN0ZXBvd2FuaWUtaS16ZGplY2lhLXp3aWVyemUtbGVtdXItY2lla2F3b3N0a2ksMjkzMDcuaHRtbNIBAA?oc=5</id>
<title type="html">Lemur – opis, występowanie i zdjęcia. Zwierzę lemur ciekawostki - Portal ekologiczny</title>
<updated>2022-10-13T01:30:50.000000000Z</updated>
<link href="https://news.google.com/atom/articles/CBMicmh0dHBzOi8vd3d3LmVrb2xvZ2lhLnBsL3Nyb2Rvd2lza28vcHJ6eXJvZGEvbGVtdXItb3Bpcy13eXN0ZXBvd2FuaWUtaS16ZGplY2lhLXp3aWVyemUtbGVtdXItY2lla2F3b3N0a2ksMjkzMDcuaHRtbNIBAA?oc=5" type="text/html"/>
<content type="html"><a href="https://news.google.com/atom/articles/CBMicmh0dHBzOi8vd3d3LmVrb2xvZ2lhLnBsL3Nyb2Rvd2lza28vcHJ6eXJvZGEvbGVtdXItb3Bpcy13eXN0ZXBvd2FuaWUtaS16ZGplY2lhLXp3aWVyemUtbGVtdXItY2lla2F3b3N0a2ksMjkzMDcuaHRtbNIBAA?oc=5" target="_blank">Lemur – opis, występowanie i zdjęcia. Zwierzę lemur ciekawostki</a>  <font color="#6f6f6f">Portal ekologiczny</font></content>
</entry>

Wszystkie informacje zawierają się w tagu feed wewnątrz którego są elementy entry z konkretnymi wiadomościami.

Wyciąganie danych

Tutaj działamy podobnie jak poprzednio. Korzystamy z klasy SimpleXML. Zmieni się tylko link do kanału oraz sposób kontroli wersji.

$url = 'https://news.google.com/news?ned=pl_pl&q=lemurs&output=atom';
$data = new SimpleXMLElement($url, 0, true);

$atom_spec = (string) $data->attributes()->xmlns;
if ($atom_spec == 'http://www.w3.org/2005/Atom') die('Nieobsługiwana wersja Atom');

Informacje o kanale znajduje się w głównym elemencie feed.

$channel = [];
$channel['id'] = (string) $data->channel->id;
$channel['generator'] = (string) $data->generator;
$channel['title'] = (string) $data->title;
$channel['subtitle'] = (string) $data->subtitle;
$channel['updated'] = (string) $data->updated;
$channel['author_name'] = (string) $data->author->name;
$channel['author_mail'] = (string) $data->author->email;
$channel['author_uri'] = (string) $data->author->uri;
$channel['rights'] = (string) $data->rights;
$channel['link'] = (string) $data->link;

Poszczególne elementy są natomiast w entry

$items= [];
foreach ($data->entry as $item) {

  $itemData = [];
  $itemData ['id'] = (string) $item->id;
  $itemData ['title'] = (string) $item->title;
  $itemData ['link'] = (string) $item->link->attributes()->href;
  $itemData ['update'] = (string) $item->update;
  $itemData ['description'] = (string) $item->content;
  $items[] = $itemData;
}

Pozostała część kodu jest identyczna jak poprzednio, a efekt jest zbliżony:

Kod w serwisie GitHub

Filtrowanie danych

Tutaj jeszcze trzeba napomknąć o sanityzacji danych. Wszystko co odbieramy z zewnątrz może spowodować problemy po stronie serwera. Nie jest trudno sobie wyobrazić, że w powyższy sposób można uruchomić złośliwy kod po stronie naszego czytnika. Wystarczy w dowolnym polu przekazać skrypt, albo kod php otoczony <?php ?>. Dlatego jeżeli korzystamy z niesprawdzonych źródeł, albo dajemy użytkownikowi możliwość samodzielnego wyboru źródła, powinniśmy pokusić się o filtrowanie danych.

Podsumowanie

  1. RSS 2.0 służy do dystrybuowania treści.
  2. Atom 1.0 jest nowszym standardem przeznaczonym do dystrybuowania treści.
  3. Google udostępnia informacje zagregowane z różnych serwisów w formie plików XML tworzonych w standardzie RSS oraz Atom.
  4. Do parsowania pliku XML w PHP służy klasa SimpleXMLElement.
  5. Aby zapobiec potencjalnym atakom XSS warto dodać sanityzację danych do parsera.