.NET轻松写博客园爬虫

爬虫,是一种按照一定的规则,自动地抓取网站的程序或者脚本。`.NET`写爬虫非常简单,并能轻松优化性能。今天我将分享一段简短的代码,爬出博客园前200页精华内容,然后通过微小的改动,将代码升级为多线程爬虫,让爬虫速度提升数倍;最后将对爬到了内容进行一些有趣的分析。

我的演示代码通过LINQPad运行,可以在这里找到最新的LINQPad下载链接:https://www.linqpad.net/Download.aspx

这些代码同样可以运行在Visual Studio中。其中.Dump()方法可以在Visual Studio中搜索并安装NuGet包即可兼容:

Install-Package LINQPad

爬虫的三要素

经过我“多年”的爬虫操作的经验,我认为爬虫无非就是:

  1. 下载网站数据;

  2. 解析/保存网站数据;

  3. 分析数据与下个页面之间的关系,以便继续下载下个页面数据;

下面我将通过代码演示这三点。

下载网站数据

换作以前,有WebRequest/WebClient/RestSharp之类的选择,但如今已经都被HttpClient取代了,HttpClient同时内置于.NET Framework 4.5/netstandard 1.1及以后的版本,不用安装第三方包。

代码使用也非常简单:

var client = new HttpClient();	
string response = await client.DownloadStringAsync("https://www.cnblogs.com");

其中response就是从博客园下载的html字符串。

解析网站数据

.NET解析html有多个包可供选择,如HtmlAgilityPack、CsQuery等。但AngleSharp由于其简单好用、功能强大,已经也成为解析html的不错之选。

AngleSharp是开源项目,Github地址是:https://github.com/AngleSharp/AngleSharp。

近期还加入了.NET Foundation(.NET基金会),官网地址是:https://anglesharp.github.io 。

使用AngleSharp解析html过程(在`INQPad`,按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开发者工具,用鼠标定位到分页器:

640?wx_fmt=png

如图,注意到,每一个页面按钮,都对应了一个不同的链接地址,如第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);	
}

运行结果如下:

640?wx_fmt=png

多线程优化

这个爬虫将200页数据全部爬完,根据我的网速,需要76秒,任务管理器显示如下(接收带宽只有1.7Mbps):

640?wx_fmt=png

在.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倍!同时任务管理器显示网络下载流量为(16.5Mbps):

640?wx_fmt=png

数据简单分析

现在我们得到了博客园首页博客简要数据,我将其保存到桌面的一个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();

结果:

640?wx_fmt=png

可见,每天上午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();

结果:

640?wx_fmt=png

可见:星期一、二、三的文章最多,星期四、五逐渐冷淡,星期六、星期日最少。——但星期六比星期日又多一点。(是因为996造成的吗?)

哪位大佬发文最多(取前9名)?

结果:

640?wx_fmt=png

