YouTube Data API (v3) JAVA search メソッド で paging された結果を取得する。


YouTube Data API (v3) JAVA で paging された検索結果の取得方法がわからず、
前準備と、実際のページング処理について
小一時間まさぐった結果をメモします。


参考サイト


前準備

1. APIの有効かと、キーの発行

google developer console
でYouTube Data APIを有効化し、認証情報を作成します。
search API のみで、登録/更新は行わないので、「APIキー」を発行します。
APIキーのみの発行だと、セキュリティの警告が出るので、
[Referer] の値による検証も設定しました。

2. maven pom.xml の記述

Maven Repository: com.google.apis » google-api-services-youtube
で最新版のpom.xml 記述を取得します。
google alaytics API も maven 登録されているので、
google API の java liblary は 大概登録されているようです。

<!-- https://mvnrepository.com/artifact/com.google.apis/google-api-services-youtube -->

    com.google.apis
    google-api-services-youtube
    v3-rev178-1.22.0

サンプル実装

search を実行して、ページングされた結果を収集して取得するプログラムです。
プログラムの下部に特記事項を記載します。

package xyz.monotalk.batch.utils;

import com.google.api.client.http.HttpHeaders;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpRequestInitializer;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.services.youtube.YouTube;
import com.google.api.services.youtube.model.SearchListResponse;
import com.google.api.services.youtube.model.SearchResult;
import xyz.monotalk.batch.apps.BatchRuntimeException;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * YoutubeUtils
 *
 * @author Kem
 */
public class YoutubeUtils {

    private static final String API_KEY = "xxx";

    /**
     * Global instance of the HTTP transport.
     */
    private static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport();

    /**
     * Global instance of the JSON factory.
     */
    private static final JsonFactory JSON_FACTORY = new JacksonFactory();

    /**
     * Global instance of the max number of videos we want returned (50 = upper limit per page).
     */
    private static final long NUMBER_OF_VIDEOS_RETURNED = 25;

    /**
     * Global instance of Youtube object to make all API requests.
     */
    private static YouTube youtube;

    /**
     * searchVideo
     *
     * @param query
     * @return
     */
    public static List searchVideo(String query) {
        youtube = new YouTube.Builder(HTTP_TRANSPORT, JSON_FACTORY, new HttpRequestInitializer() {
            public void initialize(HttpRequest request) throws IOException {
            }
        }).setApplicationName("yourAppName").build();

        List searchResultList = new ArrayList&lt;&gt;();
        String queryTerm = query;
        YouTube.Search.List search = null;
        try {
            search = youtube.search().list("id,snippet");
        } catch (IOException e) {
            throw new BatchRuntimeException(e);
        }
        search.setKey(API_KEY);
        search.setQ(queryTerm);
        search.setType("video");
        // 1.検索結果フィールドに"nextPageToken"を指定
        search.setFields("items(*),nextPageToken");
        HttpHeaders headers = new HttpHeaders();
        // 2."Referer" に 稼働させる予定のドメイン名を指定
        headers.set("Referer", "www.yourdomain.com/example");
        headers.setContentType("application/json");
        search.setRequestHeaders(headers);
        search.setMaxResults(NUMBER_OF_VIDEOS_RETURNED);

        SearchListResponse searchResponse = null;

        do {
            if (searchResponse != null &amp;&amp; searchResponse.getNextPageToken() != null) {
                search.setPageToken(searchResponse.getNextPageToken());
            }
            try {
                searchResponse = search.execute();
            } catch (IOException e) {
                throw new BatchRuntimeException(e);
            }
            println("START---------------------");
            try {
                println(searchResponse.toPrettyString());
                println(searchResponse.getNextPageToken());
            } catch (IOException e) {
                e.printStackTrace();
            }
            println("END-----------------------");
            searchResultList.addAll(searchResponse.getItems());
        // 検索結果の有無しは、searchResponse.getItems().isEmpty() で判定する
        // この実装だと、loopが1回余分に回るがあまり気にしないでおく。
        } while (!searchResponse.getItems().isEmpty());

        return searchResultList;
    }

    private static void println(String msg) {
        System.out.println(msg);
    }
}

特記事項1

検索結果フィールドに”nextPageToken”を指定すると、
次ページ検索用のPageTokenが戻りのJSONに含まれるようになります。
PageTokenはSearchListResponse#getNextPageToken() で取得可能です。

特記事項2

APIキー発行時に、[Referer] の値による検証を設定しているので、
headers.set("Referer", "www.yourdomain.com/example");
で”Referer”値に稼働予定のドメイン名を指定しています。
※偽装ができるということになりますが..

特記事項3

ページングの検索結果の終了判定には、
searchResponse.getItems().isEmpty()を使用しています。
結果が空なのに、PageTokenだけ返ってきたりしているので、
検索結果のリストが空かどうかで判定を実装しました。


補足

1. nextPageToken が Null か どうかで do-while-loopするとどうなるか?

最終的に以下のOUTPUTになり、止まりませんでした。

    START---------------------
    {
      "items" : [ ],
      "nextPageToken" : "CLYHEAA"
    }
    CLYHEAA
    END-----------------------
    START---------------------
    {
      "items" : [ ],
      "nextPageToken" : "CM8HEAA"
    }
    CM8HEAA
    END-----------------------

    START---------------------
    {
      "items" : [ ]
    }
    null
    END-----------------------

2. while (!searchResponse.isEmpty()) でdo-while-loopするとどうなるか?

最終的に以下のOUTPUTになり、止まりませんでした。
こちらは"nextPageToken"がNullでも止まらないです。。

    START---------------------
    {
      "items" : [ ],
      "nextPageToken" : "CLYHEAA"
    }
    CLYHEAA
    END-----------------------
    START---------------------
    {
      "items" : [ ],
      "nextPageToken" : "CM8HEAA"
    }
    CM8HEAA
    END-----------------------

    START---------------------
    {
      "items" : [ ]
    }
    null
    END-----------------------

3. Referer の記載を誤った場合のエラー

headers.set("Referer","www.yourdomain.com/example");
の値を設定した値以外にした場合、以下のエラーが出力されます。
※403エラーとなります。

xyz.monotalk.batch.apps.BatchRuntimeException: com.google.api.client.googleapis.json.GoogleJsonResponseException: 403 Forbidden
{
  "code" : 403,
  "errors" : [ {
    "domain" : "usageLimits",
    "message" : "The referrer www.xxxxxxxxxx.xyz/example does not match the referrer restrictions configured on your API key. Please use the API Console to update your key restrictions.",
    "reason" : "ipRefererBlocked",
    "extendedHelp" : "https://console.developers.google.com/apis/credentials?project=xxxxxxxxxxxxxx"
  } ],
  "message" : "The referrer www.xxxxxxxxxx.xyz/example does not match the referrer restrictions configured on your API key. Please use the API Console to update your key restrictions."
}

    at xyz.monotalk.batch.utils.YoutubeUtils.searchVideo(YoutubeUtils.java:79)
    at xyz.monotalk.batch.utils.YoutubeUtilsTest.testSearchVideo(YoutubeUtilsTest.java:11)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:117)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:42)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:262)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:84)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)

Echonest APIだと、Artist検索にまるっと、動画が含まれていたりして、
便利だった?のですが、LastFm API だとそうはいかず、自ら取りに行かざるえないですが、
勉強になりました。

以上です。

コメント