You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

requests.get().content返回内容不稳定,提取YouTube Clip视频ID报错

问题描述

我写了一个Python函数用来抓取YouTube Clip页面源码,提取其中的startTimeMs、endTimeMs和videoId信息。下面是提取videoId的代码段:

class className():
    def __init__(self, link)
        # 发送请求
        self.r = requests.get(link)

    def originalVideoID(self):
        # 获取源码
        source = str(self.r.content)

        # videoID的前后标识
        start = "\"videoDetails\":{\"videoId\":\""
        end = '\"'

        # 截取videoDetails之后的内容
        videoID = source.split(start)[1]

        # 截取引号之前的内容
        videoID = videoID.split(end)[0]

预期与实际结果

  • 预期:传入YouTube Clip链接(比如https://www.youtube.com/clip/UgkxU2HSeGL_NvmDJ-nQJrlLwllwMDBdGZFs)时,能稳定获取到videoId为NiXD4xVJM5Y。
  • 实际:有时能得到正确结果,有时会触发IndexError。

调试情况

添加start in source判断后发现,报错时该判断返回False,打印str(self.r.content)能看到返回的源码和预期完全不同。

错误栈

Traceback (most recent call last):
  File "PATHTOPROJECT\FILENAME.py", line 383, in <module>
    main()
  File "PATHTOPROJECT\FILENAME.py", line 165, in download_video
    downloadLink = className(link).originalVideoID()
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "PATHTOPROJECT\FILENAME.py", line 67, in originalVideoID
    videoID = source.split(start)[1]
              ~~~~~~~~~~~~~~~~~~~^^^
IndexError: list index out of range

目标数据位置

目标数据位于页面源码的如下script块中:

<script nonce="601b9hyYx1NEaPf0pQewqA">
    var ytInitialPlayerResponse = 
    {
        ...
        "videoDetails":
        {
            "videoId":"NiXD4xVJM5Y", ...
        },
        ...
        "clipConfig":
        {
            "postId": ... ,"startTimeMs":"0","endTimeMs":"15000"
        }
    }
</script>

想问下我哪里错了?是不是应该用selenium这类工具,还是我的requests用法有问题,需要换实现方式?


问题分析与解决思路

核心问题原因

  1. 反爬拦截:YouTube会检测请求的合法性,直接用requests.get()发送请求时,缺少浏览器标识(User-Agent)等必要头信息,容易被判定为爬虫,返回反爬页面或跳转页面,导致目标字符串不存在,触发索引错误。
  2. 字符串拆分的脆弱性:用split()硬拆字符串的方式极度依赖固定格式,一旦YouTube微调页面源码的字符格式(比如引号转义、空格变化),代码就会直接失效。

解决方案

方案1:优化requests请求,添加请求头

先给请求加上模拟浏览器的头信息,大概率能解决反爬拦截问题:

import requests

class YouTubeClipParser():
    def __init__(self, link):
        # 添加模拟浏览器的请求头
        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36'
        }
        self.r = requests.get(link, headers=headers)
        # 用response.text替代str(r.content),避免编码问题
        self.source = self.r.text

    def originalVideoID(self):
        start = '"videoDetails":{"videoId":"'
        end = '"'
        
        if start not in self.source:
            raise ValueError("无法找到videoId,可能被反爬拦截")
        
        videoID_part = self.source.split(start)[1]
        videoID = videoID_part.split(end)[0]
        return videoID
    
    # 扩展提取时间戳的方法
    def get_clip_timestamps(self):
        import re
        start_match = re.search(r'"startTimeMs":"(\d+)"', self.source)
        end_match = re.search(r'"endTimeMs":"(\d+)"', self.source)
        
        if not start_match or not end_match:
            raise ValueError("无法找到时间戳信息")
        
        return start_match.group(1), end_match.group(1)

方案2:改用正则表达式匹配(更健壮)

比起硬拆字符串,用正则匹配目标字段的方式更能适应源码的小变动,提取videoId也可以改成正则:

def originalVideoID(self):
    import re
    match = re.search(r'"videoDetails":{"videoId":"([a-zA-Z0-9_-]+)"', self.source)
    if not match:
        raise ValueError("未找到videoId")
    return match.group(1)

方案3:解析ytInitialPlayerResponse的JSON数据

目标数据在ytInitialPlayerResponse这个JS变量里,直接提取该变量的JSON内容并解析,能一次性拿到所有需要的字段,这是最可靠的方式:

def get_all_data(self):
    import re
    import json
    # 匹配ytInitialPlayerResponse的JSON内容
    match = re.search(r'var ytInitialPlayerResponse = (\{.*?\});', self.source, re.DOTALL)
    if not match:
        raise ValueError("未找到ytInitialPlayerResponse数据")
    
    json_str = match.group(1)
    # 处理JS语法兼容问题(比如末尾多余逗号)
    json_str = re.sub(r',\s*([}\]])', r'\1', json_str)
    data = json.loads(json_str)
    
    return {
        'videoId': data['videoDetails']['videoId'],
        'startTimeMs': data['clipConfig']['startTimeMs'],
        'endTimeMs': data['clipConfig']['endTimeMs']
    }

关于是否用Selenium

如果YouTube反爬机制升级,比如需要JS渲染才能加载数据,或者频繁请求被封IP,再考虑用Selenium模拟真实浏览器操作。一般情况下,优化requests请求+正则/JSON解析就足够解决问题,Selenium更重、速度也慢。

内容的提问来源于stack exchange,提问作者futium

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 14:37:05