C# YouTubeの動画検索を自動化してみる(YouTube Data API v3) 

##はじめに##
YouTubeで動画検索の自動化ツールの案件を何時間でできるかというのを見て自分でもC#で実装してみようと思った次第です。（動画はRuby）

##YouTube のAPI##
早速「YouTube API」と調べて以下のURLからAPIのKeyを取得する。GoogleアカウントさえあればAPIkeyの発行ができるので楽勝でした。

https://developers.google.com/youtube/v3/getting-started?hl=ja

<b>Cloud Consoloe</b>に移動

（初めての場合）App Engineからアプリケーションの新規作成→リージョン選択→プログラミング言語[.NET]を選択　SDKのインストールなし

GoogleCloud　Platformのホーム画面からスタートガイド→「APIの探索」
「＋APIとサービスの有効化」から「Youtube Data API v3」を選択して有効化。有効化するとAPI Keyの画面が出てくるはずです。
![GoogleAPI有効化.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510936/66149d41-2734-a843-f78e-5b74709ab6f2.png)


あとはKeyを使って送るURLを完成させればOK。基本的には楽天APIと大差ないです。詳しく知りたい方は楽天API使ってみたの記事も読んでみてください。
今回もスクリプトの全文載せます。


```youtubeapi.cs

using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;

namespace RakutenAPI
{
    class Program
    {
        private static HttpClient client;
        static void Main(string[] args)
        {

            /*APIリソースの種類（一部）
             * channels :YouTube チャンネルに関する情報に基づいて YouTube がアルゴリズム的に割り当てるカテゴリ
             * search : API リクエストで指定した検索パラメータに一致する YouTube 動画、チャンネル、または再生リストに関する情報
             * videos :YouTube 動画
             */

            string APIkey = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"; //APIキー
            string Resource = "";
            string type = "";
            string seachword = "";
            string YoutubeName = "";
            string channelId = "";
            string videoId = "";

            int ChangeResorce = 1;

            switch (ChangeResorce) {
                case 1:
                    Resource = "search"; //statistics(統計情報)を出力に指定できない
                    type = "video"; //出力項目video ,channel,playlist
                    seachword = "ドラゴンボール";//キーワードで探すとき(必須)
                    break;
                case 2:
                    Resource = "channels";
                    YoutubeName = "YOASOBI"; //特定のチャンネル(YouTubeユーザー名)
                    channelId = "UCcPdLO7vOXqmnsOD3-Ctzow";//チャンネルIDで探すとき(任意)
                    break;
                case 3:
                    Resource = "videos";
                    videoId = "q15CRdE5Bv0";//動画IDで探すとき(任意)
                    break;
            }


            //YoutubeAPI　v3  操作https://www.googleapis.com/youtube/v3/リソース?クエリ
            string RequestURL = "https://www.googleapis.com/youtube/v3/" + Resource + "?";


            //以下は入力パラメータ　v3****************************************************
            //参照URL　 https://developers.google.com/youtube/v3/docs/videos/list
            //参照URL　https://developers.google.com/youtube/v3/docs/search/list
            //参照URL　https://developers.google.com/youtube/v3/docs/channels/list
            //入力値（右側）はすべてダブルクォーテーション"で囲む。
            //入力値（左側）は参照URLから「パラメーター」の項目をそのまま入力。


            var dic = new Dictionary<string, string>();

            if (Resource == "search")
            {
                dic.Add("q", ToUTF8(seachword));//検索キーワード
                dic.Add("part", "snippet,id");  // 出力情報の選択 id or snippet 
                dic.Add("type", type); //出力項目video ,channel,playlist
                 //dic.Add("order", "viewCount");//出力順序date,rating,title,videoCount,viewCount 
                 //dic.Add("videoDuration", "any");//動画時間：any - 指定なし,　long – 20 分を超える,　medium – 4 分以上 20 分以下,　short – 4 分未満
                //dic.Add("publishedAfter", "2010-01-01T00:00:00Z");//指定の日付より後にリリースされた動画
                //dic.Add("publishedBefore", "2019-01-01T00:00:00Z");//指定の日付より前ににリリースされた動画
            }

            if (Resource == "channels") {
                dic.Add("forUsername", YoutubeName);//フィルタ(フィルタはいづれか１つのみ指定)
                //dic.Add("id", channelId); //フィルタ(フィルタはいづれか１つのみ指定)

                dic.Add("part", "snippet,id,statistics");  // 出力情報の選択 id or snippet or statistics and so on.
            }

            if (Resource == "videos")
            {
                dic.Add("chart", "mostPopular"); //フィルタ： 指定したコンテンツ地域および動画のカテゴリに関して最も人気のある動画を返します。
                dic.Add("videoCategoryId", "17"); //動画のカテゴリ　https://so-zou.jp/web-app/tech/web-api/google/youtube/category.htm
                dic.Add("regionCode", "jp");　//コンテンツ地域選択
                dic.Add("part", "snippet,id,statistics");  // 出力情報の選択　id or snippet or statistics and so on.
                 //dic.Add("id", videoId); //フィルタ：動画IDの指定かchartフィルタのいづれか１つのみ指定
            }

            //共通
            dic.Add("key", APIkey); //APIキー
            dic.Add("maxResults", "2");//検索結果の出力最大数


            //入力パラメータここまで************************************************************************

            var mylist = new List<string>();
            foreach (var m in dic)
                mylist.Add(m.Key + "=" + m.Value);

            RequestURL += string.Join("&", mylist);
            Console.WriteLine(RequestURL);

            //クライアント接続開始
            try { client = new HttpClient(); }
            catch (Exception ex) { Console.WriteLine("【接続エラー】：" + ex.Message); }

            string JSONtext = "";
            try
            {
                //URLでHTMLを取得する。
                Task<string> task_get = GetRequest(RequestURL);
                JSONtext = task_get.Result;
            }
            catch (Exception ex)
            {
                Console.WriteLine("【応答エラー】：" + ex.Message);
            }


            //取得結果確認
            //Console.WriteLine(JSONtext);

            //JSON解析
            /*
             * 例　"result": { "Daiichi" : "OK" , "Second" : "NG"} は "result"がJObject、
             * 例　"result": [ { "First" : "OK" }, { "Second" : "NG" },{ "Third" : "NG" } ]  は"result"が JArray、
             * 　　["result"][0]が{ "First" : "OK" }、["result"][1]が{ "Second" : "NG" }でそれぞれJObject
             * 例　"result" : "OK"　 は "result"がJObjectでJValueが"OK"
             */

            //Resource = searchに対してのレスポンスを想定した結果の受け取り
            JObject jobject = JObject.Parse(JSONtext);
            JArray jArrItems = (JArray)jobject["items"];

            foreach (JObject jArrItem in jArrItems)
            {
                JObject jID = (JObject)jArrItem["id"];
                JValue jid = (JValue)jID["videoId"];
                JObject jSnippet = (JObject)jArrItem["snippet"];
                JValue jTitle = (JValue)jSnippet["title"];
                Console.WriteLine("タイトル  :  " + jTitle.ToString());
                Console.WriteLine("動画ID :  " + jid.ToString());
            }




            Console.WriteLine("press any key");
            Console.ReadKey();

        }


        //keywordだけUTF８のバイト変換が必要
        public static string ToUTF8(string Keyword)
        {
            //String型を16進数バイト型文字列に変換　”楽天”→”%E6%A5%BD%E5%A4%A9”
            byte[] bytedata = Encoding.UTF8.GetBytes(Keyword);
            Keyword = "%" + BitConverter.ToString(bytedata).Replace("-", "%");
            return Keyword;
        }


        //URLからHTMLをゲット
        async static Task<string> GetRequest(string url)
        {
            HttpResponseMessage response = await client.GetAsync(url);
            string contentstr = response.StatusCode.ToString();
            Console.WriteLine("【GetRequest Response】" + contentstr);

            if (response.StatusCode == System.Net.HttpStatusCode.OK)
            {
                contentstr = await response.Content.ReadAsStringAsync();
            }
            else if (response.StatusCode == HttpStatusCode.Redirect)
            {
                //StatusCode リダイレクトのときの処理
                var r = client.GetAsync(url).Result;
                Uri uri = new Uri(new Uri(url), r.Headers.Location);
                contentstr = client.GetAsync(uri).Result.Content.ReadAsStringAsync().Result;
            }

            return contentstr;
        }
    }// End Class Program

}//End namespace


```

