使用Youtube官方API获取频道及视频数据

2020-06-04更新

下面附上笔者提供的源码(已经验证过功能。后续会在同一个工程中更新Facebook和Insgram的相关爬虫代码)。
https://github.com/zhangjz777/yfi_source
下面是工程关于Youtube相关的代码注意事项介绍。

  • 如遇到此报错“java.lang.NoSuchMethodError: javax.servlet.ServletContext.getVirtualServerName()Ljava/lang/String”,是由于pom引入的jar包中包含的低版本servlet-api 导致的,请自行排除多余的依赖。详情可以参考这篇文章:SpringBoot报错:java.lang.NoSuchMethodError: javax.servlet.ServletContext.getVirtualServerName()Ljava/lang/String;
  • 请确保你的IDE中,有lombok插件支持。
  • 请确保你已正确申请youtube api key,代码中的key是笔者的,现已弃用。
  • 因笔者水平有限,若有什么代码问题,请文明交流,留在你的评论或者私信。

一、前期调研

开始决定做Youtube的时候先是查阅了百度和Google上的一些搜索结果。当我以youtube爬虫作为搜索关键词时,结果并不尽人意:在这其中部分爬虫是以下载视频为目的,没有获取视频其他信息,因此并不是我所需要的。还有部分商业网站提供付费接口服务,但是秉着能省则省的原则,这种肯定是不会考虑的,因此只能另辟奇径。当我切换以“youtube api”为关键词搜索时,发现了下面这个。

​ 没错,Google官方提供了关于Youtube API给开发者使用。也给予此,开发者也没有必要自己花费大力气去破解youtube的api。因此,本篇文章本质上是一篇讲解youtube 官方API使用教程的文章,若已有相关开发经验的读者可以阅读另外两篇,如果你尚未了结此api库的使用方法那这篇文章或许可以给你一些参考,同时我也会附上一些我在实际开发过程中遇到的一些问题和相关的注意事项供你参考。

二、Youtube API的使用

本文中的使用方式是在Java工程中,其他语言的使用教程请参考官方文档。

1、如何才能使用YouTube API?

(1)登录Google云平台

首先你需要登录Google的云平台(请自备Google邮箱账号)

https://console.developers.google.com/apis/api/youtube.googleapis.com

(2)开通Youtube API V3服务

登录成功后在图示中找到Youtube API v3开通

按照如下顺序创建API秘钥用于发起请求时的权限验证

(3)阅读API开发文档

将API复制到别处保存后,就可以前往API使用文档页面了。

https://developers.google.com/youtube/v3/docs

这里包含了常用的业务模型,在笔者的实际开发过程中使用了Channel、Playlist、Videos、Search进行操作。

2、API的使用方式

(1)在Java项目中引入jar包依赖

下面使用到的版本号可根据官方文档提供的填入

        <!-- YouTube Data V3 support -->
        <dependency>
            <groupId>com.google.apis</groupId>
            <artifactId>google-api-services-youtube</artifactId>
            <version>v3-rev182-1.22.0</version>
        </dependency>

        <!-- Required for any code that makes calls to the YouTube Analytics API -->
        <dependency>
            <groupId>com.google.apis</groupId>
            <artifactId>google-api-services-youtubeAnalytics</artifactId>
            <version>v3-rev182-1.22.0</version>
        </dependency>

        <!-- Required for any code that makes calls to the YouTube Reporting API -->
        <dependency>
            <groupId>com.google.apis</groupId>
            <artifactId>google-api-services-youtubereporting</artifactId>
            <version>v1-rev10-1.22.0</version>
        </dependency>

另外请确保你有可以用的本地网络代理可以访问外网,具体使用方式

其中host表示,你设置的代理ip,port表示代理使用的端口。

				System.setProperty("http.proxyHost", host);
                System.setProperty("http.proxyPort", port);
                System.setProperty("https.proxyHost", host);
                System.setProperty("https.proxyPort", port);

(2)代码示例

查询指定channel id的频道下的所有视频,根据视频发布时间排序

