C#爬虫开发小结

c#,爬虫,开发,小结 · 浏览次数 : 882

小编点评

**C#爬虫库推荐** * Downloader:这是一个开源的C#爬虫库,它提供了下载进度条、事件监听等功能。 * Shellprogressbar:这是一个开源的C#进度条库,它提供多种配置选项,可以设置颜色、字体、位置等。 * LinqPad:这是一个开源的C#代码片段工具,它可以像Python写脚本一样快速执行C#代码段。 * RoslynPad:这是一个开源的C#代码片段工具,它可以像Python写脚本一样快速执行C#代码段。 **代码示例** ```csharp private async Task Download(IProgressBar bar, string url, string filepath) { var percentageBar = bar.Spawn(100, $\"正在下载:{Path.GetFileName(url)}\", PercentageBarOptions); await HttpHelper.Downloader.DownloadFileTaskAsync(url, filepath); void DownloadStarted(object? sender, DownloadStartedEventArgs e) { Trace.WriteLine( "\图片, FileName:{Path.GetFileName(e.FileName)}, TotalBytesToReceive:{e.TotalBytesToReceive}" ); } void DownloadFileCompleted(object? sender, AsyncCompletedEventArgs e) { Trace.WriteLine($"\下载完成, filepath:{filepath}\"); percentageBar.Dispose(); } void DownloadProgressChanged(object? sender, DownloadProgressChangedEventArgs e) { percentageBar.AsProgress<double>().Report(e.ProgressPercentage); } } ``` **其他建议** * 使用C#开发控制台应用可以方便分发和跨平台支持。 * 推荐使用 RoslynPad 工具快速执行C#代码段。 * 考虑使用开源库如 Downloader 和 Shellprogressbar 来进行爬虫操作。

正文

前言

2023年以来一直很忙,临近春节,各种琐事更多,但鸽了太久没写文章总是不舒坦,忙中偷闲来记录下最近用C#写爬虫的一些笔记。

爬虫一般都是用Python来写,生态丰富,动态语言开发速度快,调试也很方便

但是

我要说但是,动态语言也有其局限性,笔者作为老爬虫带师,几乎各种语言都搞过,现在这个任务并不复杂,用我最喜欢的C#做小菜一碟~

开始

之前做 OneCat 项目的时候,最开始的数据采集模块,就是用 C# 做的,同时还集成了 Chloe 作为 ORM,用 Nancy 做 HTTP 接口,结合 C# 强大的并发功能,做出来的效果不错。

这次是要爬一些壁纸,很简单的场景,于是沿用了之前 OneCat 项目的一些工具类,并且做了一些改进。

HttpHelper

网络请求直接使用 .Net Core 标准库的 HttpClient,这个库要求使用单例,在 AspNetCore 里一般用依赖注入,不过这次简单的爬虫直接用 Console 程序就行。

把 HTML 爬下来后,还需要解析,在Python中一般用 BeautifulSoup,在C#里可以用 AngleSharp ,也很好用~

为了使用方便,我又封装了一个工具类,把 HttpClient 和 AngleSharp 集成在一起。

public static class HttpHelper {
    public const string UserAgent =
        "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36";

    public static HttpClientHandler Handler { get; }

    public static HttpClient Client { get; }

    static HttpHelper() {
        Handler = new HttpClientHandler();
        Client = new HttpClient(Handler);
        Client.DefaultRequestHeaders.Add("User-Agent", UserAgent);
    }

    public static async Task<IHtmlDocument> GetHtmlDocument(string url) {
        var html = await Client.GetStringAsync(url);
        // todo 这个用法有内存泄漏问题,得优化一下
        return new HtmlParser().ParseDocument(html);
    }

    public static async Task<IHtmlDocument> GetHtmlDocument(string url, string charset) {
        var res = await Client.GetAsync(url);
        var resBytes = await res.Content.ReadAsByteArrayAsync();
        var resStr = Encoding.GetEncoding(charset).GetString(resBytes);
        // todo 这个用法有内存泄漏问题,得优化一下
        return new HtmlParser().ParseDocument(resStr);
    }
}

这段代码里面有俩 todo ,这个内存泄漏的问题在简单的爬虫中影响不大,所以后面有大规模的需求再来优化吧~

搞HTML

大部分爬虫是从网页上拿数据

如果网页是后端渲染出来的话,没有js动态加载数据,基本上用CSS选择器+正则表达式就可以拿到任何想要的数据。

经过前面的封装,请求网页+解析HTML只需要一行代码

IHtmlDocument data = await HttpHelper.GetHtmlDocument(url);

