C#爬取动态网页上的信息:B站主页

简介

动态内容网站使用 JavaScript 脚本动态检索和渲染数据,爬取信息时需要模拟浏览器行为,否则获取到的源码基本是空的。
本文使用的爬取步骤如下:

  • 使用 Selenium 获取渲染后的 HTML 文档
  • 使用 HtmlAgilityPack 解析 HTML 文档

新建项目,安装需要的库:

  • Selenium.WebDriver
  • HtmlAgilityPack

获取 HTML 文档

需要注意的主要是以下2点:

  • 设置浏览器启动参数:无头模式、禁用GPU加速、设置启动时窗口大小
  • 等待页面动态加载完成:等待5秒钟,设置一个合适的时间即可
private static string GetHtml(string url)
{
    ChromeOptions options = new ChromeOptions();
    // 不显示浏览器
    options.AddArgument("--headless");
    // GPU加速可能会导致Chrome出现黑屏及cpu占用率过高
    options.AddArgument("--nogpu");
    // 设置chrome启动时size大小
    options.AddArgument("--window-size=10,10");

    using (var driver = new ChromeDriver(options))
    {
        try
        {
            driver.Manage().Window.Minimize();
            driver.Navigate().GoToUrl(url);
            // 等待页面动态加载完成
            Thread.Sleep(5000);
            // 返回页面源码
            return driver.PageSource;
        }
        catch (NoSuchElementException)
        {
            Console.WriteLine("找不到该元素");
            return string.Empty;
        }
    }
}

解析 HTML 文档

这里以B站为例,爬取B站UP主主页上的视频信息,如视频的标题链接、封面。
先定义一个类来保存信息:

class VideoInfo
{
    public string Title { get; set; }
    public string Href { get; set; }
    public string imgurl { get; set; }
}

定义解析函数,返回视频信息列表:

private static List<VideoInfo> GetVideoInfos(string url)
{
    List<VideoInfo> videoInfos = new List<VideoInfo>();

    // 加载文档
    var html = GetHtml(url);
    var htmlDoc = new HtmlDocument();
    htmlDoc.LoadHtml(html);

    // 解析文档,先定位到视频列表标签
    var xpath = "/html/body/div[2]/div[4]/div/div/div[1]/div[2]/div/div";
    var htmlNodes = htmlDoc.DocumentNode.SelectNodes(xpath);

    // 循环解析它的子节点视频信息
    foreach (var node in htmlNodes)
    {
        var titleNode = node.SelectSingleNode("a[2]");
        var imgNode = node.SelectSingleNode("a[1]/div[1]/picture/source[1]");

        var title = titleNode.InnerText;
        var href = titleNode.Attributes["href"].Value.Trim('/');
        var imgurl = imgNode.Attributes["srcset"].Value.Split('@')[0].Trim('/');

        videoInfos.Add(new VideoInfo
        {
            Title = title,
            Href = href,
            imgurl = imgurl
        });
    }
    return videoInfos;
}

视频列表标签XPath 路径是通过浏览器调试工具,在指定标签上右键 复制完整的XPath 得到:

image

分析代码中的 node 节点时,html文本格式可能很乱,可以通过在线 HTML 代码格式化 工具格式后再进行分析。

测试

以B站UP主 星瞳_Official 为例,爬取视频信息:

static void Main(string[] args)
{
    var url = @"https://space.bilibili.com/401315430";
    var videoInfos = GetVideoInfos(url);
    foreach (var videoInfo in videoInfos)
    {
        Console.WriteLine(videoInfo.Title);
        Console.WriteLine(videoInfo.Href);
        Console.WriteLine(videoInfo.imgurl);
        Console.WriteLine();
    }
    Console.ReadKey();
}

结果如下:

等一下,好妹妹
www.bilibili.com/video/BV1uyxLeJEM9
i0.hdslb.com/bfs/archive/46a15065d1b6722a04696ffaaa2235287ceaa452.jpg

一口一个?你的超甜辣椒
www.bilibili.com/video/BV1AQsDeiEn1
i0.hdslb.com/bfs/archive/d93d47d67323ee284483e963ffed34fb9884cf61.jpg

这里只是演示爬取动态页面方法如果想获取B站UP主的视频信息,建议直接使用 API 请求数据

补充:使用 CSS 选择器

在实际使用时,动态页面可能会根据ID不同生成不同的个性内容,这直接导致 XPath 的层级发生变化、无法正常定位元素。我在实际使用过程中就遇到这种情况,比如替换上面的URL结果会直接异常:

//var url = @"https://space.bilibili.com/401315430";
var url = @"https://space.bilibili.com/3494357793507869";

推荐使用 CSS 选择器来定位元素,一般来说样式更具有唯一性,使用 CSS 选择器来定位元素需要安装库:

  • HtmlAgilityPack.CssSelectors.NetCore

检测动态页面上的源码可以发现,跟视频列表关联的主要是 .section.video.small-item.fakeDanmu-item 两个选择器:

image

优化上面的 GetVideoInfos 函数,改为 CSS 选择器:

 // 解析文档,先定位到视频列表标签
 //var xpath = "/html/body/div[2]/div[4]/div/div/div[1]/div[2]/div/div";
 //var htmlNodes = htmlDoc.DocumentNode.SelectNodes(xpath);

// 使用 CSS 选择器定位到 class="section video"
var videoSectionNode = htmlDoc.DocumentNode.QuerySelector(".section.video");
var htmlNodes = videoSectionNode.QuerySelectorAll(".small-item.fakeDanmu-item");

测试结果如下:

如果你不开心,可以尝试把手指放进圆圈里
www.bilibili.com/video/BV1gExWeuEJK
i1.hdslb.com/bfs/archive/127819dd1936b82b128576f1b8aacd95eccf58bc.jpg

和室友亲嘴上舰长?不得不出卖色相了
www.bilibili.com/video/BV1yfsReEEd3
i2.hdslb.com/bfs/archive/1ec25178134980de1c78a761430b5deeefb9260f.jpg

参考文章

相关文章

目录简介使用JS互操作使用ClipLazor库创建项目使用方法简单测...
目录简介快速入门安装 NuGet 包实体类User数据库类DbFactory...
本文实现一个简单的配置类,原理比较简单,适用于一些小型项...
C#中Description特性主要用于枚举和属性,方法比较简单,记录...
[TOC] # 原理简介 本文参考[C#/WPF/WinForm/程序实现软件开机...
目录简介获取 HTML 文档解析 HTML 文档测试补充:使用 CSS 选...