.NET輕鬆寫博客園爬蟲

爬蟲,是一種按照必定的規則,自動地抓取網站的程序或者腳本。.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

  1. 下載網站數據;
  2. 解析/保存網站數據;
  3. 分析數據與下個頁面之間的關係,以便繼續下載下個頁面數據;

下面我將經過代碼演示這三點。數據庫

下載網站數據

換做之前,有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有多個包可供選擇,如HtmlAgilityPackCsQuery等。但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形成的嗎?)

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

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

相關文章
相關標籤/搜索