.NET轻松写博客园爬虫
爬蟲,是一種按照一定的規則,自動地抓取網站的程序或者腳本。`.NET`寫爬蟲非常簡單,并能輕松優化性能。今天我將分享一段簡短的代碼,爬出博客園前200頁精華內容,然后通過微小的改動,將代碼升級為多線程爬蟲,讓爬蟲速度提升數倍;最后將對爬到了內容進行一些有趣的分析。
我的演示代碼通過LINQPad運行,可以在這里找到最新的LINQPad下載鏈接:https://www.linqpad.net/Download.aspx
這些代碼同樣可以運行在Visual Studio中。其中.Dump()方法可以在Visual Studio中搜索并安裝NuGet包即可兼容:
Install-Package LINQPad爬蟲的三要素
經過我“多年”的爬蟲騷操作的經驗,我認為爬蟲無非就是:
下載網站數據;
解析/保存網站數據;
分析數據與下個頁面之間的關系,以便繼續下載下個頁面數據;
下面我將通過代碼演示這三點。
下載網站數據
換作以前,有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開發者工具,用鼠標定位到分頁器:
如圖,注意到,每一個頁面按鈕,都對應了一個不同的鏈接地址,如第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.7Mbps):
在.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):
數據簡單分析
現在我們得到了博客園首頁博客簡要數據,我將其保存到桌面的一個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();結果:
可見:星期一、二、三的文章最多,星期四、五逐漸冷淡,星期六、星期日最少。——但星期六比星期日又多一點。(是因為996造成的嗎?)
哪位大佬發文最多(取前9名)?
結果:
可見,大佬周國通竟然在前200頁博客中,獨占54篇,我點開了他的博客(https://www.cnblogs.com/tylerzhou/)看了一下,竟然都有相當的質量——絕無放水可言,深入講了許多.NET的測試系列教程,確實是大佬!
結語
實際應用的爬蟲可能不像博客園這么簡單,爬蟲如果深入,可以遇到很多很多非常有意思的情況。
今天謹希望通過這個簡單的博客園爬蟲,讓大家多多享受寫.NET/C#代碼的樂趣?。
出處:微信公眾號【DotNet騷操作】微信可能無法留言,可點擊“閱讀原文”轉到博客園留言。原文鏈接:https://www.cnblogs.com/sdflysha/p/20190826-cnblogs-crawler-home.html
總結
以上是生活随笔為你收集整理的.NET轻松写博客园爬虫的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 设置ABP默认使用中文
- 下一篇: WTM重磅更新,LayuiAdmin免费