拿到 IHtmlDocument 对象之后,用 QuerySelector 传入css选择器,就可以拿到各种元素了。

例如这样,取出 <li> 元素下所有链接的地址

var data = await HttpHelper.GetHtmlDocument(url);
foreach (var item in data.QuerySelectorAll(".pagew li")) {
    var link = item.QuerySelector("a");
    var href = link?.GetAttribute("href");
    if (href != null) await CrawlItem(href);
}

或者结合正则表达式

var data = await HttpHelper.GetHtmlDocument(url);
var page = data.QuerySelector(".pageinfo");
Console.WriteLine("拿到分页信息:{0}", page?.TextContent);
var match = Regex.Match(page?.TextContent ?? "", @"共\s(\d+)页(\d+)条");
var pageCount = int.Parse(match.Groups[1].Value);
for (int i = 1; i <= pageCount; i++) {
    await CrawlPage(i);
}

正则表达式非常好用,爬虫必备~

这里再推荐一个好用的东西,菜鸟工具的在线正则表达式测试,拿到一个字符串之后,先在测试器里面写出一个能匹配的正则,再放到程序里,效率更高~

地址: https://c.runoob.com/front-end/854/

JSON 处理

老生常谈的问题了

JSON 在 web 开发中很常见,无论是接口交互,还是本地保存数据,这都是一种很好的格式

.Net Core 自带的 System.Text.Json 还不错,不需要手动安装依赖,没有特殊需求的话,直接用这个就好了

这里的场景是要把采集的数据存到 JSON 里,即序列化,用以下的配置代码一把梭即可,可以应付大多数场景

var jsonOption = new JsonSerializerOptions {
    WriteIndented = true,
    Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};

写入文件

await File.WriteAllTextAsync("path", JsonSerializer.Serialize(data, jsonOption));

扩展阅读

下载文件

最简单就是直接用 HttpClient 获取 Response,然后 CopyToAsync 写到文件流里面

这个用法拿来下载几个小文件还可以,但多线程下载、断点重连、失败重试等方法就得自己实现了,比较繁琐。

所以这次我直接用了第三方库 Downloader,这个库看起来很猛,功能很多,我就不翻译了,详情见项目主页

项目地址: https://github.com/bezzad/Downloader

同样的,我把下载的功能也封装到 HttpHelper

增加这部分代码

public static IDownloadService Downloader { get; }

public static DownloadConfiguration DownloadConf => new DownloadConfiguration {
    BufferBlockSize = 10240, // 通常,主机最大支持8000字节,默认值为8000。
    ChunkCount = 8, // 要下载的文件分片数量,默认值为1
    // MaximumBytesPerSecond = 1024 * 50, // 下载速度限制,默认值为零或无限制
    MaxTryAgainOnFailover = 5, // 失败的最大次数
    ParallelDownload = true, // 下载文件是否为并行的。默认值为false
    Timeout = 1000, // 每个 stream reader  的超时(毫秒),默认值是1000
    RequestConfiguration = {
        Accept = "*/*",
        AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
        CookieContainer = new CookieContainer(), // Add your cookies
        Headers = new WebHeaderCollection(), // Add your custom headers
        KeepAlive = true,
        ProtocolVersion = HttpVersion.Version11, // Default value is HTTP 1.1
        UseDefaultCredentials = false,
        UserAgent = UserAgent
    }
};

static HttpHelper() {
    // ...
    Downloader = new DownloadService(DownloadConf);
}

使用方法依然是一行代码

await HttpHelper.Downloader.DownloadFileTaskAsync(url, filepath);

不过这次没有直接封装一个下载的方法,而是把 IDownloadService 对象做成属性,因为下载的时候往往要加一些“buff”

比如监听下载进度,看下面的代码

HttpHelper.Downloader.DownloadStarted += DownloadStarted;
HttpHelper.Downloader.DownloadFileCompleted += DownloadFileCompleted;
HttpHelper.Downloader.DownloadProgressChanged += DownloadProgressChanged;
HttpHelper.Downloader.ChunkDownloadProgressChanged += ChunkDownloadProgressChanged;

这个库提供了四个事件,分别是:

  • 下载开始
  • 下载完成
  • 下载进度变化
  • 分块下载进度变化

进度条

有了这些事件,就可以实现下载进度条展示了,接下来介绍的进度条,也是 Downloader 这个库官方例子中使用的

项目地址: https://github.com/Mpdreamz/shellprogressbar

首先,把官网上的例子忘记吧,那几个例子实际作用不大。

Tick模式

这个进度条有两种模式,一种是它自己的 Tick 方法,先定义总任务数量,执行一次表示完成一个任务,比如这个:

using var bar = new ProgressBar(10, "正在下载所有图片", BarOptions);

上面代码定义了10个任务,每执行一次 bar.Tick() 就表示完成一次任务,执行10次后就整个完成~

IProgress<T> 模式

这个 IProgress<T> 是C#标准库的类型,用来处理进度条的。

ProgressBar 对象可以使用 AsProgress<T> 方法转换称 IProgress<T> 对象,然后调用 IProgress<T>Report 方法,报告进度。

这个就很适合下载进度这种非线性的任务,每次更新时,完成的进度都不一样

Downloader的下载进度更新事件,用的是百分比,所以用这个 IProgress<T> 模式就很合适。

进度条嵌套

本爬虫项目是要采集壁纸,壁纸的形式是按图集组织的,一个图集下可能有多个图片

为了应对这种场景,可以用一个进度条显示总进度,表示当前正在下载某个图集

然后再嵌套子进度条,表示正在下载当前图集的第n张图片

然后的然后,再套娃一个孙子进度条,表示具体图片的下载进度(百分比)

这里用到的是 ProgressBarSpawn 方法,会生成一个 ChildProgressBar 对象,此时更新子进度条对象的值就好了。

直接看代码吧

var list = // 加载图集列表
using var bar = new ProgressBar(list.Count, "正在下载所有图片", BarOptions);

foreach (var item in list) {
    bar.Message = $"图集:{item.Name}";
    bar.Tick();

    foreach (var imgUrl in item.Images) {
        using (var childBar = bar.Spawn(item.ImageCount,$"图片:{imgUrl}",ChildBarOptions)) {
            childBar.Tick();
            // 具体的下载代码
        }
    }
}

这样就实现了主进度条显示下载了第几个图集,子进度条显示下载到第几张图片。

然后具体下载代码中,使用 Downloader 的事件监听,再 Spawn 一个新的进度条显示单张图片的下载进度。

代码如下:

private async Task Download(IProgressBar bar, string url, string filepath) {
    var percentageBar = bar.Spawn(100, $"正在下载:{Path.GetFileName(url)}", PercentageBarOptions);

    HttpHelper.Downloader.DownloadStarted += DownloadStarted;
    HttpHelper.Downloader.DownloadFileCompleted += DownloadFileCompleted;
    HttpHelper.Downloader.DownloadProgressChanged += DownloadProgressChanged;

    await HttpHelper.Downloader.DownloadFileTaskAsync(url, filepath);

    void DownloadStarted(object? sender, DownloadStartedEventArgs e) {
        Trace.WriteLine(
            $"图片, FileName:{Path.GetFileName(e.FileName)}, TotalBytesToReceive:{e.TotalBytesToReceive}");
    }

    void DownloadFileCompleted(object? sender, AsyncCompletedEventArgs e) {
        Trace.WriteLine($"下载完成, filepath:{filepath}");
        percentageBar.Dispose();
    }

    void DownloadProgressChanged(object? sender, DownloadProgressChangedEventArgs e) {
        percentageBar.AsProgress<double>().Report(e.ProgressPercentage);
    }
}

注意所有的 ProgressBar 对象都需要用完释放,所以这里在 DownloadFileCompleted 事件里面 Dispose 了。

上面的是直接用 using 语句,自动释放。

进度条配置

这个东西的自定义功能还不错。

可以配置颜色、显示字符、显示位置啥的

var barOptions = new ProgressBarOptions {
    ForegroundColor = ConsoleColor.Yellow,
    BackgroundColor = ConsoleColor.DarkYellow,
    ForegroundColorError = ConsoleColor.Red,
    ForegroundColorDone = ConsoleColor.Green,
    BackgroundCharacter = '\u2593',
    ProgressBarOnBottom = true,
    EnableTaskBarProgress = RuntimeInformation.IsOSPlatform(OSPlatform.Windows),
    DisplayTimeInRealTime = false,
    ShowEstimatedDuration = false
};

EnableTaskBarProgress 这个选项可以同时更新Windows任务状态栏上的进度

具体配置选项可以直接看源码,里面注释很详细。

如果 Spawn 出来的子进度条没配置选项,那就会继承上一级的配置。

小结

用 C# 来做爬虫还是舒服的,至少比 Java 好很多

做控制台应用,打包成exe也方便分发

除了本文提到的这些第三方库,使用C#开发控制台应用还有其他好用的玩法

比如下面这俩

做图形界面的话,如果要跨平台,Winform、WPF之类的就不考虑了

微软的MAUI好像有点坑,且没有官方Linux支持,也pass掉

比较成熟的可以选 avalonia

轻量级的可以试试: https://github.com/picoe/Eto

