油管(YouTube)的视频详情获取核心是YouTube Data API v3 的 videos.list 接口(对应item_get_video功能),通过视频 ID(video_id)获取标题、播放量、互动数据、创作者信息、时长 / 封面、版权 / 字幕等全量字段,适用于内容聚合、数据分析、舆情监测、跨平台内容管理等场景。该接口采用 API 密钥与 OAuth2.0 双重认证,官方配额严格,本攻略覆盖合规接入、全流程代码、调试排错与生产级优化,兼顾入门与企业级稳定性需求。
一、接口核心认知:功能与适配场景
1. 接口定位与核心价值
2. 核心参数与返回字段
(1)请求参数(官方规范)
(2)返回核心字段(按业务场景分类)
3. 接口限制与注意事项
二、对接前准备:权限与环境搭建
1. 获取接口权限(官方唯一合规路径)
2. 技术环境准备
(1)支持语言与协议
(2)必备工具与依赖
三、实操步骤:接口对接全流程(Python 示例)
步骤 1:理解认证与配额规则
(1)API 密钥认证(公开数据)
(2)OAuth2.0 授权(用户级数据)
步骤 2:完整代码实现(官方 SDK+HTTP 直连双示例)
(1)依赖安装
(2)官方 SDK 实现(推荐,配额更稳定)
import os
import time
import pandas as pd
import logging
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
# 日志配置
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
handlers=[logging.FileHandler("youtube_item_get_video.log"), logging.StreamHandler()]
)
# 配置(替换为你的API密钥)
API_KEY = "你的YouTube API密钥"
YOUTUBE_API_SERVICE_NAME = "youtube"
YOUTUBE_API_VERSION = "v3"
def build_youtube_client():
"""构建YouTube API客户端"""
return build(
YOUTUBE_API_SERVICE_NAME,
YOUTUBE_API_VERSION,
developerKey=API_KEY,
cache_discovery=False
)
def parse_duration(duration: str) -> str:
"""解析ISO 8601时长为HH:MM:SS格式"""
from isodate import parse_duration as iso_parse
duration_obj = iso_parse(duration)
total_seconds = int(duration_obj.total_seconds())
hours = total_seconds // 3600
minutes = (total_seconds % 3600) // 60
seconds = total_seconds % 60
return f"{hours:02d}:{minutes:02d}:{seconds:02d}" if hours else f"{minutes:02d}:{seconds:02d}"
def standardize_video_data(raw_video: dict) -> dict:
"""标准化视频数据,统一输出格式"""
snippet = raw_video.get("snippet", {})
stats = raw_video.get("statistics", {})
content_details = raw_video.get("contentDetails", {})
status = raw_video.get("status", {})
return {
"视频ID": raw_video.get("id", ""),
"标题": snippet.get("title", ""),
"描述": snippet.get("description", "")[:100] + "..." if len(snippet.get("description", "")) > 100 else snippet.get("description", ""),
"封面链接": snippet.get("thumbnails", {}).get("maxres", {}).get("url", snippet.get("thumbnails", {}).get("high", {}).get("url", "")),
"时长": parse_duration(content_details.get("duration", "PT0S")),
"发布时间": snippet.get("publishedAt", "").replace("T", " ").replace("Z", ""),
"播放量": int(stats.get("viewCount", 0)),
"点赞数": int(stats.get("likeCount", 0)),
"评论数": int(stats.get("commentCount", 0)),
"收藏数": int(stats.get("favoriteCount", 0)),
"创作者ID": snippet.get("channelId", ""),
"创作者名称": snippet.get("channelTitle", ""),
"话题标签": ",".join(snippet.get("tags", [])),
"隐私状态": status.get("privacyStatus", "public"),
"是否可嵌入": status.get("embeddable", True),
"是否有字幕": content_details.get("caption", "false"),
"请求时间": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
}
def youtube_item_get_video(video_ids: list, parts: str = "snippet,contentDetails,statistics,status") -> dict:
"""调用YouTube videos.list接口获取视频详情(官方SDK方式)"""
if len(video_ids) > 50:
logging.error("单次最多支持50个视频ID")
return {"success": False, "error_msg": "视频ID数量超出上限"}
youtube = build(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION, developerKey=API_KEY)
try:
request = youtube.videos().list(
part=parts,
id=",".join(video_ids)
)
response = request.execute()
items = response.get("items", [])
if not items:
logging.warning("无视频数据返回")
return {"success": False, "error_msg": "无视频数据", "data": []}
standard_videos = [standardize_video_data(item) for item in items]
return {
"success": True,
"data": standard_videos,
"error_msg": ""
}
except HttpError as e:
error_msg = f"HTTP错误:{e.resp.status} - {e.content.decode('utf-8')}"
logging.error(error_msg)
return {"success": False, "error_msg": error_msg, "data": []}
except Exception as e:
logging.error(f"调用异常:{str(e)}")
return {"success": False, "error_msg": str(e), "data": []}
# 调用示例
if __name__ == "__main__":
# 单视频/批量视频获取示例
video_ids = ["dQw4w9WgXcQ"] # 可添加多个,最多50个
result = youtube_item_get_video(video_ids=video_ids)
if result["success"]:
print("YouTube视频详情:")
df = pd.DataFrame(result["data"])
print(df.to_string(index=False))
df.to_excel("youtube_video_details.xlsx", index=False)
else:
print(f"获取失败:{result['error_msg']}")