参考文档

https://developers.google.com/youtube/v3/docs/search

首先你需要的channel id 如何获取呢?根据我的观察,应该是有两种情况的。

频道首页url自带channel id

图中链接为:https://www.youtube.com/channel/UCXuqSBlHAE6Xw-yeJA0Tunw

即channel 后边的 “UCXuqSBlHAE6Xw-yeJA0Tunw” 即为当前频道的channel id,可以在api 中指定该参数。

频道首页url不包含channel id

图中链接为:https://www.youtube.com/user/EVOTV

然而,官方提供的api库里,并没有根据user name 来搜索视频的api。这时,该怎么处理呢?

其实很简单,在当前页面下打开调试窗口(F12),在Elements这一栏中搜索channel,找到与当前频道名字相同的链接,这就是该用户“隐藏”的channel id。

代码请求参考如下

YouTube youtube = new YouTube.Builder(Auth.HTTP_TRANSPORT, Auth.JSON_FACTORY, request -> {
            }).setApplicationName("youtube-cmdline-search-sample").build();
            YouTube.Search.List search = youtube.search().list("id,snippet");
            String apiKey = "你在Google云平台申请到的api key";
            search.setKey(apiKey);
						// 接口返回数据模型	
            search.setType("video");
						// 设置需要接口返回的字段
search.setFields("items(id/kind,id/videoId,snippet/title,snippet/thumbnails/default/url),nextPageToken,pageInfo,prevPageToken");
						// 返回的最大记录条数
            search.setMaxResults(50);
						// 设置要查询的channel id
            search.setChannelId(channelId);
            search.setOrder("date");
            SearchListResponse searchResponse;
            while (true) {
                try {
                    searchResponse = search.execute();
                    break;
                } catch (GoogleJsonResponseException e) {
                    if (403 == e.getDetails().getCode()) {
                        // 配额用尽,使用下一个
                        LogUtil.error("youtube api 配额用尽,尝试替换api key");
                      	seatch.setKey("替换新的api key");
                        return;
                    } else {
                        LogUtil.error("其它异常,结束任务");
                        throw e;
                    }
                }
            }
            List<SearchResult> searchResultList = searchResponse.getItems();
            List<SearchResult> allRecord = new ArrayList<>();
            if (searchResultList != null) {
                PageInfo pageInfo = searchResponse.getPageInfo();
								// 根据分页获取全部数据
                    allRecord.addAll(searchResultList);
                    while (true) {
                      	// 设置分页的参数
                        search.setPageToken(searchResponse.getNextPageToken());
                        searchResponse = search.execute();
                        if (searchResponse == null ||
                                AppUtil.isNull(searchResponse.getItems())) {
                            break;
                        }
                        List<SearchResult> items = searchResponse.getItems();
                        if (AppUtil.isNull(items)) {
                            break;
                        }
                        allRecord.addAll(items);
                        if (items.size() < 50) {
                            break;
                        }
            }
            if (AppUtil.isNull(allRecord)) {
                return;
            }
            // 获取所有的video id
            List<String> videoIds = allRecord.stream()
                    .map(SearchResult::getId)
                    .map(ResourceId::getVideoId)
                    .collect(Collectors.toList());
            videoIds.forEach(System.out::println);
            }

查询指定video的详细信息(使用参数video id)

参考文档

https://developers.google.com/youtube/v3/docs/videos

video id的来源

在视频详情页中:https://www.youtube.com/watch?v=P_7piye1Who

v后边的参数“P_7piye1Who”即为当前视频的video id。

获取使用前一个api 获取频道下的视频,返回的也是video id。

YouTube youtubeVideo = new YouTube.Builder(Auth.HTTP_TRANSPORT, Auth.JSON_FACTORY, request -> {
            }).setApplicationName("youtube-cmdline-search-sample").build();
            YouTube.Videos.List search = youtubeVideo.videos().list("id,snippet");
