PHPで「Wikipedia API」を利用する

(1/1)
フリーの百科事典として有名な「Wikipedia」にも Wikipedia API と呼ぶ WebAPI が用意されている。検索キーワードにヒットする見出し語のサマリなどを返してくれる Web サービスで、利用料はかからない。
今回は、PHP で WebAPI を利用する手始めとして、Wikipedia API を利用し、検索キーワードを入力し、ヒットした見出し語のサマリを表示するプログラムを作ってみることにする。
さらに、KAKASI を使った単語分解処理と組み合わせることで、用語の解説として Wikipedia のハイパーリンクを張ったようなサイトを構築することもできるだろう。

なお、本記事は作成当初 http://wikipedia.simpleapi.net を利用するプログラムを紹介していたが、2020 年(令和 2 年)12 月現在、この WebAPI は動作していないようなので、Wikipedia API を利用するプログラムに全面改訂した。

(2021 年 4 月 6 日)PHP8 対応,リファラ・チェック改良

目次

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

PHPで「Wikipedia API」を利用する

サンプル・プログラム

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

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

PHPで「Wikipedia API」を利用する
まず、変数 $query に検索キーワードが入っていたら、検索処理のルートに入る。
次に、WebAPI の応答フォーマットが XML か JSON かを識別する。後述するが、一般的なWebAPI には XML を返すものと JSON を返すものがあり、今回は学習用として、どちらにも対応できるような関数を用意している。

応答フォーマットが XML のとき、WebAPI をコールして、サマリを返す関数が searchWikipediaSummaryXML である。Wikipedia API を呼び出したら、エラーチェックを行い、エラーが無ければ変数 $res にサマリーを、エラーがあれば FALSE を返す。

最後に、makeCommonBody 関数を使って HTML の body タグ部分を生成し、あらかじめ用意したヘッダとフッタをあわせて、ブラウザに表示する。

今後紹介するマッシュアップ・プログラムも、基本的にこのフローに沿って構築されている。

Wikipedia API

Wikipedia API は、入力パラメータ(IN)は GET 渡しで、出力結果(OUT)は XML や JSON で返ってくるという無償の WebAPI である。
WebAPIのURL
URL
https://ja.wikipedia.org/w/api.php

入力パラメータ
フィールド名 要否 内  容
format 省略可 xml, json, yaml等
action 必須 操作:ここではquery
prop 省略可 action固有のパラメータ。記事の各構成要素を取得する。ここではextractsを指定し、サマリを抽出する。
応答データ(xml) api query pages page extract サマリー

解説:初期値など

