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

目次
cronへの登録
DB登録のためのコマンドラインオプションは、後述する定数 STORE_DB に定義したものを使う。たとえば定義値が "store123" であれば、cronに下記のように記述する。
/usr/bin/php /e-soul/webtech/php05/program/getPublicComment.php store1234567890=0
サンプル・プログラムのダウンロード
getPublicComment.php | サンプル・プログラム本体。 |
pahooDataStructure.php | 日時を使うためのクラス pahooDateTime。 使い方は「日付と時刻」を参照のこと。include_pathが通ったディレクトリに配置する。 |
プログラムの方針
ところが、このRSSは最新十数件しか保持していないようで、それ以前のパブリックコメントがあっても分からない。電子政府の外部連携APIを利用しようと考えたが、利用登録など手続きが煩雑であり、公開プログラムにするには面倒と判断した。

そこで、RSS情報を、1日数回cronで参照し、データベースに登録し、登録した内容を検索・参照できるようなプログラムを作ることにする。
プログラムの保守性を考え、登録と検索・参照は同一プログラムで実行できるようにする。
DBMSは、PHPからインストール不要で使える SQLite を使うことにする。

解説:準備
getPublicComment.php
46: // 表示幅(ピクセル)
47: define('WIDTH', 600);
48:
49: // タイムゾーン(日本標準時)
50: define('TZONE', '+09:00');
51:
52: // 電子政府パブリックコメントRSS
53: define('PC_RSS_URL', 'https://public-comment.e-gov.go.jp/rss/pcm_list.xml');
54:
55: // SQLite DBファイル名:各自の環境に合わせて変更すること
56: define('DBFILE', './publicComment.sqlite3');
57:
58: // SQLite テーブル名
59: define('TABLE_PC', 'public_comment'); //パブリックコメント情報
60:
61: // DB登録用キー
62: define('STORE_DB', 'store1234567890');
63:
64: // 実行するSQL
65: define('PRE_SELECT', 'SELECT * FROM ' . TABLE_PC . ' WHERE id=:id');
66: define('PRE_INSERT', 'INSERT INTO ' . TABLE_PC . ' (id, title, uri, announce_dt, close_dt, category, refer, premiere, latest) VALUES (:id, :title, :uri, :announce_dt, :close_dt, :category, :refer, :premiere, :latest)');
67: define('PRE_UPDATE', 'UPDATE ' . TABLE_PC . ' set title=:title, uri=:uri, announce_dt=:announce_dt, close_dt=:close_dt, category=:category, refer:=refer, latest=:latest WHERE id=:id');
68: define('PRE_QUERY', 'SELECT * FROM ' . TABLE_PC . ' WHERE close_dt >= :close_dt ORDER BY close_dt DESC');
69: define('PRE_LATEST', 'SELECT latest FROM ' . TABLE_PC . ' WHERE 1 ORDER BY latest DESC');
70:
71: // データ構造に関わるクラス:include_pathが通ったディレクトリに配置
72: require_once('pahooDataStructure.php');

SQLite のデータベース実体ファイルは定数 DBFILE に定義する。PHPから書き込み可能なファイルであれば、フォルダやファイル名は自由に設定してかまわない。
本プログラム中のユーザー関数 initDB によってデータベースを自動生成するので、事前準備は不用である。

