网站修改title,有限公司破产后债务谁承担,wordpress移动主题puck,镇江网友之家百姓话题1前言爬虫一般都是用Python来写#xff0c;生态丰富#xff0c;动态语言开发速度快#xff0c;调试也很方便但是我要说但是#xff0c;动态语言也有其局限性#xff0c;笔者作为老爬虫带师#xff0c;几乎各种语言都搞过#xff0c;现在这个任务并不复杂#xff0c;用我…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 {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 TaskIHtmlDocument GetHtmlDocument(string url) {var html await Client.GetStringAsync(url);// todo 这个用法有内存泄漏问题得优化一下return new HtmlParser().ParseDocument(html);}public static async TaskIHtmlDocument 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 这个内存泄漏的问题在简单的爬虫中影响不大所以后面有大规模的需求再来优化吧~4搞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);
}正则表达式非常好用爬虫必备~5JSON 处理老生常谈的问题了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));6下载文件最简单就是直接用 HttpClient 获取 Response然后 CopyToAsync 写到文件流里面这个用法拿来下载几个小文件还可以但多线程下载、断点重连、失败重试等方法就得自己实现了比较繁琐。所以这次我直接用了第三方库 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, // 下载文件是否为并行的。默认值为falseTimeout 1000, // 每个 stream reader 的超时毫秒默认值是1000RequestConfiguration {Accept */*,AutomaticDecompression DecompressionMethods.GZip | DecompressionMethods.Deflate,CookieContainer new CookieContainer(), // Add your cookiesHeaders new WebHeaderCollection(), // Add your custom headersKeepAlive true,ProtocolVersion HttpVersion.Version11, // Default value is HTTP 1.1UseDefaultCredentials 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;这个库提供了四个事件分别是下载开始下载完成下载进度变化分块下载进度变化7进度条有了这些事件就可以实现下载进度条展示了接下来介绍的进度条也是 Downloader 这个库官方例子中使用的首先把官网上的例子忘记吧那几个例子实际作用不大。Tick模式这个进度条有两种模式一种是它自己的 Tick 方法先定义总任务数量执行一次表示完成一个任务比如这个using var bar new ProgressBar(10, 正在下载所有图片, BarOptions);上面代码定义了10个任务每执行一次 bar.Tick() 就表示完成一次任务执行10次后就整个完成~IProgressT 模式这个 IProgressT 是C#标准库的类型用来处理进度条的。ProgressBar 对象可以使用 AsProgressT 方法转换称 IProgressT 对象然后调用 IProgressT 的 Report 方法报告进度。这个就很适合下载进度这种非线性的任务每次更新时完成的进度都不一样Downloader的下载进度更新事件用的是百分比所以用这个 IProgressT 模式就很合适。进度条嵌套本爬虫项目是要采集壁纸壁纸的形式是按图集组织的一个图集下可能有多个图片为了应对这种场景可以用一个进度条显示总进度表示当前正在下载某个图集然后再嵌套子进度条表示正在下载当前图集的第n张图片然后的然后再套娃一个孙子进度条表示具体图片的下载进度百分比这里用到的是 ProgressBar 的 Spawn 方法会生成一个 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.AsProgressdouble().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 出来的子进度条没配置选项那就会继承上一级的配置。8小结用 C# 来做爬虫还是舒服的至少比 Java 好很多做控制台应用打包成exe也方便分发