// 设置要查询的字段信息 
search.setFields("items(id,snippet/publishedAt,snippet/title,snippet/description,snippet/tags,snippet/channelId,snippet/thumbnails)");
            String apiKey = "申请到的API key";
            if (apiKey == null) {
                return null;
            }
            search.setKey(apiKey);
            search.setMaxResults(50);
            StringBuilder videoIdStr = new StringBuilder();
            if (videoIds.size()  > 1) {
                for (int i = 0; i < videoIds.size(); i++) {
                    videoIdStr.append(videoIds.get(i));
                    if (i != videoIds.size() - 1) {
                        videoIdStr.append(",");
                    }
                }
            } else {
                videoIdStr.append(videoIds.get(0));
            }
						// 此处可以放入最多50个id进行查询,以逗号分隔
            search.setId(videoIdStr.toString());
            VideoListResponse response;
            while (true) {
                try {
                    response = search.execute();
                    break;
                } catch (GoogleJsonResponseException e) {
                    if (403 == e.getDetails().getCode()) {
                        // 配额用尽,使用下一个
                        LogUtil.error("youtube api 配额用尽,尝试替换api key");
                        String newApiKey = "尝试替换新的配额";
                    } else {
                        LogUtil.error("其它异常,结束任务");
                        throw e;
                    }
                }
            }
            if (response == null) {
                return null;
            }
            List<Video> items = response.getItems();
            if (AppUtil.isNull(items)) {
                return null;
            }
            for (Video item : items) {
                // 标签数据
                List<String> tags = item.getSnippet().getTags();
                StringBuilder stringBuilder = new StringBuilder();
                if (AppUtil.isNotNull(tags)) {
                    for (String tag : tags) {
                        stringBuilder.append(tag).append(",");
                    }
                }
                String description = item.getSnippet().getDescription();
                if (description == null) {
                    description = "";
                }
                System.out.println("description:" + description);
 	            System.out.println("channel id:" + channel.getId());
                System.out.println("title:" + item.getSnippet().getTitle());
                System.out.println("thumbnails" + item.getSnippet().getThumbnails().getDefault().getUrl());
              	System.out.println("releaseTime:" + new Date(item.getSnippet().getPublishedAt().getValue()));
            }

查询指定频道的信息(使用channel id)

参考文档

https://developers.google.com/youtube/v3/docs/channels

YouTube youtubeChannel = new YouTube.Builder(Auth.HTTP_TRANSPORT, Auth.JSON_FACTORY, request -> {
            }).setApplicationName("youtube-cmdline-search-sample").build();
            YouTube.Channels.List search = youtubeChannel.channels().list("id,snippet");
            search.setFields("items(snippet/publishedAt,snippet/title,snippet/description,snippet/thumbnails)");
            String apiKey = "从Google云平台获取的api key";
            search.setKey(apiKey);
            search.setMaxResults(50);	
						String channelId = "需要查询的channel id";
            search.setId(channelId);
            ChannelListResponse response;
            while (true) {
                try {
                    response = search.execute();
                    break;
                } catch (GoogleJsonResponseException e) {
                    if (403 == e.getDetails().getCode()) {
                        // 配额用尽,使用下一个
                        LogUtil.error("youtube api 配额用尽,尝试替换api key");
                        String newApiKey = getUsableApiKey(apiKey);
                        return null;
                    } else {
                        LogUtil.error("其它异常,结束任务");
                        throw e;
                    }
                }
            }
            if (response == null || AppUtil.isNull(response.getItems())) {
                return null;
            }
            List<Channel> items = response.getItems();
            if (AppUtil.isNull(items)) {
                return null;
            }
            Channel channel = items.get(0);
			System.out.println("channelId:" + channelId);
			System.out.println("频道名称:" + channel.getSnippet().getTitle());
			System.out.println("缩略图 thumbnails:" + channel.getSnippet().getThumbnails().getDefault().getUrl());
			System.out.println("频道简介:" + channel.getSnippet().getDescription());
}

查询指定播放列表下的视频

参考文档

https://developers.google.com/youtube/v3/docs/playlistItems

