実行環境 関連リンクと過去記事 PostgreSQL用の汎用ウェブアクセス関数を、PL/Pythonで作成 作った関数のテスト(以上、1月4日) PL/Pythonで作ったウェブアクセス関数を、PL/v8から使う(1月5日) 関数の改良(ⅰ)JSONを処理してpageidを直接返す(1月8日) 関数の改良(ⅱ)複数行に対するウェブアクセスを、ウィンドウ関数で1回にまとめる(1月9日、終)
Contents


WebAPIの例として、MediaWiki APIを使ってみる

前回の最後に書いたとおり、今回はWikipediaの記事を検索できるMediaWiki APIを使います。選んだ理由は記事タイトルの複数一括検索ができる、例えば「titles=東京|大阪|札幌」のように、バーティカルバー区切りで複数の検索語を渡せること。普通ならウェブアクセスを検索語の数だけ繰り返すところ、上手く使えば1回に集約できそうで興味を持ちました。最終的にはPL/v8でウィンドウ関数を作って処理する予定ですが、今日は前段として簡単な例。

実際に東京/大阪/札幌というタイトルの記事を検索すると ↓ こんな感じ。最低限必要なパラメータはaction=querytitles。これにformat=jsonを付けると正味のデータだけが返り、省略時は下の画像のようにHTML内に整形済JSONが表示されます。タイトル指定は、いま分かってる範囲では完全一致のみ。部分一致もしたいので調査中です。
{
    "batchcomplete": "",
    "query": {
        "pages": {
            "12606": {
                "pageid": 12606,
                "ns": 0,
                "title": "\u5927\u962a"
            },
            "20557": {
                "pageid": 20557,
                "ns": 0,
                "title": "\u672d\u5e4c"
            },
            "1287762": {
                "pageid": 1287762,
                "ns": 0,
                "title": "\u6771\u4eac"
            }
        }
    }
}


検索語に一致するタイトルの記事があればpageidが返り、存在しない場合は"missing":""が返ります。日本語など非アスキーは上の結果のとおりユニコードのコードポイントにエスケープされるけど、次項で作るPL/v8ストアドを通すと元に戻るので大丈夫。

パラメータにprop=revisionsを加えると ↓ 直近の記事修正に関する情報、例えば更新日時などが返ります。さらにrvprop=contentを付けると記事本文が。ともに非アスキーがエスケープされる点は上と同じ。
{
    "batchcomplete": "",
    "query": {
        "pages": {
            "1287762": {
                "pageid": 1287762,
                "ns": 0,
                "title": "\u6771\u4eac",
                "revisions": [
                    {
                        "revid": 58090212,
                        "parentid": 57815427,
                        "user": "61.125.222.87",
                        "anon": "",
                        "timestamp": "2016-01-01T16:00:18Z",
                        "comment": "Otheruses|\u65e5\u672c\u306e(\u65e5\u672c\
u4ee5\u5916\u306e\u5730\u540d\u3082\u3042\u308b\u306e\u3067\u56fd\u969b\u5316)"
                    }
                ]
            }
        }
    }
}

{
    "batchcomplete": "",
    "query": {
        "pages": {
            "1287762": {
                "pageid": 1287762,
                "ns": 0,
                "title": "\u6771\u4eac",
                "revisions": [
                    {
                        "contentformat": "text/x-wiki",
                        "contentmodel": "wikitext",
                        "*": "{{Otheruses|\u65e5\u672c\u306e\u90fd\u5e02\u3068\
u3057\u3066\u306e\u6771\u4eac|\u884c\u653f\u533a\u5206|\u6771\u4eac\u90fd|\u305d\
u306e\u4ed6}}\n[[\u30d5\u30a1\u30a4\u30eb:Tokyo Montage 2015.
(...)


記事本文の検索は ↓ こんな感じ。パラメータlist=searchsrwhat=textを指定しsrsearchに検索語を入れれば、それを本文に含む記事の情報がデフォルトで10件返ります。
APIの説明を読むとsrwhat=title指定でタイトルの部分一致検索もできそうな感じですが、今日試したところsearch-title-disabledというエラーが返ってきて謎。
{
    "batchcomplete": "",
    "continue": {
        "sroffset": 10,
        "continue": "-||"
    },
    "query": {
        "searchinfo": {
            "totalhits": 640
        },
        "search": [
            {
                "ns": 0,
                "title": "PostgreSQL",
                "snippet": "<span class=\"searchmatch\">Postgre</span>SQL, often
simply <span class=\"searchmatch\">Postgres</span>, is an object-relational
database management system (ORDBMS) with an emphasis on extensibility and
standards-compliance",
                "size": 81026,
                "wordcount": 7261,
                "timestamp": "2015-12-30T18:52:57Z"
            },
            {
                "ns": 0,
                "title": "EnterpriseDB",
                "snippet": "products, <span class=\"searchmatch\">Postgres</span> 
Plus Enterprise Edition and <span class=\"searchmatch\">Postgres</span> Plus
Standard Edition. 
(...)


PL/v8MediaWiki APIの「タイトル → pageid」検索関数を作る

テスト用ストアドとして ↓ 前項の最初のクエリを例に。タイトルへの検索語(複数可)を文字列の配列として渡し、結果をJSON型で、リクエストしたURLも含めて返します。
create or replace function plv8_MediaWiki_ex1(wiki_titles text[])
returns json language plv8 immutable as $$
    var url = 'https://en.wikipedia.org/w/api.php?action=query&format=json&titles='
            + encodeURIComponent(wiki_titles.join('|')),
        sql = "select convert_from(plpy_urlopen('" + url + "'), 'utf8')",
        res = plv8.execute(sql);
    return { 'url': url, 'response': JSON.parse(res[0]['convert_from']) };
$$;

実行すると ↓ こんな感じ。前項のブラウザではエスケープされた日本語が、こちらでは元に戻ります。二つ目のクエリが、結果のJSONデータをPostgreSQLの演算子で絞り込んだもの。
select plv8_MediaWiki_ex1('{東京}');

plv8_mediawiki_ex1
{"url":"https://en.wikipedia.org/w/api.php?action=query&format=json&titles=%E6%9D%B1%
E4%BA%AC","response":{"batchcomplete":"","query":{"pages":{"2126632":{"pageid":212663
2,"ns":0,"title":"東京"}}}}}
(1 row)

select plv8_MediaWiki_ex1('{東京}')->'response'->'query'->'pages';
?column?
{"2126632":{"pageid":2126632,"ns":0,"title":"東京"}}
(1 row)


↓ 複数の検索語を渡した結果。MediaWiki APIの仕様で、それぞれの検索語の結果は配列でなく「pageid:オブジェクト, pageid:オブジェクト, ...」となり、順序付けがありません。だから各オブジェクトを各検索語にひも付けるのが面倒ですが、次回作る関数は、この処理を含めて複数検索語のクエリを使いやすくします。
select plv8_MediaWiki_ex1('{東京,大阪,札幌}');

plv8_mediawiki_ex1
{"url":"https://en.wikipedia.org/w/api.php?action=query&format=json&titles=%E6%9D%B1%
E4%BA%AC%7C%E5%A4%A7%E9%98%AA%7C%E6%9C%AD%E5%B9%8C","response":{"batchcomplete":"","q
uery":{"pages":{"2126632":{"pageid":2126632,"ns":0,"title":"東京"},"3211539":{"pageid
":3211539,"ns":0,"title":"大阪"},"3211578":{"pageid":3211578,"ns":0,"title":"札幌"}}}
}}
(1 row)