爬虫,是一种按照必定的规则,自动地抓取网站的程序或者脚本。.NET
写爬虫很是简单,并能轻松优化性能。今天我将分享一段简短的代码,爬出博客园前200页精华内容,而后经过微小的改动,将代码升级为多线程爬虫,让爬虫速度提高数倍;最后将对爬到了内容进行一些有趣的分析。html
本文演示的代码,能够从这里下载:https://github.com/sdcb/blog-data/tree/master/2019/20190826-cnblogs-crawler-homegit
个人演示代码经过LINQPad
运行,能够在这里找到最新的LINQPad
下载连接:https://www.linqpad.net/Download.aspxgithub
这些代码一样能够运行在Visual Studio
中。其中.Dump()
方法能够在Visual Studio
中搜索并安装NuGet
包便可兼容:正则表达式
Install-Package LINQPad
通过我“多年”的爬虫骚操做的经验,我认为爬虫无非就是:shell
下面我将经过代码演示这三点。数据库
换做之前,有WebRequest
/WebClient
/RestSharp
之类的选择,但现在已经都被HttpClient
取代了,HttpClient
同时内置于.NET Framework 4.5
/netstandard 1.1
及之后的版本,不用安装第三方包。json
代码使用也很是简单:网络
var client = new HttpClient(); string response = await client.DownloadStringAsync("https://www.cnblogs.com");
其中response
就是从博客园下载的html
字符串。多线程
.NET
解析html
有多个包可供选择,如HtmlAgilityPack
、CsQuery
等。但AngleSharp
因为其简单好用、功能强大,已经也成为解析html
的不错之选。dom
AngleSharp
是开源项目,Github
地址是:https://github.com/AngleSharp/AngleSharp。
近期还加入了.NET Foundation
(.NET基金会),官网地址是:https://anglesharp.github.io 。
使用AngleSharp
解析html
示例代码(在LINQPad
中,按Ctrl+Shift+P
快速安装NuGet
包):
Install-Package AngleSharp Install-Package Newtonsoft.Json
使用代码以下:
var parser = new HtmlParser(); IHtmlDocument dom = parser.ParseDocument(@"<ul> <li> <a href=""cnblogs.com"">博客园</a> <a href=""baidu.com"">百度</a> <a href=""google.com"">谷歌</a> </li> <ul>"); var data = dom.QuerySelectorAll("ul li a").Select(x => new { Link = x.GetAttribute("href"), Title = x.TextContent }).Dump();
运行效果:
Link | Title |
---|---|
cnblogs.com | 博客园 |
baidu.com | 百度 |
google.com | 谷歌 |
而后这些数据能够经过JSON
序列化,保存到桌面上:
File.WriteAllText(@"C:\Users\sdfly\Desktop\cnblogs.json", JsonConvert.SerializeObject(data));
在解析网页数据时,可能还须要灵活运用正则表达式
,来抓取没那么直观的信息。
咱们找到博客园的分页器,打开F12
开发者工具,用鼠标定位到分页器:
如图,注意到,每个页面按钮,都对应了一个不一样的连接地址,如第2页,对应的的连接是:/sitehome/p/2
,第3页,对应的是:/sitehome/p/3
。
博客园首页内容一共有200
页,所以只需将在每一页拼接一个$"/sitehome/p/{页面数码}"
便可。
根据上面的知识,能够轻松将博客园首页200页数据爬出来:
var http = new HttpClient(); var parser = new HtmlParser(); for (var page = 1; page <= 200; ++page) { string pageData = await http.GetStringAsync($"https://www.cnblogs.com/sitehome/p/{page}"); IHtmlDocument doc = await parser.ParseDocumentAsync(pageData); doc.QuerySelectorAll(".post_item").Select(tag => new { Title = tag.QuerySelector(".titlelnk").TextContent, Page = page, UserName = tag.QuerySelector(".post_item_foot .lightblue").TextContent, PublishTime = DateTime.Parse(Regex.Match(tag.QuerySelector(".post_item_foot").ChildNodes[2].TextContent, @"(\d{4}\-\d{2}\-\d{2}\s\d{2}:\d{2})", RegexOptions.None).Value), CommentCount = int.Parse(tag.QuerySelector(".post_item_foot .article_comment").TextContent.Trim()[3..^1]), ViewCount = int.Parse(tag.QuerySelector(".post_item_foot .article_view").TextContent[3..^1]), BriefContent = tag.QuerySelector(".post_item_summary").TextContent.Trim(), }).Dump(page); }
运行结果以下:
这个爬虫将200
页数据所有爬完,根据个人网速,须要76
秒,任务管理器显示以下(下载速度只有每秒1.7 Mbps
)
在.NET
/C#
中,只需对此代码的for
循环修改成LINQ
,而后而加以使用Parallel LINQ
,便可将代码并行化:
Enumerable.Range(1, 200) // for循环转换为LINQ .AsParallel() // 将LINQ并行化 .AsOrdered() // 按顺序保存结果(注意并不是按顺序执行) .SelectMany(page => { return Task.Run(async() => // 非异步代码使用async/await,须要包一层Task { string pageData = await http.GetStringAsync($"https://www.cnblogs.com/sitehome/p/{page}".Dump()); IHtmlDocument doc = await parser.ParseDocumentAsync(pageData); return doc.QuerySelectorAll(".post_item").Select(tag => new { Title = tag.QuerySelector(".titlelnk").TextContent, Page = page, UserName = tag.QuerySelector(".post_item_foot .lightblue").TextContent, PublishTime = DateTime.Parse(Regex.Match(tag.QuerySelector(".post_item_foot").ChildNodes[2].TextContent, @"(\d{4}\-\d{2}\-\d{2}\s\d{2}:\d{2})", RegexOptions.None).Value), CommentCount = int.Parse(tag.QuerySelector(".post_item_foot .article_comment").TextContent.Trim()[3..^1]), ViewCount = int.Parse(tag.QuerySelector(".post_item_foot .article_view").TextContent[3..^1]), BriefContent = tag.QuerySelector(".post_item_summary").TextContent.Trim(), }); }).GetAwaiter().GetResult(); // 等待Task执行完毕 })
经过这个很是简单的优化,在个人电脑上,便可将运行时间下降为14.915
秒,速度快了5倍!同时任务管理器显示网络下载流量为:
如今咱们获得了博客园首页博客简要数据,我将其保存到桌面的一个json
文件中(你们也能够试着保存为其它格式,如数据库中)。固然少不了分析一番。使用LINQPad
,能够很轻松地分析这些数据,并生成图表。
void Main() { var data = JsonConvert.DeserializeObject<List<CnblogsItem>>( File.ReadAllText(@"C:\Users\sdfly\Desktop\cnblogs.json")); } class CnblogsItem { public string TItle { get; set; } public int Page { get; set; } public string UserName { get; set; } public DateTime PublishTime { get; set; } public int CommentCount { get; set; } public int ViewCount { get; set; } public string BriefContent { get; set; } }
我建立了一个CnblogsItem
的类,用来反序列号桌面上json
文件的数据。返序列化完成后,这些数据保存在data
变量中。
Util.Chart(data
.GroupBy(x => x.PublishTime.Hour)
.Select(x => new { Hour = x.Key, ViewCount = 1.0 * x.Sum(v => v.ViewCount) }) .OrderByDescending(x => x.Hour), x => x.Hour, y => y.ViewCount).Dump();
结果:
可见,天天上午9点发文章浏览量最高,凌晨3-4点发文章浏览量最低(谁会在晚上3-4点爬起来看东西呢?)
Util.Chart(data
.GroupBy(x => x.PublishTime.DayOfWeek)
.Select(x => new { WeekDay = x.Key, ArticleCount = x.Count() }) .OrderBy(x => x.WeekDay), x => x.WeekDay.ToString(), y => y.ArticleCount).Dump();
结果:
可见:星期1、2、三的文章最多,星期4、五逐渐冷淡,星期6、星期日最少。——但星期六比星期日又多一点。(是由于996
形成的吗?)
Util.Chart(data
.GroupBy(x => x.UserName)
.Select(x => new { UserName = x.Key, ArticleCount = x.Count() }) .OrderByDescending(x => x.ArticleCount) .Take(9), x => x.UserName, y => y.ArticleCount).Dump();
结果:
可见,大佬周国通
居然在前200页博客中,独占54篇,我点开了他的博客(https://www.cnblogs.com/tylerzhou/)看了一下,居然都有至关的质量——绝无放水可言,深刻讲了许多.NET
的测试系列教程,确实是大佬!
实际应用的爬虫可能不像博客园这么简单,爬虫若是深刻,能够遇到不少不少很是有意思的状况。
今天谨但愿经过这个简单的博客园爬虫,让你们多多享受写.NET
/C#
代码的乐趣😃。
转载:
https://www.cnblogs.com/sdflysha/p/20190826-cnblogs-crawler-home.html