0014: // 初期化処理 ================================================================
0015: //内部エンコード
0016: define('INTERNAL_ENCODING', 'UTF-8');
0017: mb_internal_encoding(INTERNAL_ENCODING);
0018: mb_regex_encoding(INTERNAL_ENCODING);
0019: 
0020: //自分自身のファイル名
0021: define('MYSELF', basename($_SERVER['SCRIPT_NAME']));
0022: 
0023: //参考サイトURL
0024: define('REFERENCE', 'https://www.pahoo.org/e-soul/webtech/php06/php06-03-01.shtm');
0025: 
0026: //プログラム・タイトル
0027: define('TITLE', 'Wikipedia検索');
0028: 
0029: //リファラチェック+リリースフラグの設定
0030: if (isset($_SERVER['HTTP_HOST']) && ($_SERVER['HTTP_HOST'] == 'localhost')) {
0031:     define('FLAG_RELEASE', FALSE);
0032:     define('REFER_ON', '');
0033:     ini_set('display_errors', 1);
0034:     ini_set('error_reporting', E_ALL);
0035: else {
0036:     //リリース・フラグ(公開時にはTRUEにすること)
0037:     define('FLAG_RELEASE', TRUE);
0038:     //リファラ・チェック(直リン防止用;空文字ならチェックしない)
0039:     define('REFER_ON', 'www.pahoo.org');
0040: }
0041: 
0042: //応答フォーマット:切替可能
0043: define('REQUEST_FORMAT', 'xml');
0044: //define('REQUEST_FORMAT', 'json');
0045: 
0046: //API呼び出しURL:変更不可
0047: define('REQUEST_WIKIPEDIA', 'https://ja.wikipedia.org/w/api.php');
0048: 
0049: //初期値
0050: //表示幅(ピクセル)
0051: define('WIDTH',  600);
0052: //検索キーワード(デフォルト)
0053: define('DEF_QUERY', 'PHP');

 define  関数を使って、いくつかの初期値を定義している。変更不可としている定数以外は自由に改変できる。
後述する応答フォーマットを REQUEST_FORMAT によって切り換えることができるようにしてある。

解説:サマリのリクエストURLを取得する

0154: /**
0155:  * Wikipedia API:サマリのリクエストURLを取得する
0156:  * @param string $query  検索キーワード(UTF-8)
0157:  * @return string URL URL
0158: */
0159: function getURL_WikipediaAPI_summary($query) {
0160:     return REQUEST_WIKIPEDIA . '?format=' . REQUEST_FORMAT . '&action=query&prop=extracts&exintro&explaintext&redirects=1&titles=' . urlencode($query);
0161: }

ユーザー関数 getURL_WikipediaAPI_summary は、検索キーワードを引数にして、Wikipedia API のリクエスト URL を返す。検索キーワードは UTF-8 で渡すこと。

Wikipedia検索(サマリ):XML応答

0163: /**
0164:  * Wikipedia検索(サマリ):XML応答
0165:  * @param string $query  検索キーワード(UTF-8)
0166:  * @param string $errmsg エラーメッセージを格納
0167:  * @return string サマリ/FALSE:エラー発生
0168: */
0169: function searchWikipediaSummaryXML($query, &$errmsg) {
0170:     $url = getURL_WikipediaAPI_summary($query);      //リクエストURL
0171: 
0172:     //XMLエラーを有効にする
0173:     libxml_use_internal_errors(TRUE);
0174: 
0175:     //応答読み込み
0176:     $xml = @simplexml_load_file($url);
0177: 
0178:     //Wikipedia API接続失敗
0179:     if (count(libxml_get_errors()) > 0) {
0180:         $res = FALSE;
0181:         $errmsg = 'Wikipedia APIに接続できません';
0182:     //検索結果あり
0183:     } else if (isset($xml->query->pages->page->extract)) {
0184:         $res = $xml->query->pages->page->extract;
0185:         $errmsg = '';
0186:     //検索結果なし
0187:     } else {
0188:         $res = FALSE;
0189:         $errmsg = '検索結果がありません';
0190:     }
0191: 
0192:     return $res;
0193: }

ユーザー関数 searchWikipediaSummaryXML は、検索キーワードとエラーメッセージを格納する変数を引数にして、XML で応答結果(サマリー)を受け取る場合に使う。

まず、検索キーワードをユーザー関数 getURL_WikipediaAPI_summary に渡し、リクエスト URL を受け取る。
次に、 libxml_use_internal_errors  関数で XML エラーを有効にしてから、 simplexml_load_file  関数で応答XML を受け取る。
XML エラーを有効にしたので、リクエスト URL が反応しない場合は、 libxml_get_errors  関数にエラーが入る。結果の配列が無ければ、リクエスト URL は何らかの応答を返している。
最後に、応答構造 $xml->query->pages->page->extract が存在するかどうかをチェックし、存在すれば、それがサマリーである。
無ければ、検索結果無しのエラーを返す。

解説:Wikipedia検索(サマリ):JSON応答

0195: /**
0196:  * Wikipedia検索(サマリ):JSON応答
0197:  * @param string $query  検索キーワード
0198:  * @param string $errmsg エラーメッセージを格納
0199:  * @return string サマリ/FALSE:エラー発生
0200: */
0201: function searchWikipediaSummaryJSON($query, &$errmsg) {
0202:     $url = getURL_WikipediaAPI_summary($query);      //リクエストURL
0203: 
0204:     $json = @file_get_contents($url);
0205: 
0206:     //Wikipedia API接続失敗
0207:     if ($json == FALSE) {
0208:         $res = FALSE;
0209:         $errmsg = 'Wikipedia APIに接続できません';
0210:     //JSON解釈
0211:     } else {
0212:         $res = FALSE;
0213:         $errmsg = '検索結果がありません';
0214:         $json = json_decode($json);
0215:         foreach ($json as $obj1) {
0216:             if (is_object($obj1)) {
0217:                 foreach ($obj1 as $obj2) {
0218:                     if (is_object($obj2)) {
0219:                         foreach ($obj2 as $obj3) {
0220:                             if (is_object($obj3)) {
0221:                                 if (isset($obj3->extract)) {
0222:                                     $res = (string)$obj3->extract;
0223:                                     $errmsg = '';
0224:                                 }
0225:                             }
0226:                         }
0227:                     }
0228:                 }
0229:             }
0230:         }
0231:     }
0232:     return $res;
0233: }

ユーザー関数 searchWikipediaSummaryJSON は、検索キーワードとエラーメッセージを格納する変数を引数にして、JSON で応答結果(サマリー)を受け取る場合に使う。
一般的に WebAPI では、応答が XML の場合と JSON の場合とがあるので、ここでは学習のため、両方のユーザー関数を用意した。

まず、検索キーワードをユーザー関数 getURL_WikipediaAPI_summary に渡し、リクエスト URL を受け取るところはユーザー関数 searchWikipediaSummaryXML と同じである。
JSON の場合、応答は  file_get_contents  関数を使って受け取る。戻り値が FALSE であれば、リクエスト URL が反応していないとしてエラーを返す。
戻り値があれば、 json_decode 
が、応答構造が可変であるため、foreach を使ってオブジェクト・ツリーを下ってゆく。

HTML BODYを作成する

0235: /**
0236:  * HTML BODYを作成する
0237:  * @param string $query    検索キーワード
0238:  * @param string $summary  Wikipediaサマリ
0239:  * @param string $errmsg   エラーメッセージ;エラー無しの時は空文字
0240:  * @param string $url      リクエストURL
0241:  * @return string HTML BODY
0242: */
0243: function makeCommonBody($query$summary$errmsg$url) {
0244:     $myself  = MYSELF;
0245:     $refere  = REFERENCE;
0246:     $width   = WIDTH;
0247:     $p_title = TITLE;
0248:     $version = '<span style="font-size:small;">' . date('Y/m/d版', filemtime(__FILE__)) . '</span>';
0249: 
0250:     //表示内容を $msg へ
0251:     if ($errmsg == '') {
0252:         $msg = '<h3>検索結果</h3>' . nl2br($summary);
0253:     } else {
0254:         $msg = '<span style="color:red;">error: ' . $errmsg . '</span>';
0255:     }
0256: 
0257:     //デバッグ用表示
0258:     if (! FLAG_RELEASE) {
0259:         $phpver = phpversion();
0260:         $format = REQUEST_FORMAT;
0261: $phpver =<<< EOT
0262: PHPver : {$phpver}<br />
0263: FORMAT : {$format}<br />
0264: WebAPI : <a href="{$url}">{$url}</a><br />
0265: <dl>
0266: 
0267: EOT;
0268:     } else {
0269:         $phpver = '';
0270:     }
0271: 
0272: $body =<<< EOT
0273: <body>
0274: <h2>{$p_title} {$version}</h2>
0275: <form name="myform" method="GET" action="{$myself}" enctype="multipart/form-data">
0276: 検索キーワード:
0277: <input type="text" name="query" id="query" size="40" value="{$query}" />
0278: <input type="submit" name="exec"  value="検索" /> 
0279: <input type="submit" name="reset" value="リセット" />
0280: </form>
0281: <div style="border-style:solid; border-width:1px; margin:20px 0px 0px 0px; padding:5px; width:{$width}px; overflow-wrap:break-word; word-break:break-all;">
0282: {$msg}
0283: </div>
0284: 
0285: <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;">
0286: <h3>使い方</h3>
0287: <ol>
0288: <li>[<span style="font-weight:bold;">検索キーワード</span>]に検索したいキーワードを入力してください.</li>
0289: <li>[<span style="font-weight:bold;">検索</span>]ボタンを押してください.</li>
0290: <li>検索結果を表示します.</li>
0291: <li>[<span style="font-weight:bold;">リセット</span>]ボタンを押すと,初期化します.</li>
0292: </ol>
0293: ※参考サイト:<a href="{$refere}">{$refere}</a>
0294: <p>{$phpver}</p>
0295: </div>
0296: </body>
0297: 
0298: EOT;
0299:     return $body;
0300: }

検索キーワード、サマリ、エラーメッセージ、リクエスト URL を引数として、ブラウザに表示する HTML の body タグを生成するのがユーザー関数 makeCommonBody である。
検索キーワード入力と結果表示を 1 つの関数で処理している。入力した検索キーワードは URL 変数 query に追加し、再び自分自身(PHP プログラム)を呼び出す。頭で説明したサンプル・プログラムの流れの通り、query に値が入ることで検索処理を実行する仕組みである。

まず、エラーメッセージがあるかどうかをチェックし、無ければ「検索結果」の見出しを、あればエラーメッセージを赤色にして変数 $msg に代入する。変数 $msg は、このあと、結果表示用に利用する。

デバッグ用に定数 FLAG_RELEASE が FALSE のときは、PHP のバージョン、応答フォーマット(XML または JSON)、WebAPI のリクエスト URL を変数 $phpver に代入する。

最後に、変数 $body に body タグの内容を代入していく。
代入する内容が長いので、実際の HTML タグを書くようにして代入ができる PHP のヒアドキュメント構文]を利用している。可変部分は "{$変数}:blue" のようにして、変数の値を代入することができる。