另外,推荐一个工具 RoslynPad,这个好像是模仿 LinqPad 的。

可以像Python写脚本一样快速执行C#代码段,还支持引入nuget包,对于写爬虫或者简单代码实验,非常方便

最关键的是开源免费!LinqPad实在太贵了,RoslynPad现在越更新越好用,感觉慢慢可以赶上 LinqPad 了~

PS:新年的公众号红包封面还没搞,争取今晚搞定~

与C#爬虫开发小结相似的内容:

C#爬虫开发小结

前言 2023年以来一直很忙,临近春节,各种琐事更多,但鸽了太久没写文章总是不舒坦,忙中偷闲来记录下最近用C#写爬虫的一些笔记。 爬虫一般都是用Python来写,生态丰富,动态语言开发速度快,调试也很方便 但是 我要说但是,动态语言也有其局限性,笔者作为老爬虫带师,几乎各种语言都搞过,现在这个任务并

谁说爬虫只能Python?看我用C#快速简单实现爬虫开发和演示!

前言:说到爬虫,基本上清一色的都知道用Python,但是对于一些没玩过或者不想玩Python的来说,却比较头大一点。所以以下我站在C# 的角度,来写一个简单的Demo,用来演示C# 实现的简单小爬虫。大家感兴趣可以自己拓展出更加丰富的爬虫功能。 前提:引用包HtmlAgilityPack 先来个爬取

【程序员的福音】一款C#开源的GitHub加速神器

前言 作为一个程序员你是否会经常会遇到GitHub无法访问(如下无法访问图片),或者是访问和下载源码时十分缓慢就像乌龟爬行一般。之前有尝试过手动修改host文件来解决网站的访问问题,以及更换网络但还是有时候无法正常的访问GitHub,今天给大家推荐的这款由C#开源的GitHub加速神器成功的帮我们解

Selenium 自动化浏览器,解决懒加载的网页获取问题

Selenium 自动化浏览器,解决懒加载的网页获取问题。可以用于爬虫这些 在使用 Selenium WebDriver 进行自动化测试时,可以通过设置日志级别来控制输出的日志信息。在 C# 中,可以通过以下方式来禁用 Selenium WebDriver 输出的日志信息: 导入 OpenQA.Se

阶段总结与展望——我的简历

我是一位刚进大二的大学生,在大数据这个专业已经学习了一年多了。 目前,对c++、python、java等编程语言有所了解,能够编写出一些基本的程序,当前接触了Linux操作系统、爬虫操作、Web前端、数据库,有一定的编写程序能力;在校期间获得了“python程序设计一等奖”。 在这门课程结束后,你将

C# 开发技巧 轻松监控方法执行耗时

前言 MethodTimer.Fody 是一个功能强大的库,可以用于测量 .NET 应用程序中的方法的执行时间。允许你在不修改代码的情况下,自动地测量和记录方法的执行时间。 这个工具是基于.NET的 weaving 技术,通过修改IL(Intermediate Language,中间语言)代码来插入

C#开源、简单易用的Dapper扩展类库 - Dommel

前言 今天大姚给大家分享一个C#开源(MIT License)、免费、简单易用的Dapper扩展类库,帮助.NET开发者使用Dapper的CRUD操作变得更简单:Dommel。 项目特性 Dommel 使用 IDbConnection 接口上的扩展方法为 CRUD 操作提供了便捷的 API。 Dom

C语言指针易混淆知识点总结

指针 定义 指针是一个变量,存储另一个变量的内存地址,它允许直接访问和操作内存中的数据,使得程序能够以更灵活和高效的方式处理数据和内存。 获取变量地址:使用取地址符 &。 访问地址上的数据:使用解引用符 *。 例子1 指针是存储另一个变量地址的变量。通过使用取地址符 & 和解引用符 *,我们可以灵活

C++如何在main函数开始之前(或结束之后)执行一段逻辑?

1. 问题 2. 考察的要点 3. 解决策略 3.1. 方案一:使用GCC的拓展功能 3.2. 方案二:使用全局变量 3.3. 方案三:atexit 4. Demo测试 4.1. 测试代码 4.2. 执行结果 5. 程序异常退出场景 5.1. 存在的问题 5.2. 解决方案 5.2.1. 原理 5.

C#/.NET这些实用的编程技巧你都会了吗?

DotNet Exercises介绍 DotNetGuide专栏C#/.NET/.NET Core编程常用语法、算法、技巧、中间件、类库练习集,配套详细的文章教程讲解,助你快速掌握C#/.NET/.NET Core各种编程常用语法、算法、技巧、中间件、类库等等。 GitHub开源地址:https:/