播放列表是Youtube区别于Facebook和Ins而独有的,笔者理解播放列表其实相当于对视频合集的分类。

那如何获取playlist id呢?其实与video id一样,点进去一个播放列表详情页,查看它的url

https://www.youtube.com/watch?v=2i1PdfaAKFA&list=PL8mG-RkN2uTwChYF-gaygFQero5g5IXgr

其中list= 后边的 “PL8mG-RkN2uTwChYF-gaygFQero5g5IXgr”即为当前播放列表的playlist id。同时,也可以通过官方提供的api获取一个频道下所有的播放列表,这里不再赘述。

下面是演示代码

YouTube youtube = new YouTube.Builder(Auth.HTTP_TRANSPORT, Auth.JSON_FACTORY, request -> {
            }).setApplicationName("youtube-cmdline-search-sample").build();
            YouTube.PlaylistItems.List search = youtube.playlistItems().list("id,snippet");
            String apiKey = "获取的youtube api key";
            search.setKey(apiKey);
						// 需要接口返回的字段信息            
search.setFields("items(snippet/resourceId/videoId),nextPageToken,pageInfo,prevPageToken");
            search.setMaxResults(50);
						String playlistId = "获取的播放列表";
            search.setPlaylistId(playlistId);
            PlaylistItemListResponse searchResponse;
            while (true) {
                try {
                    searchResponse = search.execute();
                    break;
                } catch (GoogleJsonResponseException e) {
                    if (403 == e.getDetails().getCode()) {
                        // 配额用尽,使用下一个
                        LogUtil.error("youtube api 配额用尽,尝试替换api key");
                        String newApiKey = "尝试获取新的api ";
                        if (newApiKey == null) {
                            // 结束当前任务
                            LogUtil.error("系统当前配额已用完");
                            return;
                        }
                        if (!apiKey.equals(newApiKey)) {
                            // 返回新的api-key则再次尝试
                            search.setKey(newApiKey);
                        } else {
                            // api-key相同则退出
                            return;
                        }
                    } else {
                        LogUtil.error("其它异常,结束任务");
                        throw e;
                    }
                }
            }
            List<PlaylistItem> searchResultList = searchResponse.getItems();
            List<PlaylistItem> allRecord = new ArrayList<>();
            if (searchResultList != null) {
                PageInfo pageInfo = searchResponse.getPageInfo();
                if (pageInfo.getTotalResults() < 50) {
                    // 添加第一页数据
                    List<String> allVideoIds = searchResultList.stream()
                            .map(PlaylistItem::getSnippet)
                            .map(PlaylistItemSnippet::getResourceId)
                            .map(ResourceId::getVideoId)
                            .collect(Collectors.toList());
                    // 打印所有视频id
					allVideoIds.forEach(System.out::println);
                    return;
                } else {
                    // 获取多页数据
                    allRecord.addAll(searchResultList);
                    while (true) {
                      	// 请求下一页的数据
                        search.setPageToken(searchResponse.getNextPageToken());
                        searchResponse = search.execute();
                        if (searchResponse == null ||
                                AppUtil.isNull(searchResponse.getItems())) {
                            break;
                        }
                        List<PlaylistItem> items = searchResponse.getItems();
                        if (AppUtil.isNull(items)) {
                            break;
                        }
                        allRecord.addAll(items);
                        if (items.size() < 50) {
                            break;
                        }
                    }
                }
            }
            if (AppUtil.isNull(allRecord)) {
                return;
            }
            List<String> videoIds = allRecord.stream()
                    .map(PlaylistItem::getSnippet)
                    .map(PlaylistItemSnippet::getResourceId)
                    .map(ResourceId::getVideoId)
                    .collect(Collectors.toList());
			videoIds.forEach(System.out::println);

(3)API相关注意事项

分页相关注意事项

​ 以上绝大部分的请求api 的最大返回条数为50条,分页大小设置超过接口限制则会返回错误提示。

