PHPでRSSを作る(その2)

(1/1)
PHPでRSSを作る」では、あらかじめ用意したテキストからRSSを生成するプログラムを作ったが、今回は、RSSを備えていないサイトで、新着情報一覧ページをスクレイピングしてRSSを生成するプログラムを作ってみることにする。1つのプログラムで複数サイトのRSSを生成できることを目指す。

目次

サンプル・プログラムの実行例

PHPでRSSを作る(その2)
makeRSS.php?id=3 のように、idでサイトを指定する。このサイトIDについては、後述する。

サンプル・プログラムのダウンロード

圧縮ファイルの内容
RSSmaker.phpサンプル・プログラム本体

サンプル・プログラムの流れ

PHPでRSSを作る(その2)
サイト毎にスクレイピング関数を用意し、$id に応じて呼び出す。
RSS 1.0を作成するユーザー関数 makeRSS10、RSS 1.1を作成するユーザー関数 makeRSS1w は、「PHPでRSSを作る」をそのまま流用した。

解説:RSS作成サイト

RSSを作成するサイト(スクレイピング対象サイト)は、あらかじめグローバル変数 $ListSites に代入しておく。この配列変数の構造は下表の通り。
$ListSites の構造
サイトID
(1次元目)
添字
(2次元目)

(2次元目)
1 titleサイト名
urlスクレイピングURL
funcスクレイピング関数名
2 titleサイト名
urlスクレイピングURL
funcスクレイピング関数名

   1: <?php
   2: /** RSSmaker.php
   3:  * PHPでスクレイピングしてRSSを作成(RSSメーカー)
   4:  *
   5:  * @copyright   (c)studio pahoo
   6:  * @author      パパぱふぅ
   7:  * @動作環境    PHP 8
   8:  * @参考URL     https://www.pahoo.org/e-soul/webtech/php02/php02-52-01.shtm
   9:  *
  10:  * [コマンドライン・パラメータ]
  11:  * id = RSS作成サイト番号:必須
  12: */
  13: // 初期化処理 ================================================================
  14: define('INTERNAL_ENCODING', 'UTF-8');

