フィードっていろいろある
フィードは情報を配信するための形式のこと。RSS 1.0, 2.0, ATOMなど、いくつかの種類がある。それぞれ少しずつ形式や構造が違うけど、今回はこれらすべてに対応したリーダーを作る方法を紹介するよ!
それぞれのフィード、どうやって読む?
フィードって基本的にXML形式で書かれている。だから、SimpleXMLというPHPのライブラリを使って解析することができる。RSSやATOMそれぞれの特徴を理解しながら、タグを解析して欲しい情報を取り出してみよう。ちょっとしたコツで、どんなフィードもサクッと読めるようになるよ。
複数のサイトのフィードを効率よく取得する
複数のサイトのフィードを効率的に取得するため、curl_multi_* 関数を利用。また、負荷対策のため、10個ずつ処理するようにしている。
PHPフィードリーダーコード
<?php /* 1. fetchFeeds($feedUrls) この関数は、与えられたURLリストから、それぞれのフィードの内容を取得します。 引数: $feedUrls: フィードを取得したいURLの配列。 戻り値: フィードの内容を含む配列。 詳細: 関数はURLリストを受け取り、それをバッチ単位に分割します。各バッチに対して、cURLマルチハンドリングを使用してフィードの内容を効率的に一斉に取得します。全てのバッチの取得が完了したら、その結果を結合して返します。 */ function fetchFeeds(array $urls) { $multiCurl = curl_multi_init(); $curlArray = []; foreach ($urls as $i => $url) { $curlArray[$i] = curl_init($url); curl_setopt($curlArray[$i], CURLOPT_RETURNTRANSFER, true); curl_setopt($curlArray[$i], CURLOPT_USERAGENT, 'CustomFeedParser/1.0'); curl_setopt($curlArray[$i], CURLOPT_CONNECTTIMEOUT, 10); // 接続のタイムアウトを10秒に設定 curl_setopt($curlArray[$i], CURLOPT_TIMEOUT, 30); // リクエスト全体の最大実行時間を30秒に設定 curl_multi_add_handle($multiCurl, $curlArray[$i]); } $active = null; do { $status = curl_multi_exec($multiCurl, $active); if ($active) { curl_multi_select($multiCurl); } } while ($active && $status == CURLM_OK); $results = []; foreach ($curlArray as $id => $curl) { if (curl_errno($curl)) { $results[$id] = ['error' => curl_error($curl)]; } else { $results[$id] = curl_multi_getcontent($curl); } curl_multi_remove_handle($multiCurl, $curl); } curl_multi_close($multiCurl); return $results; } /* 2. escape($data) この関数は、文字列や配列内の文字列を安全にエスケープして、XSS攻撃から保護します。 引数: $data: エスケープしたい文字列、またはその文字列を含む配列。 戻り値: エスケープされた文字列、またはその文字列を含む配列。 詳細: 関数は再帰的に動作し、深い階層の配列内の文字列までエスケープを行います。エスケープには、htmlspecialchars関数を使用しています。 */ function escape($data) { return htmlspecialchars($data, ENT_QUOTES, 'UTF-8'); } /* 3. parseFeeds($feeds) この関数は、フィードの内容を解析して、エントリーのタイトルと日付を取得します。 引数: $feeds: fetchFeeds関数で取得されるフィード内容の配列。 戻り値: エントリータイトルと日付を含む配列。 詳細: 関数内では、SimpleXMLを使用してフィードの内容を解析します。RSS 1.0, RSS 2.0, ATOMの各形式を識別し、それぞれの形式に応じてタイトルと日付を抽出します。 */ function parseFeeds($feedsContent) { $parsedFeeds = []; // libxmlのエラーレポートを有効にする libxml_use_internal_errors(true); foreach ($feedsContent as $content) { $xml = simplexml_load_string($content, 'SimpleXMLElement', LIBXML_NOCDATA | LIBXML_PARSEHUGE | LIBXML_NOENT); if ($xml === false) { $parsedFeeds[] = ['error' => 'Failed to parse XML']; continue; } $feedType = ''; if (isset($xml->channel)) { $feedType = 'rss2.0'; } elseif (isset($xml->entry)) { $feedType = 'atom'; } elseif (isset($xml->item)) { $feedType = 'rss1.0'; } switch ($feedType) { case 'rss2.0': $feedData = [ 'title' => (string) $xml->channel->title, 'link' => (string) $xml->channel->link, 'description' => (string) $xml->channel->description, 'items' => [] ]; foreach ($xml->channel->item as $item) { $date = isset($item->pubDate) ? (string) $item->pubDate : ''; $feedData['items'][] = [ 'title' => (string) $item->title, 'link' => (string) $item->link, 'description' => (string) $item->description, 'date' => $date ]; } break; case 'atom': $feedData = [ 'title' => (string) $xml->title, 'link' => (string) $xml->link['href'], 'description' => (string) $xml->subtitle, 'items' => [] ]; foreach ($xml->entry as $entry) { $date = isset($entry->published) ? (string) $entry->published : ''; $feedData['items'][] = [ 'title' => (string) $entry->title, 'link' => (string) $entry->link['href'], 'description' => (string) $entry->summary, 'date' => $date ]; } break; case 'rss1.0': $feedData = [ 'title' => (string) $xml->title, 'link' => (string) $xml->link, 'description' => (string) $xml->description, 'items' => [] ]; foreach ($xml->item as $item) { $date = isset($item->children('http://purl.org/dc/elements/1.1/')->date) ? (string) $item->children('http://purl.org/dc/elements/1.1/')->date : ''; $feedData['items'][] = [ 'title' => (string) $item->title, 'link' => (string) $item->link, 'description' => (string) $item->description, 'date' => $date ]; } break; default: $feedData = ['error' => 'Unknown feed format']; } $parsedFeeds[] = $feedData; } return $parsedFeeds; } /* 4. processBatch($batchUrls) この関数は、与えられたバッチのURLリス[f:id:onsen222:20230824123112j:plain]トから、それぞれのフィードの内容を取得します。 引数: $batchUrls: フィードを取得したいURLのサブセット(バッチ単位でのリスト)。 戻り値: バッチ内のURLから取得したフィードの内容を含む配列。 詳細: この関数はfetchFeeds関数の内部で使用されます。バッチ内の各URLに対してcURLハンドルを初期化し、それらのハンドルを使用してフィードの内容を並行して取得します。 */ function processBatch(array $batchUrls) { $feedsContent = fetchFeeds($batchUrls); $feedsData = parseFeeds($feedsContent); foreach ($feedsData as $feedData) { if (isset($feedData['error'])) { echo escape($feedData['error']); } else { echo "Feed Title: " . escape($feedData['title']) . "<br>"; echo "Feed Link: " . escape($feedData['link']) . "<br>"; //echo "Feed Description: " . escape($feedData['description']) . "<br><hr>"; foreach ($feedData['items'] as $item) { echo "Item Title: " . escape($item['title']) . "<br>"; echo "Item Link: " . escape($item['link']) . "<br>"; //echo "Item Description: " . escape($item['description']) . "<br>"; echo "Item Date: " . escape($item['date']) . "<br><hr>"; } } } } $urls = [ 'FEED_URL_1', 'FEED_URL_2', // ... 他のフィードURL、例えば100個以上 ]; //サーバの負荷対策のため、10個ずつ処理 $batches = array_chunk($urls, 10); foreach ($batches as $batchUrls) { processBatch($batchUrls); // 必要に応じて、ここで一時的に処理を遅延させることも考慮できます sleep(2); // 例: 2秒待機 }
以上です!
(このコードのサポートは一切できません。自己責任でご利用ください)