TypeScript
Angular2
YahooAPI

Yahoo!ショッピングAPIを使って検索ワードからレビューを取得するまで(TypeScript)

はじめに

なんかアフィリエイトに繋がるサイトを作りたいなぁと思い、商品のレビュー一覧が見れればその商品に対する世間の評価が知れて良いんじゃないか!!という軽い気持ちで作り出した(すでにあるとか商品レビューには店舗への評価も含まれるから純粋な商品評価ではないなどは置いといて…笑)。
その途中でYahoo!ショッピングの商品検索API商品レビュー検索APIを使ったのでメモも兼ねて投稿しました。
あ、ちなみにTypeSctiptは初めて使ったので温かい目で見てください。

概要

ユーザーが検索キーワードを入力

商品検索APIを使ってキーワードから商品を検索

検索結果の商品それぞれのレビューを商品レビュー検索APIを使って取得

※ Yahoo!JapanのWebAPIの利用には"アプリケーションID"が必要になるのでここから取得

使用技術

  • Angular2
  • TypeScript

内容

1. フォームを作成

form.html
<form
    [formGroup]="shoppingSearchForm"
    (ngSubmit)="onSubmit(shoppingSearchForm.value)"
  >

  <div class="field">
    <label for="keyword">商品名</label>
    <input
      type="text"
      id="keyword"
      placeholder="iPhone X"
      [formControl]="shoppingSearchForm.controls['keyword']"
    >
  </div>

  <button class="ui button" type="submit" [disabled]="!shoppingSearchForm.valid">レビュー取得</button>
</form>

2. 検索キーワードを使って商品を検索

【商品検索APIをたたく処理を記載したclass】

yahoo-shopping-api.service.ts
import { Injectable } from '@angular/core';
import { Jsonp, URLSearchParams } from '@angular/http';
import { Observable } from 'rxjs/Rx';
import 'rxjs/add/operator/map';

@Injectable()
export class YahooShoppingApiService {

  constructor(private _jsonp: Jsonp) { }

  /**
   * 商品検索API
   */
  itemSearch (query: string, results: number) {
    // appid
    const appid = '[自分のappid]'
    // パラメータを設定
    const searchParams = new URLSearchParams();
    searchParams.set('appid', appid);
    searchParams.set('query', query || '');
    searchParams.set('results', results.toString());
    searchParams.set('callback', 'JSONP_CALLBACK');

    // APIをたたく
    return this._jsonp
      .get('https://shopping.yahooapis.jp/ShoppingWebService/V1/json/itemSearch', { search: searchParams })
      .map(res => res.json());
  }
}

【上記の処理を使う側のclass】
ここではページ遷移先で取得した商品情報を使うためlocalStorageに情報を保存(ページ遷移の処理は省略)

top/index.component.ts
import { Component } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import { YahooShoppingApiService } from '[YahooShoppingApiServiceのパス]';

@Component({
  selector: 'top-index',
  templateUrl: './index.component.html',
  styleUrls: ['./index.component.css'],
  providers: [YahooShoppingApiService]
})
export class IndexComponent {

  // 商品情報入力フォーム
  shoppingSearchForm: FormGroup;

  constructor(
    private fb: FormBuilder,
    private _yahooShoppingApiService: YahooShoppingApiService,
  ) {
    this.shoppingSearchForm = fb.group({
      // 商品名
      'keyword': ['', Validators.required]
    });
  }

  // "レビュー取得"ボタンを押した時の処理
  onSubmit(value: any): void {
    // 商品検索APIを使って、商品を検索する
    let itemList: object[] = []
    this._yahooShoppingApiService
      .itemSearch(value.keyword, 50)
      .subscribe(data => {
        itemList = data.ResultSet[0].Result;
      }, null, () => {
        // localStrageに検索結果を保存
        localStorage.setItem('yahooItemSearchResults', JSON.stringify(itemList));
      });
  }

}

3. 取得した商品それぞれのレビューを取得

【商品レビュー検索APIをたたく処理を記載したclass】

yahoo-shopping-api.service.ts
・・・

  /**
   * 商品検索API
   */
  itemSearch (query: string, results: number) {
    ・・・
  }

  /**
   * 商品レビュー検索API
   */
  async reviewSearch (janCodes: number[]) {
    let reviews: object[] = [];
    // janコードを利用して、各商品に対するレビューを取得する
    for (let jan of janCodes) {
      // パラメータを設定
      const searchParams = new URLSearchParams();
      searchParams.set('appid', appid);
      searchParams.set('jan', jan.toString());
      searchParams.set('callback', 'JSONP_CALLBACK');

      // APIをたたく
      const itemReviews = await new Promise(resolve => {
        let results: object[] = []
        this._jsonp
          .get('https://shopping.yahooapis.jp/ShoppingWebService/V1/json/reviewSearch', { search: searchParams })
          .map(res => res.json())
          .subscribe(data => {
            results = results.concat(data.ResultSet.Result);
          }, null, () => resolve(results));
      });
      reviews = reviews.concat(itemReviews);
    }

    return reviews;
  }

【上記の処理を使う側のclass】

reviews/index.component.ts
import { Component, OnInit } from '@angular/core';
import { YahooShoppingApiService } from '[YahooShoppingApiServiceのパス]';
import { Observable } from 'rxjs/Rx';

@Component({
  selector: 'reviews-index',
  templateUrl: './index.component.html',
  styleUrls: ['./index.component.css'],
  providers: [YahooShoppingApiService]
})
export class IndexComponent implements OnInit {

  // レビューの情報
  reviews: object[] = [];

  constructor(
    private _yahooShoppingApiService: YahooShoppingApiService
  ) {}

  async ngOnInit() {
    // Yahoo商品検索APIの結果を取り出す
    const yahooItemSearchResult: object[] = JSON.parse(localStorage.getItem('yahooItemSearchResults')) || [];

    // それぞれのJANコードを取得
    let janCodes: number[] = []
    for (let i in yahooItemSearchResult) {
      if (yahooItemSearchResult[i]['JanCode']) {
        janCodes.push(yahooItemSearchResult[i]['JanCode']);
      }
    }

    // Yahoo商品レビューAPIからレビュー情報を取得する
    this.reviews = await this._yahooShoppingApiService.reviewSearch(janCodes);
  }

}

苦労した点

  • Yahoo!ショッピングのAPIを叩こうとするとクロスドメインの問題(CORS?)でアクセスできない
    JSONPを利用することで解決
    (参考:JSONP Example with Observables, Angular、Bootstrapなどの学習備忘録)

  • Observableの扱い
    Observableからデータを取得する時に使用するsubscribeは以下のように使用する

Observable<any>.subscribe(data => {
    onNext,     // データを取得した時の処理
    onError,    // エラー処理
    onCompleted // データの取得が完了した時の処理
})

最後に

Yahoo!のWebAPIは商用利用が禁止なのでアフィリエイトと連携させての利用はできないっぽい!?
アフィリエイトもYahooの使う場合は宣伝にもなるんだしいいじゃないかー泣

参考