解説:中央日報(日本語版)をスクレイピングする

 331: /**
 332:  * 中央日報(日本語版)をスクレイピングする
 333:  * @param   array $id    RSS作成サイト番号
 334:  * @param   array $param スクレイピング用パラメータ
 335:  * @return  string RSS文字列
 336: */
 337: function joinsCom($id, $param) {
 338:     global $TempRSS10, $TempRSS11, $NameSpace;
 339:     $sites    = array();    //サイト諸元を格納する配列
 340:     $contents = array();    //コンテンツ情報を格納する配列
 341: 
 342:     //ページ読み込み
 343:     $str = @file_get_contents($param['url']);
 344:     //エラー処理
 345:     if (($str == NULL|| ($str == FALSE)) {
 346:         $errmsg = '中央日報(日本語版):情報が取得できない.';
 347:         errorRSS($errmsg, $sites, $contents);
 348:         $outstr = makeRSS10($sites, $contents, $TempRSS10, $NameSpace);
 349:         return $outstr;
 350:     }
 351: 
 352:     //サイト諸元
 353:     $arr = array();
 354:     static $pat11 = '/\<meta\sname\=\"description\"\scontent\=\"([^\"]+)\"/iums';
 355:     static $pat12 = '/\<meta\sname\=\"copyright\"\scontent\=\"([^\"]+)\"/iums';
 356:     $sites['domain'] = getDomainURL($param['url']);
 357:     $sites['rss'] = $_SERVER['SCRIPT_NAME'. '?id=' . $id;
 358:     $sites['title'] = $param['title'];
 359:     $sites['description'] = (preg_match($pat11, $str, $arr> 0? $arr[1: '';
 360:     $sites['creator'] = CREATOR;
 361:     $sites['copyright'] = (preg_match($pat12, $str, $arr> 0? $arr[1: '';
 362: 
 363:     //コンテンツ情報
 364:     $arr = array();
 365:     static $pat21 = '/<a\shref\="([^\"]+)[^>]+>([^<]+)<\/a>[^<]+<em\sclass\=\"time\">([^<]+)<\/em>/iums';
 366:     preg_match_all($pat21, $str, $arr, PREG_SET_ORDER);
 367:     $cnt = 0;
 368:     foreach ($arr as $key=>$val) {
 369:         $uri = trim($val[1]);
 370:         $uri = $sites['domain'. preg_replace('/^\//ui', '', $uri);
 371:         $uri = preg_replace('/\&([^(amp\;)].)/ui', '&amp;\1', $uri);
 372:         $contents[$key + 1]['uri'] = $uri;
 373:         $contents[$key + 1]['title'] = trim($val[2]);
 374:         $contents[$key + 1]['description'] = '';
 375:         $contents[$key + 1]['latest'] = str2iso8601_01($val[3]);
 376:         $cnt++;
 377:     }
 378: 
 379:     //RSS1.0作成
 380:     $outstr = makeRSS10($sites, $contents, $TempRSS10, $NameSpace);
 381: 
 382:     //RSS1.1作成
 383:     //$outstr = makeRSS11($sites, $contents, $TempRSS11, $NameSpace);
 384: 
 385:     return $outstr;
 386: }

ユーザー関数 [#joinsCom:joinsCom] は、中央日報(日本語版)新着一覧をスクレイピングし、RSSを生成する。

組み込み関数  preg_match  や  preg_replace  を用いてスクレイピングを行う。日付テキストに年号を含まない点について少し工夫している。

解説:中央日報(日本語版)をスクレイピングする

 281: /**
 282:  * アニメ!アニメ!をスクレイピングして,結果を返す.
 283:  * @param   string $contents サイト・コンテンツ
 284:  * @return  array 情報配列/FALSE:解析失敗
 285: */
 286: function animeanime($contents) {
 287:     //フィード情報格納用配列
 288:     $items = array();
 289: 
 290:     //ページ読み込み
 291:     $str = @file_get_contents($param['url']);
 292: 
 293:     //エラー処理
 294:     if (($str == NULL|| ($str == FALSE)) {
 295:         $errmsg = 'アニメ!アニメ!:情報が取得できない.';
 296:         errorRSS($errmsg, $sites, $contents);
 297:         $outstr = makeRSS10($sites, $contents, $TempRSS10, $NameSpace);
 298:         return $outstr;
 299:     }
 300: 
 301:     static $pat11 = '/^<\?xml[^>]+>/iums';
 302:     $outstr = trim(preg_replace($pat11, '', $str));
 303: 
 304:     return $outstr;
 305: }

アニメ!アニメ!には RSS があるのだが、冒頭にxml識別子を含んでいるため、ユーザー関数 animeanime を使って取り除いている。

解説:日本気象協会をスクレイピングする

 227: /**
 228:  * 日本気象協会をスクレイピングして,結果を返す.
 229:  * @param   string $contents サイト・コンテンツ
 230:  * @return  array 情報配列/FALSE:解析失敗
 231: */
 232: function tenkiJp($contents) {
 233:     //フィード情報格納用配列
 234:     $items = array();
 235: 
 236:     //インスタンスを生成する.
 237:     $scrp = new pahooScraping($contents);
 238:     //エラー処理
 239:     if ($scrp->iserror())   return FALSE;
 240: 
 241:     //チャネルの情報を格納する.
 242:     $domain = 'https://tenki.jp';
 243:     $items[0]['title']       = '日本気象協会';
 244:     $items[0]['link']        = 'https://tenki.jp/forecaster/';
 245:     $items[0]['rss']         = '';
 246:     $items[0]['description'] = $scrp->getDescription();
 247:     $domDoc = $scrp->queryXPath('//small[@class="ft-copyright"]');
 248:     $items[0]['creator']     = '日本気象協会';
 249:     $items[0]['copyright']   = $domDoc->item(0)->nodeValue;
 250: 
 251:     //フィード情報を格納する.
 252:     //タイトル
 253:     $domDoc = $scrp->queryXPath('/html/body/div/section/section/div/ul[@class="forecaster-diary-entries-list"]/li/a/div/p[@class="title"]');
 254:     $cnt = (int)$domDoc->length;
 255:     for ($i = 0$i < $domDoc->length$i++) {
 256:         $title = $domDoc->item($i)->nodeValue;
 257:         $items[$i + 1]['title'] = $title;
 258:         $items[$i + 1]['description'] = '';     //概要は空にする.
 259:     }
 260:     //リンク
 261:     $domDoc = $scrp->queryXPath('/html/body/div/section/section/div/ul/li/a');
 262:     for ($i = 0$i < $cnt$i++) {
 263:         $url = (string)$domDoc->item($i)->getAttribute('href');
 264:         if (preg_match('/[0-9]+\.html$/', $url> 0) {
 265:             $items[$i + 1]['link'] = $domain . $url;
 266:         }
 267:     }
 268:     //更新日時
 269:     $domDoc = $scrp->queryXPath('//span[@class="date-time"]');
 270:     for ($i = 0$i < $cnt$i++) {
 271:         $dt = $domDoc->item($i)->nodeValue;
 272:         $items[$i + 1]['date'] = str2iso8601($dt, $items[$i + 1]['link']);
 273:     }
 274: 
 275:     //インスタンスを解放する.
 276:     $scrp = NULL;
 277: 
 278:     return $items;
 279: }

ユーザー関数 tenkiJp は、日本気象協会最新の記事をスクレイピングし、RSSを生成する。

HTMLに対して XPath式の評価を行うことでスクレイピングを行っている。

解説:聯合ニュースをスクレイピングする

 388: /**
 389:  * 聯合ニュースをスクレイピングする
 390:  * @param   array $id    RSS作成サイト番号
 391:  * @param   array $param スクレイピング用パラメータ
 392:  * @return  string RSS文字列/FALSE:スクレイピング失敗
 393: */
 394: function ynaCoKr($id, $param) {
 395:     global $TempRSS10, $TempRSS11, $NameSpace;
 396: 
 397:     //ページ読み込み
 398:     $curl = curl_init() ;
 399:     curl_setopt($curl, CURLOPT_URL, $param['url']);
 400:     curl_setopt($curl, CURLOPT_HEADER, 1) ; 
 401:     curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'GET');
 402:     curl_setopt($curl, CURLOPT_SSL_VERIFYPEER , FALSE);     //証明書は無視
 403:     curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);       //結果を文字列で
 404:     curl_setopt($curl, CURLOPT_USERAGENT, 'Mozilla/5.0');
 405:     curl_setopt($curl, CURLOPT_TIMEOUT, 5);
 406:     $res1 = curl_exec($curl);
 407:     $res2 = curl_getinfo($curl);
 408:     curl_close($curl);
 409:     $str = substr($res1, $res2['header_size']);
 410: 
 411:     //エラー処理
 412:     if (($str == NULL|| ($str == FALSE)) {
 413:         $errmsg = '聯合ニュース:情報が取得できない.';
 414:         errorRSS($errmsg, $sites, $contents);
 415:         $outstr = makeRSS10($sites, $contents, $TempRSS10, $NameSpace);
 416:         return $outstr;
 417:     }
 418: 
 419:     static $pat11 = '/^<\?xml[^>]+>/iums';
 420:     $outstr = trim(preg_replace($pat11, '', $str));
 421: 
 422:     return $str;
 423: }

ユーザー関数 ynaCoKr は、聯合ニュースRSSを取得するだけである。

ただし、HTTP通信で UserAgent のないものを排除するようなので、cURL 関数を使ってブラウザからのアクセスのように見せかけている。

参考サイト

(この項おわり)
header