ウエブAPIを用いた初めてのCGI (perl編) プログラム演習3

さて、前回の準備編で用意したプログラムをもとに、ヤフーウエブ検索APIへキーワードを送信し、そのウエブ検索結果を取得してCGIとして画面に出力しましょう。
処理は大きく分けて、

  • HTTP経由でAPI結果(XML)を取得する
  • 取得したXMLをパース(解析)する
  • 画面に出力する

今回、パースしながら画面に描画しているので、API取得のXML解析がメインです。それぞれ複数の方法でプログラムしています。少々泥臭い方法ですが、if文の部分を 0/1で切り替えてそれぞれの方法を試してみてください.

bash-3.2$ cat index.pl 
#!/usr/bin/perl

use strict;
use warnings;
use Data::Dumper;
use Socket;
use CGI;
use HTTP::Request;
use XML::DOM;
use XML::Simple;
use XML::LibXML;

my $SEARCH_API_HOST  = 'search.yahooapis.jp';
my $SEARCH_API_URL   = '/WebSearchService/V1/webSearch';
my $SEARCH_API_APPID = 'p3O1lFOxg66up7VWRDmVkEJnF6baGNJj38rT6gUY3DzyF.kX4_PepRsfnk1AYss1J.YG';

print STDERR `date` . " script start\n";

my $cgiObj = new CGI;
my $script_name = ($ENV{'SCRIPT_NAME'} =~ m|/([^/]*)$|)[0];

print "Content-type: text/html\n\n";

print<<EOM;
<html>
<head>
<title>Sample Program 3 perl-1</title>
<meta http-equiv="Content-Type" content="text/html" charset="utf-8">
</head>
<body>
EOM

print '<form action="./' . $script_name . '">';
print '<input type="input" name="query" value="'. $cgiObj->param('query') . '">';
print '<input type="submit" value="search">';
print '</form>';

my $responseXML = '';
my $encodeQuery = $cgiObj->param('query');
$encodeQuery =~ s/(\W)/'%' . unpack('H2', $1)/eg;

# HTTPでXMLを取得する方法 ( use Socket; を利用)
if(1){
    socket(SOCKET, PF_INET, SOCK_STREAM, 0);
    connect(SOCKET, pack_sockaddr_in(80, inet_aton($SEARCH_API_HOST)));
    select(SOCKET); $|=1; select(STDOUT);
    print SOCKET "GET ${SEARCH_API_URL}?appid=${SEARCH_API_APPID}&query=${encodeQuery} HTTP/1.0\r\n";
    print SOCKET "\r\n";
    while(<SOCKET>) {
        last if(/^\r\n$/);
    }
    while(<SOCKET>){
        $responseXML .= $_;
    }
}
# HTTPでXMLを取得する方法 ( use LWP; を利用)
if(0) {
    my $ua = new LWP::UserAgent;
    my $req = HTTP::Request->new(GET=>"http://${SEARCH_API_HOST}${SEARCH_API_URL}?appid=${SEARCH_API_APPID}&query=${encodeQuery}");
    my $res = $ua->request($req);
    $responseXML = $res->content;
}

# XMLの解析と出力 ( use XML::DOM; を利用 )
if(1){
    my $parser = new XML::DOM::Parser;
    my $doc = $parser->parse($responseXML);
    my $nodes = $doc->getElementsByTagName('Result');
    my $nodeCount = $nodes->getLength();
    for my $i (0..($nodeCount-1)) {
        my $title = $nodes->item($i)->getElementsByTagName('Title')->item(0)->getChildNodes()->item(0)->toString;
        my $url   = $nodes->item($i)->getElementsByTagName('ClickUrl')->item(0)->getChildNodes()->item(0)->toString;
        print qq|<li><a href="${url}">${title}</a>\n|;
    }
}

# XMLの解析と出力 ( use XML::Simple; を利用 )
if(0) {
    my $xmlSimple = new XML::Simple;
    my $doc = $xmlSimple->XMLin($responseXML);
    foreach my $site (@{$doc->{'Result'}}) {
        my $title = $site->{'Title'};
        my $url   = $site->{'ClickUrl'};
        print qq|<li><a href="${url}">${title}</a>\n|
    }
}
# XMLの解析と出力 ( use XML::LibXML; を利用 )
if(0) {
    my $parser = new XML::LibXML;
    my $doc = $parser->parse_string($responseXML, 'utf-8');
    my $root = $doc->documentElement();
    foreach my $site ($root->getChildrenByTagName('Result')) {
        my $title = $site->getChildrenByTagName('Title')->string_value;
        my $url   = $site->getChildrenByTagName('ClickUrl')->string_value;
        print qq|<li><a href="${url}">${title}</a>\n|;
    }
}

print<<EOM;
</body>
</html>
EOM

print STDERR "query=" . $cgiObj->param('query');
print STDERR `date` . " script end\n";

exit;

複数の方法で解説しましたが、APIの取得の部分は LPW, XMLの解析の部分はXML::Simpleが簡単に使えるえて便利です。
XMLの解析は処理コストがかかる部分で大規模なシステムを開発する場合はボトルネックになる場合があるためその用途では解析速度が速い(解析処理はCのライブラリを利用する)XML::LibXMLが使われるようです。(Perl で XML の処理はどれが速いかベンチ)

【演習】
ウエブAPIからの応答XMLを利用すると上のサンプル以上に豊富な情報をもつ(ヤフーのウエブ検索結果に似た)結果を画面に出力することができる。HTMLのタグリファレンスを調べて作ったCGIをリッチにしましょう。
参考

【演習】
ヤフーAPIの応答のstart= のパラメータを用いると検索結果の10位以降も取得できます。
検索ヒット件数(XMLで応答される)の表示と次の10件, 前の10件も表示できるように改良しましょう