読者です 読者をやめる 読者になる 読者になる

さわだのノート

書籍のお仕事に役立つかもしれない思いつきを記録しています。

RSS: 記事の更新情報 Rss Feed

PerlでYahoo!校正APIを使う

Yahoo!デベロッパーネットワーク校正支援APIPerlから使ったときにはまったのでメモを。

校正支援APIは日本語の間違いらしきところを指摘してくれるWeb APIです。アプリケーションIDを取得すれば簡単・無料で原稿をチェックできるうえ、一日につき50,000件までリクエストもできるという使い勝手のよさが魅力です。

リクエスト方法はGETもしくはPOSTで行います。いつものごとくPerlを使って試してみたのですが、GETとPOSTで違う結果が出てしまいました。

use v5.18;
use warnings;
use utf8;
use URI;
use LWP::UserAgent;
use HTTP::Request::Common qw/POST/;

my $ua = LWP::UserAgent->new;
my $url = 'http://jlp.yahooapis.jp/KouseiService/V1/kousei';
my $appid = 'your application id';
my $sentence = <<'EOF';

遙か彼方に小形飛行機が見える
EOF

say 'POSTで実行';

my %param = ( appid => $appid, sentence => $sentence,);
my $req = POST $url, \%param;

my $res = $ua->request($req);
say $res->decoded_content;

say "\n";

say 'GETで実行';

$uri->query_form( appid => $appid, sentence => $sentence,);
my $get = $ua->get($uri);

say $get->decoded_content;

結果がこちら。

POSTで実行
<?xml version="1.0" encoding="UTF-8"?>
<ResultSet xmlns="urn:yahoo:jp:jlp:KouseiService" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:yahoo:jp:jlp:KouseiService http://jlp.yahooapis.jp/KouseiService/V1/kousei.xsd">
  <Result>
    <StartPos>2</StartPos>
    <Length>2</Length>
    <Surface>遙か</Surface>
    <ShitekiWord>●か</ShitekiWord>
    <ShitekiInfo>表外漢字あり</ShitekiInfo>
  </Result>
  <Result>
    <StartPos>4</StartPos>
    <Length>2</Length>
    <Surface>彼方</Surface>
    <ShitekiWord>彼方(かなた)</ShitekiWord>
    <ShitekiInfo>用字</ShitekiInfo>
  </Result>
  <Result>
    <StartPos>7</StartPos>
    <Length>5</Length>
    <Surface>小形飛行機</Surface>
    <ShitekiWord>小型飛行機</ShitekiWord>
    <ShitekiInfo>誤変換</ShitekiInfo>
  </Result>
</ResultSet>


GETで実行
<?xml version="1.0" encoding="UTF-8"?>
<ResultSet xmlns="urn:yahoo:jp:jlp:KouseiService" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:yahoo:jp:jlp:KouseiService http://jlp.yahooapis.jp/KouseiService/V1/kousei.xsd">
  <Result>
    <StartPos>1</StartPos>
    <Length>2</Length>
    <Surface>遙か</Surface>
    <ShitekiWord>●か</ShitekiWord>
    <ShitekiInfo>表外漢字あり</ShitekiInfo>
  </Result>
  <Result>
    <StartPos>3</StartPos>
    <Length>2</Length>
    <Surface>彼方</Surface>
    <ShitekiWord>彼方(かなた)</ShitekiWord>
    <ShitekiInfo>用字</ShitekiInfo>
  </Result>
  <Result>
    <StartPos>6</StartPos>
    <Length>5</Length>
    <Surface>小形飛行機</Surface>
    <ShitekiWord>小型飛行機</ShitekiWord>
    <ShitekiInfo>誤変換</ShitekiInfo>
  </Result>
</ResultSet>

原因

StartPos要素の値がPOSTとGETとで変わっています。これ、GETの結果のほうが実は正確です。じゃあなんでこんな簡単な内容で違う結果が出てくるのだろう……というところでずいぶんはまってしまいました。

結論から言うと、HTTP::Request::CommonのPOST関数が悪さをしていたみたいです。POST関数の第二引数にハッシュリファレンス(もしくは配列リファレンス)を渡すと、内部でURIモジュールを使ってパラメーターを組み立てます。

そのあと正規表現で「%0A」を「%0D%0A」に置換しているようなのです。

# HTML/4.01 says that line breaks are represented as "CR LF" pairs (i.e., `%0D%0A')
$content =~ s/(?<!%0D)%0A/%0D%0A/g if defined($content);

つまり改行コードの「LF」を「CRLF」に変換しているのですね。それで改行を含むテキストだと、StartPos要素の値が改行のたびに1ずつずれていく……というのが原因のようです。なぜこのような仕組みになっているのかまではよくわかりませんでした。

とりあえず解決案

ということはPOST関数には第二引数を渡さずに、HTTP::Requestのコンストラクタに直接パラメーターを設定してやればうまくいきそうです。

use v5.18;
use warnings;
use utf8;
use URI;
use LWP::UserAgent;
use HTTP::Request::Common qw/POST/;

my $ua = LWP::UserAgent->new;
my $url = 'http://jlp.yahooapis.jp/KouseiService/V1/kousei';
my $appid = 'your application id';
my $sentence = <<'EOF';

遙か彼方に小形飛行機が見える
EOF

my %param = ( appid => $appid, sentence => $sentence, );

# パラメーターを自分で組み立て
my $testurl = URI->new('http:');
$testurl->query_form(%param);
my $content = $testurl->query;

# HTTP::Requestのコンストラクタを作成&値の設定
# Content-Lengthの設定は必須。手動で設定しています
my $req = POST $url;
$req->content($content);
$req->content_length(length $content);

say 'POSTで実行';
my $res = $ua->request($req);
say $res->decoded_content;

これでGETと同じ結果を取得できました。

ソースを読んで問題解決を図るなんて経験を初めてしました。いろいろと悩まされたものの、いい経験をしたのではないか……と思うことにしておきます。