こんにちは。バックエンドエンジニアの田中ことTOCです。 今回は、最近ママリアプリでリリースされた地域カテゴリのリニューアルについて、ケンオールとNatural Language APIを用いて実装したので、その内容や工夫ポイントなどを紹介していこうと思います。
目次
はじめに
ママリではQ&Aが様々なカテゴリに分かれているのですが、その中に「地域」というカテゴリが存在しています。このカテゴリでは自身が登録する地域に特化したQ&Aが見られるのですが、以前までは都道府県単位でしか見れず、自身が住んでいる地域とは関係ない質問も多く表示されてしまっていました。 そこで、今回の改善では都道府県だけでなく、市区町村まで登録できるようにして、カテゴリ内で絞り込みができるようになりました。これにより、自分の地域に特化した情報を探しやすくなるなど使いやすさの向上をはかりました。
今回はこの地域カテゴリ改善で市区町村を登録する際にケンオールとNatural Language APIを合わせて使ってみました。
ケンオールを使ってみた!
まずは市区町村をどうやって取得してこようかと考えました。候補として、位置情報、郵便番号、フリーワード検索などが挙がりましたが、社内でのアンケートなどをもとに、郵便番号検索をまず初めに実装しようという話になりました。 そこで真っ先に候補に挙がったのがケンオールでした。
ケンオールは郵便番号APIというものが用意されており、郵便番号をリクエストとして送ると、レスポンスとして都道府県名や市区町村名、市区町村コードなど郵便番号に紐づく情報が簡単に取得することができます。 今回は、これっ!といったライブラリが見つからなかったことや、そもそもAPI自体がシンプルな設計になっていたので、CakePHPのHttpClientを用いて自前で実装を行いました。
<?php public function postalcode(string $postalCode): array { $response = $this->http->get( "https://api.kenall.jp/v1/postalcode/$postalCode", [], ['headers' => ['Authorization' => "Token $this->token"]] ); // レスポンスの処理 }
これにより、常に最新の情報を取得できるので、データの更新コストなどを考えずに実装できて魅力的ですね! ただ、ケンオールでは「神奈川県横浜市青葉区」を取得した場合、下記のように市区町村データが分割されずに取得できます。
{ "jisx0402": "14117", "postal_code": "2270000", "prefecture": "神奈川県", "city": "横浜市青葉区", . . . }
今回は「横浜市」と「青葉区」は分割して取りたい...でも市区町村ってたくさんありすぎてパターン化は難しそう...となったため、GoogleのNatural Language APIを利用することを検討しました。
Natural Language APIで市区町村をいい感じに分割するぞ!
Natural Language APIはGoogleが提供するAPIで、与えた単語について自然言語処理を使って、感情分析、エンティティ分析、エンティティ感情分析など、さまざまな分析を簡単に行うことができます。今回はこのAPIの中でも「エンティティ分析」を用いて単語分割をしてみました。 実装としてはGoogleが公式で提供しているgoogle-cloud-phpというライブラリを用いて実装しました。
まずはドキュメントを参考にクライアントライブラリを設定などをしていきます。 そしてライブラリが使えるようになったら、実際に単語を与えて分割をしてみます。
<?php public function analyzeEntities(string $content): Collection { $annotation = $this->client->analyzeEntities($content, ['language' => 'ja']); // レスポンスの処理 }
上記に「横浜市青葉区」を与えると、以下のようなデータ(配列に変換済み)が取得できます。
Array ( [0] => Array ( [name] => 横浜市 [type] => LOCATION [metadata] => Array ( [wikipedia_url] => https://en.wikipedia.org/wiki/Yokohama [mid] => /m/0kstw ) [salience] => 0.5485816 [mentions] => Array ( [0] => Array ( [text] => Array ( [content] => 横浜市 [beginOffset] => 0 ) [type] => PROPER ) ) ) [1] => Array ( [name] => 青葉区 [type] => LOCATION [metadata] => Array ( [mid] => /m/08ddxr [wikipedia_url] => https://en.wikipedia.org/wiki/Aoba-ku,_Yokohama ) [salience] => 0.45141837 [mentions] => Array ( [0] => Array ( [text] => Array ( [content] => 青葉区 [beginOffset] => 9 ) [type] => PROPER ) ) ) )
うまく分割されていることがわかります。
salience
というのは文章中の関連度みたいなもので、分割された単語はこの salience
スコアが高い順に並べられています。そのまま配列の順番で抽出すると、単語によっては青葉区→横浜市のような順番で抽出されてしまうため、beginOffset
を起点に並び替えてから抽出するようにしています。
このNatural Language APIを用いることで、こちら側で全パターンを網羅するような分岐処理を書かなくても、市区町村を分割した状態で取得できるようになりました!
このようにケンオールとNatural Language APIを組み合わせることで各地域を分割した状態で登録できる状態が実現できました。
工夫した点
ここまでで各APIを用いて、どうやって市区町村登録を実現したのかを述べてきました。 ただ市区町村はいろんなパターンがあり、個別で対応した点もいくつかあったので紹介します。
一つの郵便番号が複数の市区町村に紐づいている
「432-8053」この郵便番号は 「静岡県 浜松市 中区」と「静岡県 浜松市 南区」両方に紐づいています。なのでケンオールでこの郵便番号を検索すると複数のオブジェクトが返ってきます。この場合、どちらか一つの市区町村に寄せてもよかったのですが、今回私たちが行いたい絞り込み検索では中区と南区を区別した方がユーザビリティも上がると考えたので、複数取得できるようにして、選択できる形式にしました。
分割できない市区町村が存在する
Natural Language APIはいい感じに単語を分割してくれますが、それでもうまく認識されない単語も存在します。 例えば「919-0212」の郵便番号は「福井県 南条郡 南越前町」に紐づきますが、これをNatural Language APIを利用すると下記のように分割されずに返ってきます。同じような状態になる市区町村は何個かあったので、こちらは個別対応でロジックを書くようにしました。
Array ( [0] => Array ( [name] => 南条郡南越前町 [type] => LOCATION [metadata] => Array ( ) [salience] => 1 [mentions] => Array ( [0] => Array ( [text] => Array ( [content] => 南条郡南越前町 [beginOffset] => 0 ) [type] => PROPER ) ) ) )
日本語指定をしないと別言語として認識される場合がある
「778-0003」という郵便番号は「徳島県 三好市」に紐づきますが、三好市が中国語と認識されてしまいうまく分割できないケースも発生しました。今回利用したライブラリはオプションで言語の指定ができるので、日本語を扱う場合は下記のように日本語指定をすることでうまく分割できるようになります。
<?php $this->client->analyzeEntities($content, ['language' => 'ja']);
まとめ
以上のような工夫もあり、ママリの地域カテゴリがリニューアルされました。 これで自分の地域を登録して、知りたい情報にいち早く辿り着けるようになりました\(^^)/ 地域カテゴリでは自身が住んでいる地域の病院や子供と遊べる施設などに関する質問が多いです。 今回の改善で利用者がさらに増えていって、自分の地域の子育て情報を得られる有用な場になっていくのが楽しみです!
ちなみにケンオールの利用に伴い、導入事例として掲載していただきました。 もしよければこちらもご覧ください。
最後に、今回紹介したママリアプリを一緒に開発してくれるエンジニアを募集しています。 少しでも興味をもたれた方は、ぜひ一度お話させてもらえるとうれしいです。