【问题标题】:How to use the YouTube API to extract video titles and next page token?如何使用 YouTube API 提取视频标题和下一页令牌?
【发布时间】:2021-02-09 00:03:51
【问题描述】:

我正在使用 YouTube API 来检索播放列表中的所有标题。

我设法从 API 中检索 JSON 文本并找到正确的元素 this post

Dim wc As New Net.WebClient
Dim incstr As IO.Stream = Nothing
wc.Encoding = Encoding.UTF8
incstr = wc.OpenRead("https://www.googleapis.com/youtube/v3/playlistItems?part=contentDetails%2Csnippet&maxResults=50&playlistId=PL4_Dx88dpu7cEY_cBjTZFFM1tVKF5Plsx&key=yourkey")

Using rd As New IO.StreamReader(incstr)
    RichTextBox1.Text = rd.ReadToEnd
End Using
Dim strBuf As String = (richtextbox1.text)

Dim myjobj As New Json.Linq.JObject
myjobj = Json.Linq.JObject.Parse(strBuf)

dim titleText = JObject.Parse(json)("title")("runs")(0)("text")

但是,不幸的是,这段代码在最后一行生成了一个错误,例如:System.ArgumentException: 'Can not convert Object to String.'

此外,API 只从播放列表中返回最多 50 个元素,正如@Jimi 建议的那样:

如果 playList 中有超过 50 个元素,那么您会在主类对象中返回一个 nextPageToken 条目。通过设置pageToken=[The Provided Token String] 查询元组,您可以使用此令牌查询下一页。

我确实在 JSON 文本的开头看到:

"nextPageToken": "CGQQAA",
"prevPageToken": "CDIQAQ",

但不知道如何抓住它们。

那么,由于当时的限制是 50 首歌曲,我如何使用下一页令牌一次检索所有歌曲?

【问题讨论】:

  • 你好!您在附加的代码中泄露了应用的 API 密钥。请考虑尽快撤销它,因为如果您没有保护 API 密钥,API 密钥可能会被滥用于恶意目的。

标签: json vb.net winforms json.net youtube-api


【解决方案1】:

当请求播放列表时,Google API 使用一种分页形式(这也适用于其他类型的 API 响应)。
每个响应页面的最大条目数为 50。

▶ 响应格式是一个 JSON 对象,其中包含查询响应的描述,包括请求的项目总数和当前响应中包含的项目。

▶ 如果查询包含的项目多于每页的最大值,则 nextPageToken 属性设置为代表 Page Token 的字符串值,可用于查询下一页结果(您可以在 YouTubePlayList 类的 LoadPlaylistAsync() 方法中看到它是如何在代码中工作的)。
当没有其他页面可用时,nextPageToken 设置为 null (Nothing)。
通过查看这个Token值,我们可以判断Playlist内容是否完整。

可以使用带有访问密钥的 OAuth 2.0 调用 API,这需要 Google API 客户端库,或者使用简单的 HTTP GET 和由FormUrlEncodedContent 组成的 URI,标准 URL 查询元组。

在这里,我使用的是后一种方法,因为它非常简化并且效果很好。
如果您有 Google SDK,可以按照PlaylistItems list documentation page 中的示例进行操作。

我正在使用类结构(模型)来反序列化 JSON 响应:使用标准 .Net 类处理内容非常简单,其中 JSON 属性值已经转换为正确格式的强类型值。
辅助类 YouTubePlayList 包含类模型和反序列化 JSON 或将类对象序列化回 JSON 字符串所需的方法。

如果需要,它还使用静态 HttpClient 执行 HTTP GET 请求并处理 API 响应的分页。
该类使用 HttpClient 类提供的异步方法。

▶ 类通过查询 API 所需的访问密钥进行初始化。
如果密钥尚不可用,请按照说明在API Reference documentation page 中创建一个(免费),这会将您转到开发者控制台的 API 访问窗格。

要加载播放列表,请将播放列表令牌传递给 LoadPlaylistAsync() 方法。
此方法返回一个 PlayList 对象,其中包含与 API 查询本身相关的所有信息以及所有生成的项目,在一个 List(Of PlayListEntry) 对象中。
所有反序列化和 HTTP 调用任务都在内部处理。


例如,在Button.Click处理程序中,初始化传递Key的帮助类,调用LoadPlaylistAsync()方法指定要下载的播放列表的Token,然后处置YouTubePlayList类对象并使用返回的任何需要的类对象:

我正在使用您在上一个问题中提供的播放列表令牌

