C#网络爬虫开发
1前言爬虫一般都是用Python来写,生态丰富,动态语言开发速度快,调试也很方便
但是
我要说但是,动态语言也有其局限性,笔者作为老爬虫带师,几乎各种语言都搞过,现在这个任务并不复杂,用我最喜欢的C#做小菜一碟~
2开始
之前做 OneCat 项目的时候,最开始的数据采集模块,就是用 C# 做的,同时还集成了 Chloe 作为 ORM,用 Nancy 做 HTTP 接口,结合 C# 强大的并发功能,做出来的效果不错。
这次是要爬一些壁纸,很简单的场景,于是沿用了之前 OneCat 项目的一些工具类,并且做了一些改进。
3HttpHelper
网络请求直接使用 .Net Core 标准库的 HttpClient,这个库要求使用单例,在 AspNetCore 里一般用依赖注入,不过这次简单的爬虫直接用 Console 程序就行。
把 HTML 爬下来后,还需要解析,在Python中一般用 BeautifulSoup,在C#里可以用 AngleSharp ,也很好用~
为了使用方便,我又封装了一个工具类,把 HttpClient 和 AngleSharp 集成在一起。
public static class HttpHelper {<br> public const string UserAgent =<br> "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36";<br><br> public static HttpClientHandler Handler { get; }<br><br> public static HttpClient Client { get; }<br><br> static HttpHelper() {<br> Handler = new HttpClientHandler();<br> Client = new HttpClient(Handler);<br> Client.DefaultRequestHeaders.Add("User-Agent", UserAgent);<br> }<br><br> public static async Task<IHtmlDocument> GetHtmlDocument(string url) {<br> var html = await Client.GetStringAsync(url);<br> // todo 这个用法有内存泄漏问题,得优化一下<br> return new HtmlParser().ParseDocument(html);<br> }<br><br> public static async Task<IHtmlDocument> GetHtmlDocument(string url, string charset) {<br> var res = await Client.GetAsync(url);<br> var resBytes = await res.Content.ReadAsByteArrayAsync();<br> var resStr = Encoding.GetEncoding(charset).GetString(resBytes);<br> // todo 这个用法有内存泄漏问题,得优化一下<br> return new HtmlParser().ParseDocument(resStr);<br> }<br>}<br>这段代码里面有俩 todo ,这个内存泄漏的问题在简单的爬虫中影响不大,所以后面有大规模的需求再来优化吧~
4搞HTML
大部分爬虫是从网页上拿数据
如果网页是后端渲染出来的话,没有js动态加载数据,基本上用CSS选择器+正则表达式就可以拿到任何想要的数据。
经过前面的封装,请求网页+解析HTML只需要一行代码
IHtmlDocument data = await HttpHelper.GetHtmlDocument(url);<br>拿到 IHtmlDocument 对象之后,用 QuerySelector 传入css选择器,就可以拿到各种元素了。
例如这样,取出 元素下所有链接的地址
var data = await HttpHelper.GetHtmlDocument(url);<br>foreach (var item in data.QuerySelectorAll(".pagew li")) {<br> var link = item.QuerySelector("a");<br> var href = link?.GetAttribute("href");<br> if (href != null) await CrawlItem(href);<br>}<br>或者结合正则表达式
var data = await HttpHelper.GetHtmlDocument(url);<br>var page = data.QuerySelector(".pageinfo");<br>Console.WriteLine("拿到分页信息:{0}", page?.TextContent);<br>var match = Regex.Match(page?.TextContent ?? "", @"共\s(\d+)页(\d+)条");<br>var pageCount = int.Parse(match.Groups.Value);<br>for (int i = 1; i <= pageCount; i++) {<br> await CrawlPage(i);<br>}<br>使用方法依然是一行代码
var jsonOption = new JsonSerializerOptions {<br> WriteIndented = true,<br> Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping<br>};<br>不过这次没有直接封装一个下载的方法,而是把 IDownloadService 对象做成属性,因为下载的时候往往要加一些“buff”
比如监听下载进度,看下面的代码
await File.WriteAllTextAsync("path", JsonSerializer.Serialize(data, jsonOption));这个库提供了四个事件,分别是:
[*]下载开始
[*]下载完成
[*]下载进度变化
[*]分块下载进度变化
7进度条
有了这些事件,就可以实现下载进度条展示了,接下来介绍的进度条,也是 Downloader 这个库官方例子中使用的
项目地址: https://github.com/Mpdreamz/shellprogressbar
首先,把官网上的例子忘记吧,那几个例子实际作用不大。
Tick模式
这个进度条有两种模式,一种是它自己的 Tick 方法,先定义总任务数量,执行一次表示完成一个任务,比如这个:
public static IDownloadService Downloader { get; }<br><br>public static DownloadConfiguration DownloadConf => new DownloadConfiguration {<br> BufferBlockSize = 10240, // 通常,主机最大支持8000字节,默认值为8000。<br> ChunkCount = 8, // 要下载的文件分片数量,默认值为1<br> // MaximumBytesPerSecond = 1024 * 50, // 下载速度限制,默认值为零或无限制<br> MaxTryAgainOnFailover = 5, // 失败的最大次数<br> ParallelDownload = true, // 下载文件是否为并行的。默认值为false<br> Timeout = 1000, // 每个 stream reader 的超时(毫秒),默认值是1000<br> RequestConfiguration = {<br> Accept = "*/*",<br> AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,<br> CookieContainer = new CookieContainer(), // Add your cookies<br> Headers = new WebHeaderCollection(), // Add your custom headers<br> KeepAlive = true,<br> ProtocolVersion = HttpVersion.Version11, // Default value is HTTP 1.1<br> UseDefaultCredentials = false,<br> UserAgent = UserAgent<br> }<br>};<br><br>static HttpHelper() {<br> // ...<br> Downloader = new DownloadService(DownloadConf);<br>}<br>上面代码定义了10个任务,每执行一次 bar.Tick() 就表示完成一次任务,执行10次后就整个完成~
IProgress 模式
这个 IProgress 是C#标准库的类型,用来处理进度条的。
ProgressBar 对象可以使用 AsProgress 方法转换称 IProgress 对象,然后调用 IProgress 的 Report 方法,报告进度。
这个就很适合下载进度这种非线性的任务,每次更新时,完成的进度都不一样
Downloader的下载进度更新事件,用的是百分比,所以用这个 IProgress 模式就很合适。
进度条嵌套
本爬虫项目是要采集壁纸,壁纸的形式是按图集组织的,一个图集下可能有多个图片
为了应对这种场景,可以用一个进度条显示总进度,表示当前正在下载某个图集
然后再嵌套子进度条,表示正在下载当前图集的第n张图片
然后的然后,再套娃一个孙子进度条,表示具体图片的下载进度(百分比)
这里用到的是 ProgressBar 的 Spawn 方法,会生成一个 ChildProgressBar 对象,此时更新子进度条对象的值就好了。
直接看代码吧
await HttpHelper.Downloader.DownloadFileTaskAsync(url, filepath);<br>这样就实现了主进度条显示下载了第几个图集,子进度条显示下载到第几张图片。
然后具体下载代码中,使用 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;
var jsonOption = new JsonSerializerOptions {<br> WriteIndented = true,<br> Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping<br>};<br>
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().Report(e.ProgressPercentage);
}
}
注意所有的 ProgressBar 对象都需要用完释放,所以这里在 DownloadFileCompleted 事件里面 Dispose 了。
上面的是直接用 using 语句,自动释放。
进度条配置
这个东西的自定义功能还不错。
可以配置颜色、显示字符、显示位置啥的
using var bar = new ProgressBar(10, "正在下载所有图片", BarOptions);<br>EnableTaskBarProgress 这个选项可以同时更新Windows任务状态栏上的进度
具体配置选项可以直接看源码,里面注释很详细。
如果 Spawn 出来的子进度条没配置选项,那就会继承上一级的配置。
8小结
用 C# 来做爬虫还是舒服的,至少比 Java 好很多
做控制台应用,打包成exe也方便分发
https://img2023.cnblogs.com/blog/2807374/202301/2807374-20230103133728640-245634516.jpg
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页:
[1]