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

使用HtmlAgilityPack抓取YouTube视频信息时节点返回null问题

问题根因

你之前推测的「HtmlAgilityPack无法读取ytd-page-manager这类自定义标签」是错误的:只要节点存在于加载到的HTML源码中,HtmlAgilityPack可以正常解析任意名称的自定义标签,和标签名格式无关。
返回null的核心原因有两个:

  • HtmlAgilityPack本身没有JavaScript执行能力,直接调用Load()拉取到的只是YouTube服务端返回的初始骨架HTML,所有视频数据、ytd开头的自定义组件都是页面加载后靠JS动态渲染插入到DOM里的,你写的XPath对应的节点根本不存在于初始源码中,自然匹配不到结果。
  • 你写的全路径带索引的XPath鲁棒性极差,YouTube前端会频繁调整DOM层级、板块顺序,哪怕你拿到了渲染后的完整DOM,只要节点索引、层级有一点变动,这个XPath就会直接失效。
可行实现方案

不要硬爬动态渲染后的DOM节点,YouTube频道页的所有视频数据在初始返回的HTML里就已经内嵌在ytInitialData这个全局JS变量中,直接提取这段JSON数据解析即可,不需要JS渲染环境,稳定性远高于XPath匹配DOM的方案。

实现步骤

  • 拉取页面时配置常规浏览器UserAgent,避免被YouTube反爬拦截
  • 遍历页面script标签,提取ytInitialData变量对应的JSON内容
  • 反序列化JSON后按固定结构遍历提取视频标题、描述、链接、播放量等需要的字段

参考代码

using HtmlAgilityPack;
using System.Text.Json;

string url = "https://www.youtube.com/c/charlieputh/featured";
HtmlWeb webClient = new HtmlWeb();
// 模拟浏览器请求头
webClient.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36";
HtmlDocument doc = webClient.Load(url);

// 提取内嵌的ytInitialData数据
string ytInitialDataRaw = string.Empty;
HtmlNodeCollection scriptNodes = doc.DocumentNode.SelectNodes("//script");
foreach (HtmlNode script in scriptNodes)
{
    string scriptText = script.InnerText;
    if (scriptText.StartsWith("var ytInitialData = "))
    {
        int startPos = "var ytInitialData = ".Length;
        int endPos = scriptText.LastIndexOf(';');
        ytInitialDataRaw = scriptText.Substring(startPos, endPos - startPos);
        break;
    }
}

// 解析JSON提取视频信息
using JsonDocument jsonDoc = JsonDocument.Parse(ytInitialDataRaw);
var selectedTab = jsonDoc.RootElement
    .GetProperty("contents")
    .GetProperty("twoColumnBrowseResultsRenderer")
    .GetProperty("tabs")
    .EnumerateArray()
    .First(tab => tab.GetProperty("tabRenderer").GetProperty("selected").GetBoolean())
    .GetProperty("tabRenderer");

var videoList = selectedTab
    .GetProperty("content")
    .GetProperty("sectionListRenderer")
    .GetProperty("contents")
    .EnumerateArray()
    .SelectMany(section => section.GetProperty("itemSectionRenderer").GetProperty("contents").EnumerateArray())
    .Where(content => content.TryGetProperty("shelfRenderer", out _))
    .SelectMany(shelf => shelf.GetProperty("shelfRenderer").GetProperty("content").GetProperty("horizontalListRenderer").GetProperty("items").EnumerateArray())
    .Select(item => item.GetProperty("gridVideoRenderer"));

foreach (var videoItem in videoList)
{
    // 提取视频标题
    string videoTitle = videoItem.GetProperty("title").GetProperty("runs")[0].GetProperty("text").GetString();
    // 提取视频ID
    string videoId = videoItem.GetProperty("videoId").GetString();
    // 其他字段比如观看量、发布时间、短描述都可以在gridVideoRenderer节点下找到对应属性
    Console.WriteLine($"视频标题:{videoTitle},视频地址:https://www.youtube.com/watch?v={videoId}");
}
Console.ReadLine();

注意事项

  • 这个方案不需要依赖Selenium、Playwright这类重的浏览器自动化工具,请求和解析速度快,资源占用低
  • 如果后续YouTube调整了内嵌JSON的结构,只需要对照实际返回的JSON调整属性访问路径即可,维护成本远低于超长XPath
  • 如果请求返回异常,可以适当调整请求头、增加请求间隔,避免触发YouTube的反爬机制

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 00:09:30