Private Async Sub btnLoadPlaylist_Click(sender As Object, e As EventArgs) Handles btnLoadPlaylist.Click
    ' Initialize with the Google API Key
    Dim playListHelper As New YouTubePlayList("AIzaSy---------------------------------")
    ' Load the Playlist passing the playListId
    Dim playList = Await playListHelper.LoadPlaylistAsync("PL4_Dx88dpu7epfH6ybwqJpf9uL2tAl368")
    ' Dispose of the class object, to close and dispose of the HttpClient object
    playListHelper.Dispose()

    For Each item As YouTubePlayList.PlayListEntry In playList.Items
        Dim itemTitle = item.ItemContent.Title
        Dim itemChannel = item.ItemContent.ChannelTitle
        Dim videoAddress = "https://www.youtube.com/watch?v=" & item.ItemContent.ResourceId.VideoId
    Next
End Sub

类对象的示例功能:

Google PlayList 下载器帮助类

需要Json.Net (Newtonsoft.Json) 12.0.3+

Imports System
Imports System.Collections.Generic
Imports System.Globalization
Imports System.Net.Http
Imports System.Threading.Tasks
Imports Newtonsoft.Json
Imports Newtonsoft.Json.Converters

Public Class YouTubePlayList
    Implements IDisposable

    Private m_Json As String = String.Empty
    Private m_KeyToken As String = String.Empty
    Private m_APIUrl As String = "https://www.googleapis.com/youtube/v3/playlistItems?"
    Private Shared client As HttpClient = Nothing

    Public Sub New(key As String)
        Me.New(key, String.Empty)
    End Sub

    Public Sub New(key As String, json As String)
        m_KeyToken = key
        If Not String.IsNullOrEmpty(json) Then
            m_Json = json
        End If
        client = New HttpClient()
    End Sub

    Public Function Deserialize() As PlayList
        Return Deserialize(m_Json)
    End Function

    Public Function Deserialize(json As String) As PlayList
        If String.IsNullOrEmpty(json) Then Return Nothing
        Return JsonConvert.DeserializeObject(Of PlayList)(json, ConverterSettings.Settings)
    End Function

    Public Function Serialize(root As PlayList) As String
        Return JsonConvert.SerializeObject(root, ConverterSettings.Settings)
    End Function

    Friend NotInheritable Class ConverterSettings
        Public Shared ReadOnly Settings As New JsonSerializerSettings() With {
            .MetadataPropertyHandling = MetadataPropertyHandling.Ignore,
            .DateParseHandling = DateParseHandling.None,
            .Converters = {New IsoDateTimeConverter() With {
                .DateTimeStyles = DateTimeStyles.AssumeUniversal
            }}
        }
    End Class

    Public Async Function LoadPlaylistAsync(playListToken As String) As Task(Of PlayList)
        Dim resultPlayList As PlayList = Nothing
        Dim nextPageToken As String = String.Empty
        Dim listItemsRead As Integer = 0
        Dim resultsPerPage As Integer = 50

        While nextPageToken IsNot Nothing
            Dim queryUrl = Await GetRequestUrl(playListToken, nextPageToken, resultsPerPage)
            Using response = Await client.GetAsync(queryUrl)
                If response.IsSuccessStatusCode Then
                    Dim jsonResponse = Await response.Content.ReadAsStringAsync()
                    Dim playList = Deserialize(jsonResponse)

                    listItemsRead += playList.Items.Count

                    If resultPlayList Is Nothing Then
                        resultPlayList = playList
                    Else
                        resultPlayList.Items.AddRange(playList.Items)
                    End If
                    nextPageToken = playList.NextPageToken
                    resultPlayList.PageInfo.ResultsPerPage = listItemsRead
                End If
            End Using
        End While
        Return resultPlayList
    End Function

    Private Async Function GetRequestUrl(playListToken As String, nextPageToken As String, resultsPerPage As Integer) As Task(Of String)
        Dim content = New FormUrlEncodedContent(New KeyValuePair(Of String, String)() {
            New KeyValuePair(Of String, String)("part", "contentDetails,snippet,id"),
            New KeyValuePair(Of String, String)("maxResults", resultsPerPage.ToString()),
            New KeyValuePair(Of String, String)("pageToken", nextPageToken),
            New KeyValuePair(Of String, String)("playlistId", playListToken),
            New KeyValuePair(Of String, String)("key", m_KeyToken)
        })
        Return m_APIUrl & Await content.ReadAsStringAsync()
    End Function


    Public Class PlayList
        <JsonProperty("kind")>
        Public Property Kind As String
        <JsonProperty("etag")>
        Public Property Etag As String
        <JsonProperty("nextPageToken")>
        Public Property NextPageToken As String
        <JsonProperty("prevPageToken")>
        Public Property PrevPageToken As String
        <JsonProperty("items")>
        Public Property Items As List(Of PlayListEntry)
        <JsonProperty("pageInfo")>
        Public Property PageInfo As PageInfo
    End Class

    Public Class PlayListEntry
        <JsonProperty("kind")>
        Public Property Kind As String
        <JsonProperty("etag")>
        Public Property Etag As String
        <JsonProperty("id")>
        Public Property Id As String
        <JsonProperty("snippet")>
        Public Property ItemContent As Content
        <JsonProperty("contentDetails")>
        Public Property ContentDetails As ContentDetails
    End Class

    Public Class ContentDetails
        <JsonProperty("videoId")>
        Public Property VideoId As String
        <JsonProperty("videoPublishedAt")>
        Public Property VideoPublishedAt As DateTimeOffset
    End Class

    Public Class Content
        <JsonProperty("publishedAt")>
        Public Property PublishedAt As DateTimeOffset
        <JsonProperty("channelId")>
        Public Property ChannelId As String
        <JsonProperty("title")>
        Public Property Title As String
        <JsonProperty("description")>
        Public Property Description As String
        <JsonProperty("thumbnails")>
        Public Property Thumbnails As Thumbnails
        <JsonProperty("channelTitle")>
        Public Property ChannelTitle As String
        <JsonProperty("playlistId")>
        Public Property PlaylistId As String
        <JsonProperty("position")>
        Public Property Position As Long
        <JsonProperty("resourceId")>
        Public Property ResourceId As ResourceId
    End Class

    Public Class ResourceId
        <JsonProperty("kind")>
        Public Property Kind As String
        <JsonProperty("videoId")>
        Public Property VideoId As String
    End Class

    Public Class Thumbnails
        <JsonProperty("default")>
        Public Property DefaultImage As ImageDescriptor
        <JsonProperty("medium")>
        Public Property Medium As ImageDescriptor
        <JsonProperty("high")>
        Public Property High As ImageDescriptor
        <JsonProperty("standard", NullValueHandling:=NullValueHandling.Ignore)>
        Public Property Standard As ImageDescriptor
        <JsonProperty("maxres", NullValueHandling:=NullValueHandling.Ignore)>
        Public Property MaxResolution As ImageDescriptor
    End Class

    Public Class ImageDescriptor
        <JsonProperty("url")>
        Public Property Url As Uri
        <JsonProperty("width")>
        Public Property Width As Integer
        <JsonProperty("height")>
        Public Property Height As Integer
    End Class

    Public Class PageInfo
        <JsonProperty("totalResults")>
        Public Property TotalResults As Integer
        <JsonProperty("resultsPerPage")>
        Public Property ResultsPerPage As Integer
    End Class

    Public Sub Dispose() Implements IDisposable.Dispose
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub
    Protected Sub Dispose(disposing As Boolean)
        If disposing Then
            client?.CancelPendingRequests()
            client?.Dispose()
            client = Nothing
        End If
    End Sub
