ひでぼ~blog

C#ときどきゲーム

BlazorのStream Renderingを使ってYoutubeから大量のコメントを取得・表示する

BlazorのStream Renderingを使ってYoutubeの動画から取得した大量のコメントを表示してみようと思います。

実行環境

View

後述のCommentServiceを使ってコメントを取得します。取得後はtableとバインドしているフィールドにコメントを追加しStateHasChange()を呼びます。StateHasChange()を呼ぶことで更新した分のHTMLがサーバからクライアントに送信されます。

@page "/comment-getter"
@using YoutubeCommentGetter.Core
@attribute [StreamRendering]
@inject CommentService CommentService

<table class="table">
@foreach (var comment in _comments)
{
    <tr class="table-light"><td>@comment</td></tr>
}
</table>

@code
{
    private readonly List<string> _comments = new();
    
    // 動画IDはクエリパラメータで指定する
    [SupplyParameterFromQuery]
    public string? VideoId { get; set; }

    protected override async Task OnInitializedAsync()
    {
        await foreach (var commentsPerPage in CommentService.GetAsync(VideoId))
        {
            // コメントを100件ずつ取得して更新
            _comments.InsertRange(0, commentsPerPage);
            StateHasChanged();

            // 画面の変化が分かりやすいように少し間を置く
            await Task.Delay(500);
        }
    }
}

Logic

APIの仕様上、コメントは最大で100件ずつしか取得できないようになっています。なので100件ずつコメントをyield returnして消費側でコメントを100件ずつ描画できるようにしました。

public class CommentService(IOptions<GoogleApiSettings> options)
{
    private readonly GoogleApiSettings _settings = options.Value;
    public async IAsyncEnumerable<IEnumerable<string>> GetAsync(string videoId)
    {
        var youtubeService = new YouTubeService(new BaseClientService.Initializer()
        {
            ApiKey = _settings.ApiKey,
            ApplicationName = _settings.ApplicationName
        });
        
        var commentThreadsRequest = youtubeService.CommentThreads.List("snippet");
        commentThreadsRequest.VideoId = videoId;
        commentThreadsRequest.TextFormat = CommentThreadsResource.ListRequest.TextFormatEnum.PlainText;
        commentThreadsRequest.Order = CommentThreadsResource.ListRequest.OrderEnum.Time;
        commentThreadsRequest.MaxResults = 100;
        var nextPageToken = string.Empty;

        while (nextPageToken is not null)
        {
            var result = new List<string>();
            commentThreadsRequest.PageToken = nextPageToken;

            var response = await commentThreadsRequest.ExecuteAsync();
            foreach (var commentThread in response.Items)
            {
                // コメントの取得
                var commentSnippet = commentThread.Snippet.TopLevelComment.Snippet;
                result.Add(commentSnippet.TextDisplay);

                // リプライの取得
                if (commentThread.Replies is null)
                    continue;

                result.AddRange(commentThread.Replies.Comments.Select(reply => reply.Snippet).Select(replySnippet => replySnippet.TextDisplay));
            }
            
            nextPageToken = response.NextPageToken;

            yield return result;
        }
    }
}

動かしてみる

こんな感じになりました。

HTTP/2のストリームを使って通信するので1リクエスト内でデータをやり取りしています。devtoolsを見るとやたら長い通信をしているのがあるのが分かります。

参考

learn.microsoft.com

developers.google.com