PHPで政府パブリックコメントを検索・表示

(1/1)
1994年(平成6年)10月施行の行政手続法により、中央省庁が、告示などの命令、審査基準、処分基準、行政指導指針などを定める場合、事前に内容を公示し、意見を求めるパブリックコメント(意見公募)が行われるようになった。
以前は各省庁がバラバラに行っていたが、現在、政府パブリックコメントに情報が集約されている。

ところが、現在どんなパブリックコメントが出ており、いつ締め切りになるのかを一元的に調べる仕組みがないようだ(各省庁のメールマガジンや、非公式なツイート・ボットがあることは確認している)。
そこで、政府パブリックコメントから最新情報を逐次取得しデータベースに登録し、ブラウザから検索・表示できるようなPHPプログラムを作ってみることにする。

(2024年12月28日)不具合修正

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

PHPで政府パブリックコメントを検索・表示

目次

cronへの登録

政府パブリックコメントを定期的に取得しDB更新する目的で、cronWindowsタスクスケジューラに組み込むことをおすすめする。
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 を参照するようにする。
ところが、このRSSは最新十数件しか保持していないようで、それ以前のパブリックコメントがあっても分からない。電子政府の外部連携APIを利用しようと考えたが、利用登録など手続きが煩雑であり、公開プログラムにするには面倒と判断した。

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

解説:準備

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 クラス

日時を扱うクラス 「日付と時刻」で紹介した日時を扱うクラス pahooDateTime は、ファイル "pahooDataStructure.php" に含まれている。
クラスについては「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: }

ユーザー関数 getPCinfo は、政府パブリックコメントRSSから必要な情報を配列に代入する。
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: }

本プログラムの実行直後、ユーザー関数 initDB を実行し、DBが存在しなかったら自動生成する。

解説: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: }

ユーザー関数 getPCinfo によって取得したパブリックコメント情報をDBに登録する関数が storeDB である。
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: }

ユーザー関数 makeTable は、DB検索と、検索結果の一覧表作成を同時に行う。

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;

冒頭で initDB を実行し、必要に応じてDB生成を行う。
次に、isButton 関数を使って、コマンドラインに STORE_DB があれば、関数 storeDB を実行して即終了する。

それ以外の場合は、DB検索を行い、結果をブラウザに表示する。

参考サイト

(この項おわり)
header