メイン・プログラム

0302: // メイン・プログラム =======================================================
0303: //初期値
0304: $query = getParam('query', TRUEDEF_QUERY);
0305: $summary = $errmsg = '';
0306: 
0307: //リセット
0308: if (isButton('reset')) {
0309:     $query = DEF_QUERY;
0310: }
0311: 
0312: //検索実行
0313: if ($query != '') {
0314:     if (REQUEST_FORMAT == 'xml') {
0315:         $summary = searchWikipediaSummaryXML($query$errmsg);
0316:     } else {
0317:         $summary = searchWikipediaSummaryJSON($query$errmsg);
0318:     }
0319: }
0320: 
0321: //リクエストURL
0322: $url = getURL_WikipediaAPI_summary($query);
0323: 
0324: $HtmlBody = makeCommonBody($query$summary$errmsg$url);
0325: 
0326: // 表示処理
0327: echo $HtmlHeader;
0328: echo $HtmlBody;
0329: echo $HtmlFooter;

メイン・プログラムの流れは、冒頭で説明したサンプル・プログラムの流れの通りである。

まず、URL 変数として query があれば、それを検索キーワードとして変数 $query に代入する。
reset ボタンが押下されたら、変数 $query の値をデフォルト値にする。
続いて、検索を実行する。
最後に、makeCommonBody を呼び出し、事前に用意してあるヘッダ $HtmlHeader、フッタ $HtmlFooter とあわせてブラウザに表示する。
メイン・プログラムで呼び出している、その他の関数については後述する。