End Class

C# 版本

using System.Collections.Generic;
using System.Globalization;
using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

public class YouTubePlayList : IDisposable
{
    private string m_Json = string.Empty;
    private string m_KeyToken = string.Empty;
    private string m_APIUrl = "https://www.googleapis.com/youtube/v3/playlistItems?";
    private static HttpClient client = null;

    public YouTubePlayList(string key) : this(key, string.Empty) { }
    public YouTubePlayList(string key, string json)
    {
        m_KeyToken = key;
        if (!string.IsNullOrEmpty(json)) {
            m_Json = json;
        }
        client = new HttpClient();
    }

    public PlayList Deserialize() => Deserialize(m_Json);

    public PlayList Deserialize(string json)
    {
        if (string.IsNullOrEmpty(json)) return null;
        return JsonConvert.DeserializeObject<PlayList>(json, ConverterSettings.Settings);
    }

    public string Serialize(PlayList root) =>
        JsonConvert.SerializeObject(root, ConverterSettings.Settings);

    internal sealed class ConverterSettings
    {
        public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings {
            MetadataPropertyHandling = MetadataPropertyHandling.Ignore,
            DateParseHandling = DateParseHandling.None,
            Converters = {
                new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal }
            }
        };
    }

    public async Task<PlayList> LoadPlaylistAsync(string playListToken)
    {
        PlayList resultPlayList = null;
        string nextPageToken = string.Empty;
        int listItemsRead = 0;
        int resultsPerPage = 50;

        while (nextPageToken != null) {
            string queryUrl = await GetRequestUrl(playListToken, nextPageToken, resultsPerPage);
            using (var response = await client.GetAsync(queryUrl)) {
                if (response.IsSuccessStatusCode) {
                    string jsonResponse = await response.Content.ReadAsStringAsync();
                    var playList = Deserialize(jsonResponse);
                    listItemsRead += playList.Items.Count;
                    if (resultPlayList == null) {
                        resultPlayList = playList;
                    }
                    else {
                        resultPlayList.Items.AddRange(playList.Items);
                    }
                    nextPageToken = playList.NextPageToken;
                    resultPlayList.PageInfo.ResultsPerPage = listItemsRead;
                }
            }
        }
        return resultPlayList;
    }
    private async Task<string> GetRequestUrl(string playListToken, string nextPageToken, int resultsPerPage)
    {
        var content = new FormUrlEncodedContent(new KeyValuePair<string, string>[] {
            new KeyValuePair<string, string>("part", "contentDetails,snippet,id"),
            new KeyValuePair<string, string>("maxResults", resultsPerPage.ToString()),
            new KeyValuePair<string, string>("pageToken", nextPageToken),
            new KeyValuePair<string, string>("playlistId", playListToken),
            new KeyValuePair<string, string>("key", m_KeyToken)
        });
        return m_APIUrl + await content.ReadAsStringAsync();
    }

    public class PlayList
    {
        public string Kind { get; set; }
        public string Etag { get; set; }
        public string NextPageToken { get; set; }
        public string PrevPageToken { get; set; }
        public List<PlayListEntry> Items { get; set; }
        public PageInfo PageInfo { get; set; }
    }

    public class PlayListEntry
    {
        public string Kind { get; set; }
        public string Etag { get; set; }
        public string Id { get; set; }
        [JsonProperty("snippet")]
        public Content ItemContent { get; set; }
        public ContentDetails ContentDetails { get; set; }
    }
    public class ContentDetails
    {
        public string VideoId { get; set; }
        public DateTimeOffset VideoPublishedAt { get; set; }
    }

    public class Content
    {
        public DateTimeOffset PublishedAt { get; set; }
        public string ChannelId { get; set; }
        public string Title { get; set; }
        public string Description { get; set; }
        public Thumbnails Thumbnails { get; set; }
        public string ChannelTitle { get; set; }
        public string PlaylistId { get; set; }
        public long Position { get; set; }
        public ResourceId ResourceId { get; set; }
    }

    public class ResourceId
    {
        public string Kind { get; set; }
        public string VideoId { get; set; }
    }

    public class Thumbnails
    {
        [JsonProperty("default")]
        public ImageDescriptor DefaultImage { get; set; }
        public ImageDescriptor Medium { get; set; }
        public ImageDescriptor High { get; set; }
        [JsonProperty("standard", NullValueHandling = NullValueHandling.Ignore)]
        public ImageDescriptor Standard { get; set; }
        [JsonProperty("maxres", NullValueHandling = NullValueHandling.Ignore)]
        public ImageDescriptor MaxResolution { get; set; }
    }

    public class ImageDescriptor
    {
        public Uri Url { get; set; }
        public int Width { get; set; }
        public int Height { get; set; }
    }

    public class PageInfo
    {
        public int TotalResults { get; set; }
        public int ResultsPerPage { get; set; }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected void Dispose(bool disposing)
    {
        if (disposing) {
            client?.CancelPendingRequests();
            client?.Dispose();
            client = null;
        }
    }
}

【讨论】:

  • 哇,太棒了。非常感谢您的时间!!例如,是否可以将所有元素存储在 my.settings 中?
  • 您可以使用应用程序的设置,但由于设置只是磁盘上的文件,您也可以自己将 json 响应写入文件(UTF-8 编码)。您在此处看到的带有 original 播放列表的 json 响应是 11375 字节(只有 12 个项目)。无论如何,在代码中,Dim jsonResponse = Await response.Content.ReadAsStringAsync()jsonResponse 行中的 json 字符串是您可以以任何您喜欢的方式写入磁盘的 json 字符串。
猜你喜欢
  • 1970-01-01
  • 2017-10-14
  • 2011-01-29
  • 2017-12-10
  • 1970-01-01
  • 1970-01-01
  • 2012-10-28
  • 1970-01-01
  • 2015-10-23
相关资源
最近更新 更多