可见,大佬周国通竟然在前200页博客中,独占54篇,我点开了他的博客(https://www.cnblogs.com/tylerzhou/)看了一下,竟然都有相当的质量——绝无放水可言,深入讲了许多.NET的测试系列教程,确实是大佬!

结语

实际应用的爬虫可能不像博客园这么简单,爬虫如果深入,可以遇到很多很多非常有意思的情况。

今天谨希望通过这个简单的博客园爬虫,让大家多多享受写.NET/C#代码的乐趣?。

出处:微信公众号【DotNet骚操作】微信可能无法留言,可点击“阅读原文”转到博客园留言。原文链接:https://www.cnblogs.com/sdflysha/p/20190826-cnblogs-crawler-home.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/314077.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

P5055 【模板】可持久化文艺平衡树

P5055 【模板】可持久化文艺平衡树 突然发现fhq_treap也是可以支持区间翻转的,所以基本上和其他平衡树是一样的,而且还满足重量平衡树的性质,真是太优秀了,只不过常数稍微比较大。 然后这里我们变成了一颗区间平衡树&#xff0c…

kettle同步数据中文乱码问题解决

最近在使用kettle进行数据同步的时候,发现同步来的中文数据产生了乱码。试了下网上的解决方案,最终解决了这个问题。步骤如下: 1:kettle中配置源数据库、目标数据库编码 2:编辑“表输入”,去掉勾选“允许建…

WTM重磅更新,LayuiAdmin免费用 and more

从善如登,从恶如崩。对于一个开发人员来说,那就是做一个好的系统不容易,想搞砸一个系统很简单,删库跑路会还不会么。对于我们开源框架的作者来说,做一个好的框架就像登山(也许是登天)&#xff0…

Lady Layton with Math(杜教筛)

Lady Layton with Math ∑i1n∑j1nϕ(gcd(i,j))∑d1nϕ(d)∑i1n∑j1n[gcd(i,j)d]∑d1nϕ(d)∑i1nd∑j1nd[gcd(i,j)1]∑d1nϕ(d)(2∑i1nd∑j1i[gcd(i,j)1]−1)∑d1nϕ(d)(2∑i1ndϕ(i)−1)\sum_{i 1} ^{n} \sum_{j 1} ^{n} \phi(gcd(i, j))\\ \sum_{d 1} ^{n} \phi(d) \sum_{…

概率期望复习

概率期望 P(A并B)P(A)P(B)-P(A交B) 条件概率 已知A发生B发生的概率,记作P(B|A) 四种情况 P(AB都发生)a,P(AB都不发生)b P(只有A发生)c,P(只有B发生)d P(B|A)a/(ac),即P(B|A)P(AB)/P(A) 全概率公式 P(A)P(B1)P(A|B1)…P(Bn)P(A|Bn) 三门问题 生日悖论 贝叶斯…

kettle数据库操作OPTION SQL_SELECT_LIMIT=DEFAULT问题解决

今天在使用kettle配置数据库映射的时候,有如下报错: Couldnt get field info from [select * from pre_user_base_bak]You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax …

.netcore 分布式事务CAP2.6 快速入门

CAP介绍:CAP是一个用来解决微服务或者分布式系统中分布式事务问题的一个开源项目解决方案。可以解决跨服务器的数据一致性问题。一个简单的列子,如:订单系统创建订单后需要通知邮件通知用户下单成功,解决方案有下面几种&#xff1…

[SDOI2017]数字表格

[SDOI2017]数字表格 假定n<m∏i1n∏j1mf(gcd(i,j))∏d1nf(d)∑i1n∑j1m[gcd(i,j)d]∏d1nf(d)∑i1nd∑j1md[gcd(i,j)1]∏d1nf(d)∑k1ndμ(k)nkdmkd由于nd具有分块性质&#xff0c;并且n&#xff0c;m不大&#xff0c;所以我们可以先预处理出斐波那契数列的前缀积&#xff0c;…

#3601. 一个人的数论

#3601. 一个人的数论 首先这个转化还是很巧妙的&#xff0c;或者很套路的&#xff0c;直接莫比乌斯反演&#xff0c;然后看到了自然数幂之和的形式&#xff0c;那么我们就可以转化为多项式处理&#xff0c;项数就减少到了d1&#xff0c;然后看到题目给出的都是质因数分解结果&a…

2019-03-5-算法-进化(最长公共前缀)

题目描述 编写一个函数来查找字符串数组中的最长公共前缀。 如果不存在公共前缀&#xff0c;返回空字符串 “”。 示例 1: 输入: ["flower","flow","flight"] 输出: "fl"示例 2: 输入: ["dog","racecar","…

基于SQLite+EF6实现一套自己的Key-Value存储管理工具包(2)

上一篇里面整理了一下需求和思路&#xff0c;自定义了扩展的字典对象&#xff0c;这里我们再继续深入编码。BaseExtensions类&#xff0c;这个类作未来任何需要Key-Value形式扩展的基类1234567891011121314151617181920212223242526272829303132333435363738394041424344454647…

P4196 [CQOI2006]凸多边形 /【模板】半平面交

P4196 [CQOI2006]凸多边形 /【模板】半平面交 本来是个板子题&#xff0c;而且我这个板子之前在POJ写过一些题目了&#xff0c;但是这里一直让我RE。 后来解决办法竟然是&#xff1a;先读入第一个多边形不加边&#xff08;存下来&#xff09;&#xff0c;然后去读其他多边形&a…

自然数幂之和

自然数幂之和 https://blog.csdn.net/suncongbo/article/details/97622131 这个文章的整理非常全面。

kettle 空字符串 null问题解决

今天&#xff0c;在配置kettle同步mysql数据的时候&#xff0c;碰到了kettle把空字符串当成null的情况。 解决步骤&#xff1a; 找到配置文件&#xff1a;C:\Users\用户名.kettle目录中找到kettle.properties添加配置&#xff1a;KETTLE_EMPTY_STRING_DIFFERS_FROM_NULLY重启k…

从你的全世界路过—一群程序员的稻城亚丁游记

转眼之间又即将到九月&#xff0c;又到了这个适合去川西旅游的最佳季节。最近有一些朋友问我稻城亚丁的旅游情况&#xff0c;因此我将去年写的这一篇游记再次发出来&#xff0c;希望对那些有计划去川西旅游的朋友们有帮助&#xff01;温馨提示&#xff1a;本文图片较多&#xf…

Beauty Contest(凸包 + 旋转卡壳(模板))

Beauty Contest 直接跑一个凸包&#xff0c;然后跑一跑旋转卡壳&#xff0c;求最大值就行了。 /*Author : lifehappy */ #include <cstdio> #include <cmath> #include <cstring> #include <algorithm> #include <vector> #include <iostre…

P2303 [SDOI2012] Longge(数论/欧拉函数)

P2303 [SDOI2012] Longge 一道看似非常基础的数论题&#xff0c;但是蕴含了非常多的知识&#xff0c;求解 ∑i1ngcd(i,n)\sum_{i1}^ngcd(i,n) i1∑n​gcd(i,n) 这个东西我们轻松地就能化简成id∗φid*\varphiid∗φ的形式&#xff0c;然后考虑如何快速求解&#xff0c;那么可以…

2019-03-06-算法-进化(三数之和)

题目描述 给定一个包含 n 个整数的数组 nums&#xff0c;判断 nums 中是否存在三个元素 a&#xff0c;b&#xff0c;c &#xff0c;使得 a b c 0 &#xff1f;找出所有满足条件且不重复的三元组。 注意&#xff1a;答案中不可以包含重复的三元组。 例如, 给定数组 nums […

2019-03-06-算法-进化(最接近的三数之和)

题目描述 给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数&#xff0c;使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。 例如&#xff0c;给定数组 nums [-1&#xff0c;2&#xff0c;1&#xff0c;-4], 和 …

P3345 [ZJOI2015]幻想乡战略游戏

P3345 [ZJOI2015]幻想乡战略游戏 带修改带权重心 这是经典的树上寻找关键点的题目&#xff0c;我们使用点分治处理这个问题&#xff0c;因为点分治的特性&#xff0c;就相当于在树上二分了。但是这与倍增不同&#xff0c;倍增只是在链上二分&#xff0c;而点分治则是在整棵树上…