指定したパラメータを取り出す

0139: /**
0140:  * 指定したパラメータを取り出す
0141:  * @param string $key  パラメータ名(省略不可)
0142:  * @param bool   $auto TRUE=自動コード変換あり/FALSE=なし(省略時:TRUE)
0143:  * @param mixed  $def  初期値(省略時:空文字)
0144:  * @return string パラメータ/NULL=パラメータ無し
0145: */
0146: function getParam($key$auto=TRUE$def='') {
0147:     if (isset($_GET[$key]))         $param = $_GET[$key];
0148:     else if (isset($_POST[$key]))   $param = $_POST[$key];
0149:     else                            $param = $def;
0150:     if ($auto)  $param = mb_convert_encoding($paramINTERNAL_ENCODING, 'auto');
0151:     return $param;
0152: }

ユーザー関数 getParam は、本プログラムを実行した URL からパラメータを取り出す。

引数 $key はパラメータ名である。
たとえば "wikisearch.php?query=PHP" のようにして呼び出されたら、$key に "query" を指定することで、値 "PHP" を返す。
日本語は URL エンコードされるので、受け取った値をデコードしたいときは、引数 $autoTRUE にする。
URL 変数が見当たらないときの初期値は、引数 $def に代入する。
引数 $auto および $def は、PHP の可変長引数リスト機能を使って省略可能にしてある。省略時には、イコールの右側の値が代入される。