今回少しややこしいと感じたのはAPIリソースを３種類設定できるようにswitchで簡単に切り替えれるようにしました。なぜこんな面倒なことをしているのかというとリソースの種類に応じて引数と戻り値の設定に縛りがあるためです。

（１）”serch”リソースの場合
【HTTPリクエスト】https://www.googleapis.com/youtube/v3/search?クエリ
クエリで非常に細かい検索条件を設定できます。「検索」に特化したリソースと言えます。動画IDやチャンネルID、タイトルなどの情報を返します。しかし、チャンネルの詳細、動画再生回数、いいね回数などの統計情報は返せません。

（２）”channels”リソースの場合
【HTTPリクエスト】https://www.googleapis.com/youtube/v3/channels?クエリ
クエリで指定できるのはチャンネルIDのみ、チャンネルのユーザー名、コメント、登録者数、動画総再整数といった詳細情報を返します。

（３）”videos”リソースの場合
【HTTPリクエスト】https://www.googleapis.com/youtube/v3/videos?クエリ
クエリで指定できるのは動画IDや簡単な絞り込み程度、動画の再生数、いいね数、コメント数などの統計情報も返せます。


次に確認用の出力結果（生）です。
![出力結果YoutubeAPI.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510936/1208cfb8-9a88-ec49-8bf5-fa9374238c5d.png)

今回はJSONテキストが返ってきますのでC#でJSONテキストを基本アセンブリだけで扱うのは苦労するのでNewtonsoft.JsonをNuGetに追加しました。途中参考用に説明用のコメント入れていますので参考にしてください。maxResultsを５にして最終的に抜き出した動画のタイトルと動画IDを取得すると以下のようになります。
![出力結果YoutubeAPI_2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510936/1dbf4d25-f743-21a2-f65f-c41e2a9091ef.png)

あとは各動画の動画IDやチャンネルIDに対してvideoリソースやchannelリソースで再度検索すれば詳細なデータが手に入る寸法です。なぜに一度に全部データを返さないのか疑問ですがサーバーが違うのか負荷の低減なのかはわかりません。

全体としてAPIでデータを手に入れるところまでは２時間くらいでできたのですがJSONの扱いのところで４時間くらい掛かりました。ご要望があれば案件のようにしっかりCSVまで出力したかったのですが本日はこのへんで失礼します。


##参考##
検索（Search）の概要
https://developers.google.com/youtube/v3/docs/search

C#で不定形JSONを自在に扱いたい
https://qiita.com/rana_kualu/items/b4fab77d4c5c4f6fd3da

NEWTONSOFT.JSON (JSON.NET) の基本的な使い方
https://netweblog.wordpress.com/2016/10/24/json-net-newtonsoft-json-usage/