前述の通り、cronでDB登録する際、コマンドラインに渡すキーを STORE_DB に定義する。実害はないが、外部から手動でDB登録されるのも気持ちが悪いので、複雑なキーにしておくといいだろう。なお、ダウンロードしたキーでは、本サイトでDB登録を行うことはできない。
解説:pahooDataStructure クラス
クラスについては「PHPでクラスを使ってテキストの読みやすさを調べる」で解説している。
クラスファイル "pahooDataStructure.php" は、組み込み関数 require_once を使って読めるディレクトリに配置する。ディレクトリは、設定ファイル php.ini に記述されているオプション設定 include_path に設定しておく。
解説:パブリックコメント情報の取得
getPublicComment.php
266: /**
267: * 電子政府 パブリックコメントRSSをスクレイピングし
268: * パブリックコメント情報を配列に格納
269: * @param string $url スクレイピングするURL
270: * @param array $items パブリックコメント情報を格納する配列
271: * @return int 取得情報件数/FALSE:失敗
272: */
273: function getPCinfo($url, &$items) {
274: define('NS_dc', 'http://purl.org/dc/elements/1.1/'); //名前空間
275: static $pat1 = "/公示日:([0-9\/]+)/iu";
276: static $pat2 = "/締切日時:([0-9\/]+)/iu";
277: static $pat3 = "/問合せ先[^:]+:([^\s]+)/iu";
278: static $pat4 = "/<br\s*\/>/iu";
279: static $pat5 = "/\&id\=([0-9]+)/iu";
280: static $pat6 = "/カテゴリー:(.+)/iu";
281:
282: $pdt = new pahooDateTime(); //日時計算クラス
283:
284: $rss = simplexml_load_file($url);
285: if ($rss == FALSE) return FALSE;
286:
287: $cnt = 0;
288: foreach ($rss->item as $item) {
289: $items[$cnt]['id'] = (preg_match($pat5, $item->link, $arr) > 0) ?
290: (int)$arr[1] : '';
291: $items[$cnt]['title'] = trim((string)$item->title);
292: $items[$cnt]['uri'] = trim((string)$item->link);
293: $str = trim((string)$item->description);
294: $items[$cnt]['announce_dt'] = (preg_match($pat1, $str, $arr) > 0) ?
295: yyyymmdd2iso8601($arr[1]) : '';
296: $items[$cnt]['close_dt'] = (preg_match($pat2, $str, $arr) > 0) ?
297: yyyymmdd2iso8601($arr[1]) : '';
298: $items[$cnt]['refer'] = (preg_match($pat3, $str, $arr) > 0) ?
299: strip_tags($arr[1]) : '';
300: $arr = preg_split($pat4, $str);
301: $str = $arr[2];
302: $items[$cnt]['category'] = (preg_match($pat6, $str, $arr) > 0) ?
303: strip_tags($arr[1]) : '';
304: //更新日時
305: $node = $item->children(NS_dc);
306: $pdt->parse_date((string)$node->date);
307: $dt = $pdt->date('c');
308: $items[$cnt]['latest'] = $dt;
309: $cnt++;
310: }
311:
312: $pdt = NULL;
313: return ($cnt - 1);
314: }
RSSのパーシングは、「PHPでコロンを含むXML要素名を扱う方法」で紹介したように、SimpleXML を使う。

公示日、締切日、所轄官庁、連絡先窓口といった情報は全て descriptionタグに含まれているため、その内容を preg_match 関数を使ってスクレイピングし、配列へと格納してゆく。

また、日時情報は世界時をベースにした ISO 8601 でデータベースに格納すべく、先ほど紹介したクラス pahooDateTime を使う。
解説:DBの初期化
getPublicComment.php
338: /**
339: * DBの初期化
340: * @param なし
341: * @return bool TRUE/FALSE
342: */
343: function initDB() {
344: try {
345: $pdo = new PDO('sqlite:' . DBFILE);
346: $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
347:
348: // テーブル作成:パブリックコメント
349: // id:案件番号, title:題名, uri:リンク先
350: // announce_dt:公示日, close_dt:締切日
351: // category:カテゴリ, refer:問い合わせ先
352: // premiere:登録日時, latest:更新日時
353: $pdo->exec('CREATE TABLE IF NOT EXISTS ' . TABLE_PC . '(
354: id INTEGER PRIMARY KEY AUTOINCREMENT,
355: title TEXT,
356: uri TEXT,
357: announce_dt TEXT,
358: close_dt TEXT,
359: category TEXT,
360: refer TEXT,
361: premiere TEXT,
362: latest TEXT
363: )');
364: $res = TRUE;
365: } catch (PDOException $e) {
366: $res = FALSE;
367: }
368:
369: return $res;
370: }
解説:DBにパブリックコメントを登録する
getPublicComment.php
372: /**
373: * 電子政府 パブリックコメントRSSをDB登録する
374: * @param string $url 電子政府 パブリックコメントRSS
375: * @return int 登録件数
376: */
377: function storeDB($url) {
378: $item = array();
379: $cnt = 0;
380: getPCinfo($url, $items);
381: foreach ($items as $item) {
382: $res = updatePCtoDB($item['id'], $item);
383: if ($res == TRUE) $cnt++;
384: }
385: return $cnt;
386: }
1件1件のパブリックコメントに対し、updatePCtoDB 関数を実行する。

ユーザー関数 updatePCtoDB は、DBレコードを更新する。
案件番号をキーにして、未登録のパブリックコメントであれば insertPCtoDB 関数を使ってDBにinsertする。案件番号が存在しており、更新日が異なっていればDBをupdateする。

ユーザー関数 insertPCtoDB は、パブリックコメント情報をDBにinsertする。