URL 変数は、GET 渡しが優先で、無ければ POST 渡しを探し、それでも無ければ $def の値を返す。

指定したボタンが押下されたか

0128: /**
0129:  * 指定したボタンが押下されたか
0130:  * @param string $btn  ボタン名
0131:  * @return bool TRUE=押された/FALSE=押されていない
0132: */
0133: function isButton($btn) {
0134:     if (isset($_GET[$btn]) && $_GET[$btn] != '')    return TRUE;
0135:     if (isset($_POST[$btn]) && $_POST[$btn] != '')  return TRUE;
0136:     return FALSE;
0137: }

ユーザー関数 isButton は、本プログラムを呼び出した画面で指定したボタンが押下されたかどうかを調べる。

考え方は getParam と同じである。値を返さず、そのパラメータが URL 変数に存在するかどうかをチェックして返す。

PHP5以上かどうか検査する

0118: /**
0119:  * PHP5以上かどうか検査する
0120:  * @return bool TRUE:PHP5以上/FALSE:PHP5未満
0121: */
0122: function isphp5over() {
0123:     $version = explode('.', phpversion());
0124: 
0125:     return $version[0] >= 5 ? TRUE : FALSE;
0126: }

ユーザー関数 isphp5over は、動作環境が PHP5 以上かどうかを調べる。
プログラム冒頭で実行し、PHP5 以上でなければエラーメッセージを表示して、強制終了する。

リファラ・チェック

0102: //リファラ・チェック
0103: if (REFER_ON != '') {
0104:     if (isset($_SERVER['HTTP_REFERER'])) {
0105:         $url = parse_url($_SERVER['HTTP_REFERER']);
0106:         $res = ($url['host'] == REFER_ON) ? TRUE : FALSE;
0107:     } else {
0108:         $res = FALSE;
0109:     }
0110: else {
0111:     $res = TRUE;
0112: }
0113: if (! $res) {
0114:     echo 'Please refer to ' . REFER_ON . ' !';
0115:     exit(1);
0116: }
0117: 
0118: /**

この部分は、サーバ負荷を避けるため、本プログラムへ直接リンク(直リン)しても動作しないようにするための簡単なトラップである。
定数 REFER_ON には、あらかじめドメイン名をセットしておく。本プログラムが、このドメインから呼び出されていなければ、エラーメッセージを表示して、強制終了する。

エラー処理ハンドラ

0086: /**
0087:  * エラー処理ハンドラ
0088: */
0089: function myErrorHandler ($errno$errmsg$filename$linenum) {
0090:     echo 'Sory, system error occured !';
0091:     exit(1);
0092: }

0093: error_reporting(E_ALL);
0094: if (FLAG_RELEASE)   $old_error_handler = set_error_handler('myErrorHandler');

定数 FLAG_RELEASETRUE のときには、PHP のエラーメッセージ表示を抑制するための処理である。
自前のエラーハンドラ関数 myErrorHandler に差し替えて、簡単なエラーメッセージを表示して強制終了する。

参考サイト

(この項おわり)
header