目次
サンプル・プログラムの実行例
サンプル・プログラム
viewRSS.php | サンプル・プログラム本体。 |
pahooRSS.php | RSS処理に関わるクラス pahooRSS。 RSS処理に関わるクラスの使い方は「PHPで作るRSSビューア」「[https://www.pahoo.org/e-soul/webtech/php02/php02-38-01.shtm:title=PHPでRSSを作る]」「[https://www.pahoo.org/e-soul/webtech/php02/php02-52-01.shtm:title=PHPでRSSを作る(その2)]」を参照。include_path が通ったディレクトリに配置すること。 |
pahooScraping.php | RSS処理に関わるクラス pahooScraping。 スクレイピング処理に関わるクラスの使い方は「PHPで作るRSSビューア」を参照。include_path が通ったディレクトリに配置すること。 |
pahooInputData.php | データ入力に関わる関数群。 使い方は「数値入力とバリデーション」「文字入力とバリデーション」などを参照。include_path が通ったディレクトリに配置すること。 |
バージョン | 更新日 | 内容 |
---|---|---|
4.0.1 | 2023/09/29 | bug-fix |
4.0.0 | 2023/09/24 | pahooRSSクラスに変更 |
3.1 | 2021/01/17 | PHP8対応,pear/XML_RSS に変更 |
3.0 | 2017/03/20 | PHP7対応;大幅改訂 |
2.0 | 2007/10/15 | 大幅改訂 |
関数/メソッド | 機能 | 詳細 |
---|---|---|
class::pahooRSS | ||
__construct | コンストラクタ | |
__destruct | デストラクタ | |
isJapanese | 使用言語が日本語かどうかを判定する | |
setLanguage | 使用言語を設定する. | |
isError | エラーが発生しているかどうかを取得する. | |
getError | エラー番号を取得する. | |
getErrorMessage | エラー・メッセージを取得する. | |
resetError | エラー状態をリセットする. | |
isphp8over | PHP8以上かどうか検査する | |
parse | 指定したURLにあるフィード情報を解釈してプロパティ$itemsに代入する. | |
addParser | ユーザーが用意したパーサを追加する. | 情報を格納するプロパティ $items の構造は本ファイル冒頭を参照のこと. |
parseRSS091 | RSS 0.91 パーサ | チャンネル情報は添字0に、アイテム情報は添字1以降に格納する. |
parseRSS10 | RSS 1.0 パーサ | チャンネル情報は添字0に、アイテム情報は添字1以降に格納する. |
parseRSS11 | RSS 1.1 パーサ | チャンネル情報は添字0に、アイテム情報は添字1以降に格納する. |
parseRSS20 | RSS 2.0 パーサ | チャンネル情報は添字0に、アイテム情報は添字1以降に格納する. |
atom | Atom パーサ | チャンネル情報は添字0に、アイテム情報は添字1以降に格納する. |
maker | チャネル情報とフィード情報を与えてフィードを生成する. | 作成するフィードとして RSS1.0, RSS1.1, RSS2.0, Atomを選択できる. |
makeRSS10 | 与えたチャネル情報とフィード情報から RSS 1.0を生成する. | |
makeRSS11 | 与えたチャネル情報とフィード情報から RSS 1.1を生成する. | |
makeRSS20 | 与えたチャネル情報とフィード情報から RSS 2.0を生成する. | |
makeAtom | 与えたチャネル情報とフィード情報から Atomを生成する. |
バージョン | 更新日 | 内容 |
---|---|---|
1.0.1 | 2023/09/29 | __construct() -- bug-fix |
1.0.0 | 2023/09/18 | 初版 |
バージョン | 更新日 | 内容 |
---|---|---|
1.5.0 | 2024/01/28 | exitIfExceedVersion() 追加 |
1.4.2 | 2024/01/28 | exitIfLessVersion() メッセージ修正 |
1.4.1 | 2023/09/30 | コメントの訂正 |
1.4.0 | 2023/09/09 | $_GET, $_POST参照をfilter_input()関数に置換 |
1.3.0 | 2023/07/11 | roundFloat() 追加 |
RSSパーサ
このサンプル・プログラムは当初、MagpieRSS というオープン・ソースのRSSパーサを用いていたが、PHP8対応するためにはソースのあちらこちらを修正しなければならないことが分かり、pear/XML_RSS に乗り換えた。
しかし、pear/XML_RSS もPHPのバージョンアップに追いついておらず、そこで、自前で RSSパーサをクラス pahooRSS として用意することにした。
サンプル・プログラムの流れ
画面は、大きく2つのブロックに分かれる。RSSのURLを入力するためのINPUT部と、RSSを解釈した結果を表示するBODY部である。プログラムの流れとしては、URLが入力されたらBODY部を表示するようにする。
そこで、最初はINPUT部のみ表示しておき、[取得]ボタンが押されたら、URLの値をGET渡しする。もしGET渡しされた値があったら、BODY部を表示するようにする。
解説:初期化処理
30: //プログラム・タイトル
31: define('TITLE', 'RSSビューア');
32:
33: //データ入力に関わる関数群:include_pathに配置すること
34: require_once('pahooInputData.php');
35:
36: //PHPバージョン・チェック
37: exitIfLessVersion(MINUMUM_VERSION);
38:
39: //リファラチェック+リリースフラグの設定
40: if (isset($_SERVER['HTTP_HOST']) && ($_SERVER['HTTP_HOST'] == 'localhost')) {
41: define('FLAG_RELEASE', FALSE);
42: define('REFER_ON', '');
43: ini_set('display_errors', 1);
44: ini_set('error_reporting', E_ALL);
45: } else {
46: //リリース・フラグ(公開時にはTRUEにすること)
47: define('FLAG_RELEASE', TRUE);
48: //リファラ・チェック(直リン防止用;空文字ならチェックしない)
49: define('REFER_ON', 'www.pahoo.org');
50: }
51:
52: //表示幅(ピクセル)
53: define('WIDTH', 600);
54:
55: //デフォルトのRSS
56: define('DEFURL', 'https://www.pahoo.org/rss/pahoo.rdf');
57:
58: //RSS処理に関わるクラス:include_pathが通ったディレクトリに配置
59: require_once('pahooRSS.php');
関数 require_once を使って、RSS処理を行うクラス pahooRSS.php とスクレイピング処理を行うクラス pahooScraping.php を読み込む。include_pathが通ったディレクトリに配置しておくこと。クラス pahooScraping.php については「PHPでDOMDocumentを使ってスクレイピング」を参照のこと。
解説:pahooRSSクラス
15: class pahooRSS {
16: private $rssURL; //RSS URL
17: private $items; //チャネル情報、フィード情報
18: private $language; //使用言語(初期値は日本語)
19: private $error; //エラー・メッセージ
20: private $parseTable; //定義済みパーサ一覧
21: private $templateRSS10; //RSS1.0テンプレート
22: private $templateRSS11; //RSS1.0テンプレート
23: private $templateRSS20; //RSS1.0テンプレート
24: private $templateAtom; //Atomテンプレート
25: private $nameSpaces; //名前空間
26: private $userParseTable; //ユーザー定義パーサ一覧
27: private $errorTable; //エラー番号 => エラー・メッセージ 一覧
28: /**
29: * プロパティ $items の構造
30: *
31: * 添字[0] チャネルの情報
32: * [0]['title'] タイトル
33: * [0]['link'] URL
34: * [0]['rss'] RSSのURL
35: * [0]['creator'] 製作者
36: * [0]['copyright'] 著作権者
37: * [0]['description'] 説明
38: *
39: * 添字[1以上] フィードの情報
40: * [#]['title'] タイトル
41: * [#]['link'] URL
42: * [#]['description'] 説明
43: * [#]['date'] 更新日時(ISO 8601形式)
44: */
RSS処理を行うクラス pahooRSS には上記のようなプロパティを用意した。
肝になるのは、RSSを解析(パース)して、その情報を格納する連想配列 $items である。$items の構造はコメントに示したとおりである。
46: /**
47: * コンストラクタ
48: * @param なし
49: * @return object /NULL:PHP8未満のため実行できない.
50: */
51: function __construct() {
52: //PHPのバージョンをチェックする.
53: if (! $this->isphp8over()) {
54: $this->error = 111;
55: throw new Exception($this->getErrorMessage());
56: return NULL;
57: }
58: //プロパティの初期値を設定する.
59: $this->rssURL = '';
60: $this->items = array();
61: $this->language = 'ja';
62: $this->error = 0;
63:
64: //RSS 0.91, 1.0, RSS 1.1, RSS 2.0, Atomのパーサを標準で用意している.
65: //関数=>正規表現パターン対応表
66: $this->parseTable = array(
67: 'parseRSS091'=> "/<rss\s+version\=\"0\.91/ui",
68: 'parseRSS10' => "/xmlns\=\"http\:\/\/purl\.org\/rss\/1\.0/ui",
69: 'parseRSS11' => "/xmlns\=\"http\:\/\/purl\.org\/rss\/1\.1/ui",
70: 'parseRSS20' => "/rss.+version\=\"2\.0\"/ui",
71: 'atom' => "/xmlns\=\"http\:\/\/www\.w3\.org\/2005\/Atom/ui",
72: );
73: //メソッド addParser() によりユーザー定義のパーサを追加できる.
74: //関数=>正規表現パターン対応表
75: $this->userParseTable = array();
76:
77: //RSS1.0のテンプレート
78: $this->templateRSS10 =<<< EOT
79: <?xml version="1.0" encoding="utf-8" ?>
80: <rdf:RDF
81: xmlns="http://purl.org/rss/1.0/"
82: xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
83: xmlns:dc="http://purl.org/dc/elements/1.1/"
84: xml:lang="ja">
85: </rdf:RDF>
86:
87: EOT;
88:
89: //RSS1.1のテンプレート
90: $this->templateRSS11 =<<< EOT
91: <Channel xmlns="http://purl.org/net/rss1.1#"
92: xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
93: rdf:about="http://www.xml.com/xml/news.rss">
94: </Channel>
95:
96: EOT;
97:
98: //RSS2.0のテンプレート
99: $this->templateRSS20 =<<< EOT
100: <?xml version="1.0" encoding="utf-8" ?>
101: <rss version="2.0">
102: </rss>
103:
104: EOT;
105:
106: //Atomのテンプレート
107: $this->templateAtom =<<< EOT
108: <?xml version="1.0" encoding="utf-8" ?>
109: <feed xmlns="http://www.w3.org/2005/Atom">
110: </feed>
111:
112: EOT;
113:
114: //名前空間(name space)
115: $this->nameSpaces = array(
116: 'RSS1.0' => array(
117: 'xmlns' => 'http://purl.org/rss/1.0/',
118: 'rdf' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
119: 'dc' => 'http://purl.org/dc/elements/1.1/',
120: 'lang' => 'ja'
121: ),
122: 'RSS1.1' => array(
123: 'xmlns' => 'http://purl.org/net/rss1.1#',
124: 'rdf' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
125: 'about' => 'http://www.xml.com/xml/news.rss'
126: ),
127: 'ATOM' => array(
128: 'xmlns' => 'http://www.w3.org/2005/Atom'
129: )
130: );
131:
132: //エラー番号 => エラー・メッセージ 一覧
133: $this->errorTable = array(
134: 111 => array('PHP8以上が必要です', 'PHP8 or higher is required'),
135: 121 => array('ファイルが見つかりません', 'file not found'),
136: 122 => array('未登録のファイル・フォーマットです', 'unregistered file format'),
137: 123 => array('XMLファイルのフォーマットが間違っています', 'XML file is formatted incorrectly'),
138: 131 => array('登録済みパーサ関数です', 'registered parser functions'),
139: 141 => array('未登録のフィードです', 'unknown feed method'),
140: 999 => array('不明のエラー', 'unknown error'),
141: );
142: }
次に、プロパティの初期値を代入していく。
冒頭で、サポートする形式は RSS 1.0, RSS 1.1, RSS 2.0, Atom と記したが、各々のフォーマットを解析するメソッドを用意し、これを配列 parseTable に代入しておく。可変関数として呼び出せるようにするためだ。
解説:RSSパーサと例外処理
220: /**
221: * 指定したURLにあるフィード情報を解釈してプロパティ$itemsに代入する.
222: * @param string $url RSS/ATOMのURL
223: * @return bool TRUE:成功/FALSE:失敗(例外発生)
224: */
225: function parse($url) {
226: //フィード情報ファイルを読み込む.
227: if (($contents = @file_get_contents($url)) === FALSE) {
228: $this->error = 121;
229: throw new Exception($this->getErrorMessage());
230: return FALSE;
231: }
232:
233: //SimpleXMLの前処理
234: $contents = preg_replace("/encoding\=\"UTF-8\"/ui", 'encoding="utf-8"', $contents);
235:
236: //パーサー関数を呼び出す.
237: foreach ($this->parseTable as $func=>$pat) {
238: if (preg_match($pat, $contents) > 0) {
239: return $this->{$func}($contents);
240: }
241: }
242: //ユーザー定義パーサー関数を呼び出す.
243: foreach ($this->userParseTable as $func=>$pat) {
244: if (preg_match($pat, $contents) > 0) {
245: return $func($contents);
246: }
247: }
248:
249: //エラー処理
250: $this->error = 122;
251: throw new Exception($this->getErrorMessage());
252: return FALSE;
253: }
RSS または Atom のURLを引数に指定し、それを file_get_contents で読み込む。
ここで、指定したファイルが存在しないなどの原因で file_get_contents がエラーを起こすことがある。
PHPは、他のプログラミングにあるような例外という仕組みを備えている。ここれはエラーが派生したときには、他のプログラミング言語と同様、例外を発生させ、呼び出し側プログラムに通知するようにする。
PHPにおける例外発生処理は、throw キーワードを使って Exception インスタンスを生成する。引数として、あらかじめ定義したエラー・メッセージを渡してやる。
325: try {
326: $rss = new pahooRSS();
327: //ユーザー定義パーサを追加する.
328: $rss->addParser('tenkiJp', '/\<title\>気象予報士のポイント解説/ui');
329: $items = $rss->parse($uri);
330: } catch (Exception $e) {
331: $errmsg = $e->getMessage();
332: }
ここでは、例外が発生した場合には変数 [$errmsg] にエラー・メッセージを格納する。
例外処理はここまでにして、話をメソッド parse に戻そう。
指定したファイルを読み込むことができたら、連想配列 $parseTable に用意されたパターン( RSS 1.0, RSS 1.1, RSS 2.0, Atom)と比較しながら、一致したら、対応するメソッドを呼び出す。$this->{$func} は変数 $func の値に応じてメソッドを呼び出す可変関数である。
さらに、連想配列 $parseTable に用意されたパターンには一致しないが、後述するユーザー定義パーサーに一致すれば、同様に、可変関数としてユーザー定義パーサーを呼び出す。
この2つのいずれにも一致しなければ、エラーとして例外を発生する。
$items[0] チャネルの情報 | ||
["title"] | タイトル | |
["link"] | URL | |
["rss"] | RSSのURL | |
["creator"] | 製作者 | |
["copyright"] | 著作権者 | |
["desctiption"] | 説明 | |
$items[1以上] フィードの情報 | ||
["title"] | タイトル | |
["link"] | URL | |
["desctiption"] | 説明 | |
["date"] | 更新日時(ISO 8601形式) |
解説:ユーザー定義パーサ
255: /**
256: * ユーザーが用意したパーサを追加する.
257: * 情報を格納するプロパティ $items の構造は本ファイル冒頭を参照のこと.
258: * @param string $func パーサ関数
259: * @param string $pat XMLコンテンツの合致パターン(正規表現)
260: * @return bool TRUE:成功/FALSE:関数名が重複(例外発生)
261: */
262: function addParser($func, $pat) {
263: if (isset($this->userParseTable[$func])) {
264: $this->error = 131;
265: throw new Exception($this->getErrorMessage());
266: return FALSE;
267: }
268: $this->userParseTable[$func] = $pat;
269: return TRUE;
270: }
引数として、関数名と、ファイルの固有パターンを正規表現として渡す。
139: /**
140: * 日本気象協会の記事情報をパースする.
141: * チャンネル情報は添字0に、アイテム情報は添字1以降に格納する.
142: * @param string $contents RSS1.0コンテンツ
143: * @return array アイテム配列/FALSE:解析失敗(例外発生)
144: */
145: function tenkiJp($contents) {
146: //フィード情報格納用配列
147: $items = array();
148:
149: //インスタンスを生成する.
150: $scrp = new pahooScraping($contents);
151: //エラー処理
152: if ($scrp->iserror()) return FALSE;
153:
154: //チャネルの情報を格納する.
155: $domain = 'https://tenki.jp';
156: $items[0]['title'] = '日本気象協会';
157: $items[0]['link'] = 'https://tenki.jp/forecaster/';
158: $items[0]['rss'] = '';
159: $items[0]['description'] = $scrp->getDescription();
160: $domDoc = $scrp->queryXPath('//small[@class="ft-copyright"]');
161: $items[0]['creator'] = '日本気象協会';
162: $items[0]['copyright'] = $domDoc->item(0)->nodeValue;
163:
164: //フィード情報を格納する.
165: //タイトル
166: $domDoc = $scrp->queryXPath('/html/body/div/section/section/div/ul[@class="forecaster-diary-entries-list"]/li/a/div/p[@class="title"]');
167: $cnt = (int)$domDoc->length;
168: for ($i = 0; $i < $domDoc->length; $i++) {
169: $title = $domDoc->item($i)->nodeValue;
170: $items[$i + 1]['title'] = $title;
171: $items[$i + 1]['description'] = ''; //概要は空にする.
172: }
173: //リンク
174: $domDoc = $scrp->queryXPath('/html/body/div/section/section/div/ul/li/a');
175: for ($i = 0; $i < $cnt; $i++) {
176: $url = (string)$domDoc->item($i)->getAttribute('href');
177: if (preg_match('/[0-9]+\.html$/', $url) > 0) {
178: $items[$i + 1]['link'] = $domain . $url;
179: }
180: }
181: //更新日時
182: $domDoc = $scrp->queryXPath('//span[@class="date-time"]');
183: for ($i = 0; $i < $cnt; $i++) {
184: $dt = $domDoc->item($i)->nodeValue;
185: $items[$i + 1]['date'] = str2iso8601($dt, $items[$i + 1]['link']);
186: }
187:
188: //インスタンスを解放する.
189: $scrp = NULL;
190:
191: return $items;
192: }
ファイルを読み込んだHTMLがテキストとして引数に渡るので、これをスクレイピングして、上述のメソッド parse で得られる連想配列と同じ構造を持った $items に格納し、この連想配列を戻り値としてやればユーザー定義パーサの完成である。
ここでは、引数で渡されたHTMLを DOMDocument とみなし、XPath式 でスクレイピングしている。そのためのクラス myscraping も用意した。
解説:ヒアドキュメント
283: $body =<<< EOT
284: <body>
285: <h2>{$title} {$version}</h2>
286: <form name="myform" method="get" action="{$myself}" enctype="multipart/form-data">
287: フィードURL
288: <input type="text" name="uri" id="uri" size="50" value="{$url}" />
289: <input type="submit" id="exec" name="exec" value="取得" />
290: <input type="submit" id="reset" name="reset" value="リセット" />
291: </form>
292: {$output}
293:
294: <div style="border-style:solid; border-width:1px; margin:20px 0px 0px 0px; padding:5px; width:{$width}px; font-size:small; overflow-wrap:break-word; word-break:break-all;">
295: <h3>使い方</h3>
296: <ol>
297: <li>[<span style="font-weight:bold;">フィードURL</span>]に表示したいRSSやAtomのURL を入力してください.</li>
298: <li>[<span style="font-weight:bold;">取得</span>]ボタンを押下してください.</li>
299: <li>フィード一覧を表示します.</li>
300: <li>[<span style="font-weight:bold;">リセット</span>]ボタンを押下すると,表示をクリアします.</li>
301: </ol>
302: ※参考サイト:<a href="{$refere}">{$refere}</a>
303: {$debug}
304: </div>
305: </body>
306:
307: EOT;
また、ヒアドキュメントの中には、PHP変数や式を埋め込むことができる。ここでは、配列変数 $item や $uri などの値を埋め込んでいる。
同様に、HMTLのヘッダ部をユーザー変数 $HtmlHeader に、フッタ部をユーザー変数 $HtmlFooter に格納しておく。
解説:メイン・プログラム
$uri に値が入っていたら pear/XML_RSS のパーサー parse を呼び出す。
NULLが返っていたら、ユーザー変数 $errmsg にエラー・メッセージを格納する。
311: // メイン・プログラム ===================================================
312: //パラメータ
313: $rss = NULL;
314: $errmsg = '';
315: $items = array();
316: $rssonly = isButton('rssonly');
317: $uri = getParam('uri', FALSE, DEFURL);
318:
319: //リセット
320: if (isButton('reset')) {
321: $uri = '';
322:
323: //RSSを解釈する.
324: } else if ($uri != '') {
325: try {
326: $rss = new pahooRSS();
327: //ユーザー定義パーサを追加する.
328: $rss->addParser('tenkiJp', '/\<title\>気象予報士のポイント解説/ui');
329: $items = $rss->parse($uri);
330: } catch (Exception $e) {
331: $errmsg = $e->getMessage();
332: }
333: }
334:
335: //HTML BODYを作成する.
336: $HtmlBody = makeCommonBody($rssonly, $uri, $items, $errmsg);
337:
338: //表示処理
339: if ($rssonly) {
340: echo $HtmlBody;
341: } else {
342: echo $HtmlHeader;
343: echo $HtmlBody;
344: echo $HtmlFooter;
345: }
346:
347: //インスタンスを解放する
348: $rss = NULL;
349:
350: /*
解説:表示処理
この時点で表示に必要な情報は次のユーザー変数に格納されている。
$HtmlHeader | HTMLヘッダ |
$HtmlBody | BODY部 |
$HtmlFooter | HTMLフッタ |
342: echo $HtmlHeader;
343: echo $HtmlBody;
344: echo $HtmlFooter;
サンプル・プログラムの応用
このとき、INPUT部が表示される必要はないと思うので、rssonly オプションを追加した。
たとえば "viewRSS.php?rssonly&uri=[RSS URL]"と指定してやれば、スタイルシートは未定義で、INPUT部を表示しないRSSビューアとして機能する。
ぱふぅ家のホームページでは、最速セキュリティニュースで、サンプル・プログラムと同様のスクリプトを使っている。
Apache HTTPサーバの SSI の1つである "#include virtual" を利用し、RSSビューア・スクリプトをコンテンツの中に埋め込んでいる。
参考サイト
- pear/XML_RSS
- RSS -- サイト情報の要約と公開(The Web KANZAKI)
- PHPでRSSを作る:ぱふぅ家のホームページ
- PHPでRSSを作る(その2):ぱふぅ家のホームページ
- RSS 0.91 Specification (Netscape):RSS ADVISORY BOARD(英語)
- RSS1.0,RSS2.0,ATOM のフォーマット・仕様・構造:Amarronの日記
- RSS 1.1:ちょっとしたメモ
しかし、私はサイトの新着情報を一括してチェックするのに今も RSS を利用しており、この情報をボットに渡し、気になる記事をツイートしたりしている。
また、「ぱふぅ家のホームページ」でも、3つのRSSを配信し続けている。
(2024年4月20日)RSS 0.91対応,複数RSSを連続処理できるよう名前空間 NS_dc を変数に変更.
(2023年9月18日)pahooRSS, pahooScrapingクラスに変更