ユーザー関数 getPCfromDB は、案件番号をキーにしてDBをselectする。
解説:DB検索および一覧表HTMLを作成する
getPublicComment.php
503: /**
504: * DB検索および一覧表HTMLを作成する
505: * @param string $query 検索キー
506: * @return string 一覧表HTML
507: */
508: function makeTable($query) {
509: $pdt = new pahooDateTime(); //日時計算クラス
510: $pat = '/' . $query . '/ui';
511: $update = '';
512:
513: $html =<<< EOT
514: <table class="pc">
515: <tr>
516: <th>案件番号</th>
517: <th>題名</th>
518: <th>公示日</th>
519: <th>締切日</th>
520: </tr>
521:
522: EOT;
523: try {
524: $pdo = new PDO('sqlite:' . DBFILE);
525: $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
526: $stmt = $pdo->prepare(PRE_QUERY);
527: // 締切日は本日より未来日
528: $stmt->bindValue(':close_dt', date('Y-m-d', time()), PDO::PARAM_STR);
529: $res = $stmt->execute();
530: while ($row = $stmt->fetch()) {
531: // 検索キーとのマッチング
532: if ((preg_match($pat, $row['title']) == 0) &&
533: (preg_match($pat, $row['refer']) == 0) &&
534: (preg_match($pat, $row['category']) == 0)) continue;
535: // データ取得
536: $pdt->parse_date($row['announce_dt']);
537: $pdt->tzone(TZONE);
538: $announce_dt = $pdt->date('Y/m/d');
539: $pdt->parse_date($row['close_dt']);
540: $pdt->tzone(TZONE);
541: $close_dt = $pdt->date('Y/m/d');
542: $html .=<<< EOT
543: <tr>
544: <td class="id"><a href="{$row['uri']}" target="_blank">{$row['id']}</a></td>
545: <td class="title">{$row['title']}</td>
546: <td class="dt">{$announce_dt}</td>
547: <td class="dt">{$close_dt}</td>
548: </tr>
549: <tr>
550: <td colspan="2" class="description">{$row['refer']}</td>
551: <td colspan="2" class="description">{$row['category']}</td>
552: </tr>
553:
554: EOT;
555: }
556: // DB最終登録更新日の取得(RSSより)
557: $stmt = $pdo->prepare(PRE_LATEST);
558: $res = $stmt->execute();
559: $row = $stmt->fetch();
560: if ($row != FALSE) {
561: $dt = $row['latest'];
562: $pdt->parse_date($dt);
563: $pdt->tzone(TZONE);
564: $update = $pdt->date('Y/m/d H:i');
565: }
566: } catch (PDOException $e) {
567: $pdo = NULL;
568: return FALSE;
569: }
570: $pdo = NULL;
571:
572: $html .=<<< EOT
573: <tr><td colspan="4" class="update">最終更新日時:{$update}</td></tr>
574: </table>
575:
576: EOT;
577: $pdt = NULL;
578: return $html;
579: }

DB検索については、締切日が本日より未来日になるようにSQLで絞り込む。
検索キーは、題名、カテゴリ、問い合わせ先を対象に検索することから、SQLではなく、PHPプログラムの方で preg_match 関数を使ってマッチングを行う。
解説:メイン・プログラム
getPublicComment.php
660: // メイン・プログラム =======================================================
661: // パラメータを取得する
662: $query = getParam('query', TRUE, ''); //検索キー
663: if (isButton('clear')) $query = '';
664:
665: $item = array();
666: $errmsg = '';
667:
668: // DB初期化
669: if (initDB() == FALSE) {
670: $errmsg = 'DB初期化に失敗しました';
671: // DB登録:cron起動→終了
672: } else if (isButton(STORE_DB)) {
673: $n = storeDB(PC_RSS_URL);
674: }
675: // DB検索
676: $html = makeTable($query);
677:
678: // 表示処理
679: $HtmlBody = makeCommonBody($query, $html, $errmsg);
680: echo $HtmlHeader;
681: echo $HtmlBody;
682: echo $HtmlFooter;
参考サイト
- 政府パブリックコメント
- 日付と時刻:ぱふぅ家のホームページ
以前は各省庁がバラバラに行っていたが、現在、政府パブリックコメントに情報が集約されている。
ところが、現在どんなパブリックコメントが出ており、いつ締め切りになるのかを一元的に調べる仕組みがないようだ(各省庁のメールマガジンや、非公式なツイート・ボットがあることは確認している)。
そこで、政府パブリックコメントから最新情報を逐次取得しデータベースに登録し、ブラウザから検索・表示できるようなPHPプログラムを作ってみることにする。
(2024年12月28日)不具合修正