​ 再请求下一页数据时,必须用到上一次请求的 一个参数,像下面这样:

		  search.setPageToken(searchResponse.getNextPageToken());
          searchResponse = search.execute();

配额限制相关

​ 每个Google账户申请到的api key每日有10000个配额的限制。

​ 每个接口消耗的配额并不是相同的,比如Search接口每次消耗100个单位的配额,而Video的相关只消耗1-2个单位。具体每个接口的消耗配额数量可以参考文档。

​ 因此,可以事先做好配额估算,是非常必要的,可以提前多申请几个api key 用于轮换。

​ 另一个需要注意的点是,配额消耗完,接口并不是返回错误信息提醒,而是直接抛出异常。因此,需要在代码中做捕获处理,像这样:

								try {
                    searchResponse = search.execute();
                    break;
                } catch (GoogleJsonResponseException e) {
                    if (403 == e.getDetails().getCode()) {
                        System.out.println("配额用尽,使用下一个");
                    }
                }

另外,你也可以在Google云平台上申请扩充配额,但是这个流程想当繁琐复杂,对于个人开发者来说几乎不能实现。此外高并发的请求API会导致Google拒绝相应,请仔细阅读官方文档。

配额消耗情况表格:

三、订阅功能实现

​ 看到这里可能有读者会问:假如我想实现一个订阅功能呢?我希望订阅一个频道后,Youtube可以将更新信息同步推送给我指定的服务器。

​ 答案是可以的。

​ 笨重但比较直接的想法是采用轮询机制,即每隔一段时间去请求频道下的视频接口,根据返回的视频名称判断是否是新视频。这样做的缺点显而易见,一是除非轮询时间间隔特别短,否则基本没法保证时效性。二是频繁的访问查询接口会浪费掉大量的api 配额,因此这不是一种优雅的解决方案。

​ 为了避免这个问题官方提供了发布订阅系统,一种基于Webhooks实现的订阅推送(对于Webhooks机制不清楚的同学可以了解后再去尝试),可以实现几乎实时的更新推送。

详细资料参考官方的这篇文档

https://developers.google.com/youtube/v3/guides/push_notifications

其主要流程就是,在下面这个网址中添加订阅频道和回调地址

https://pubsubhubbub.appspot.com/subscribe

这样你就会在你的服务器上接收到这样的更新信息

<feed xmlns:yt="http://www.youtube.com/xml/schemas/2015"
         xmlns="http://www.w3.org/2005/Atom">
  <link rel="hub" href="https://pubsubhubbub.appspot.com"/>
  <link rel="self" href="https://www.youtube.com/xml/feeds/videos.xml?channel_id=CHANNEL_ID"/>
  <title>YouTube video feed</title>
  <updated>2015-04-01T19:05:24.552394234+00:00</updated>
  <entry>
    <id>yt:video:VIDEO_ID</id>
    <yt:videoId>VIDEO_ID</yt:videoId>
    <yt:channelId>CHANNEL_ID</yt:channelId>
    <title>Video title</title>
    <link rel="alternate" href="http://www.youtube.com/watch?v=VIDEO_ID"/>
    <author>
     <name>Channel title</name>
     <uri>http://www.youtube.com/channel/CHANNEL_ID</uri>
    </author>
    <published>2015-03-06T21:40:57+00:00</published>
    <updated>2015-03-09T19:05:24.552394234+00:00</updated>
  </entry>
</feed>

四、页面展示

当我们获取到了足够的数据后,如何在我们页面上嵌入Youtube的视频呢?其实很简单,通过iframe直接嵌入到你的document中就可以实现了(注意替换你的video id)。

<iframe src="https://www.youtube.com/embed/KZ7Z2x4FIWw?autoplay=0&amp;autohide=1" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="" style="height: 2rem;"></iframe>

最终实现效果:

五、总结

对于Youtube API的使用笔者也是第一次接触,使用上难免会有不准确之处,欢迎评论区交流,指正笔者的错误。

  • 13
    点赞
  • 80
    收藏
  • 打赏
    打赏
  • 31
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 31
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爆疯

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值