1. <wbr id="m8vu6"></wbr>

      <del id="m8vu6"><center id="m8vu6"><source id="m8vu6"></source></center></del>
        <p id="m8vu6"><sub id="m8vu6"></sub></p>

        VB.net 2010 視頻教程 VB.net 2010 視頻教程 python基礎視頻教程
        SQL Server 2008 視頻教程 c#入門經典教程 Visual Basic從門到精通視頻教程
        當前位置:
        首頁 > 編程開發 > c#教程 >
        • C#教程之DotNet應用之爬蟲入門系列(三):常見文本結構的處理

        本站最新發布   C#從入門到精通
        試聽地址  
        http://www.squ68.com/eschool/CSharpxin3721/

        爬蟲在獲取服務器正確的響應后,往往需要處理響應流或文本,從而獲得需要的數據并持久化。本文文字篇幅不會很長,主要向有興趣的讀者介紹一下爬蟲經常接觸的兩種文本結構——Html和Json——的處理方式及相關細節。值得開發新手注意的是,這兩種文本結構并不僅限于爬蟲領域出現,而是任何開發領域都可能會接觸到的,本文雖不能面面俱到,但可以引為向導。

        • Html

        完全理解該節需要掌握:

        1. 知道什么是HTML;
        2. 知道什么是XPath;
        3. 了解XPath的基本語法;
        4. 了解XPath的常用函數;
        5. 熟悉HtmlAgilityPack或AngleSharp之一。

        Html格式的響應文本經常出現在靜態頁面或后端渲染(服務器在收到Http請求后,加載數據并據此生成網頁Html,再返回瀏覽器)的網站。

        在.Net平臺,常用的Html解析工具包有html-agility-pack和AngleSharp。筆者并不清楚兩者的優劣高下(歡迎知情讀者賜教),盡管后者在README中寫道:

        The advantage over similar libraries likeHtmlAgilityPackis that the exposed DOM is using the official W3C specified API, i.e., that even things likequerySelectorAllare available in AngleSharp. Also the parser uses the HTML 5.1 specification, which defines error handling and element correction. The AngleSharp library focuses on standards compliance, interactivity, and extensibility. It is therefore giving web developers working with C# all possibilities as they know from using the DOM in any modern browser.

        但本文還是只給出HtmlAgilityPack的代碼示例(筆者目前還沒用過AngleSharp)。讀者不必糾結和擔心,這兩個項目的維護都很活躍,選擇誰都可以,只需要熟悉API即可。同時,讀者在使用解析包前還需要粗略了解XPath,回頭可以跟下文代碼互相印證。

        讓我們以博客園首頁的新聞頁為例。

        博客園首頁新聞頁面

        假設我們想抓取最近的新聞列表,實體類型定義如下:

                public sealed class NewsEntity
        	{
        		public int Id { get; set; }
        
        		public string Url { get; set; }
        
        		public string Title { get; set; }
        
        		/// <summary>
        		/// 摘要
        		/// </summary>
        		public string Roundup { get; set; }
        
        		public string Publisher { get; set; }
        
        		public DateTime? PublishTime { get; set; }
        
        		/// <summary>
        		/// 推薦數
        		/// </summary>
        		public int DiggCount { get; set; }
        
        		public int CommentCount { get; set; }
        
        		public int ViewCount { get; set; }
        
        		public DateTime CollectTime { get; set; }
        	}
        

        打開瀏覽器開發者工具可以查看該網頁的Html結構,筆者將自上而下、自外而內地向讀者介紹。

        最外層

        讓我們先進入head。新手在寫爬蟲時,往往會碰到瀏覽器上顯示正常而程序抓取卻是亂碼的情況,這時候第一反應應該是檢查自己的編碼(Encoding)是否正確。而下圖中<meta charset="utf-8">這一行,就描述了網頁文字采用的編碼是UTF-8。

        head內容

        另外,也可以在響應頭(Response Headers)里檢查Content-Type字段。但這里有兩點需要注意:并不是每個響應都會包含這個字段;就算返回了該字段,它里面的charset值也不保證和Html里的一致。因此,應當首先查看Html head里charset值。

        響應頭

        接著查看body。

        body內容

        找到并進入新聞列表所在的位置(使用谷歌瀏覽器調試時,只要鼠標放在div上,瀏覽器就會示意相應的網頁區域)。

        新聞列表

        繼續進入單個post_item,就可以看到單條新聞的信息。

        單條新聞信息

        那么如何將Html文本中的數據提取出來呢?上文說的Html解析工具包就登場了,通過Nuget管理器安裝即可。

        HtmlAgilityPack Nuget包

        因為本文側重講文本解析,所以我們假設已經獲得了正確的響應文本。

        using System;
        using System.Collections.Generic;
        using System.Text.RegularExpressions;
        using HtmlAgilityPack;
                
        public class HtmlReadTest
        	{
        		public static List<NewsEntity> GetNewsEntities(string html)
        		{
        			var doc = new HtmlDocument();
        			try
        			{
        				doc.LoadHtml(html);
        			}
        			catch
        			{
        				//Do something...
        				return null;
        			}
        			var root = doc.DocumentNode;
        			var postListNode = root.SelectSingleNode("//div[@id='post_list']");
        			var postItems = postListNode.SelectNodes("//div[@class='post_item']");
        			if (postItems == null || postItems.Count < 1)
        			{
        				return null;
        			}
        			var result = new List<NewsEntity>(postItems.Count);
        			foreach (var item in postItems)
        			{
        				var temp = CreateNewsEntity(item);
        				if (temp != null)
        				{
        					result.Add(temp);
        				}
        			}
        			return result;
        		}
        
        		private static NewsEntity CreateNewsEntity(HtmlNode node)
        		{
        			var titleNode = node.SelectSingleNode(".//a[contains(@class, 'title')]");
        			if (titleNode is null)
        			{
        				return null;
        			}
        			//獲取標題
        			string title = titleNode.InnerText?.Trim();
        			if (string.IsNullOrEmpty(title))
        			{
        				return null;
        			}
        			//獲取鏈接
        			string url = titleNode.GetAttributeValue("href", string.Empty);
        			//獲取摘要
        			var roundupNode = node.SelectSingleNode(".//p[@class='post_item_summary']");
        			string roundup = roundupNode?.InnerText?.Trim() ?? string.Empty;
        			//獲取推薦數
        			var diggNode = node.SelectSingleNode(".//span[@class='diggnum']");
        			int diggCount = 0;
        			if (diggNode != null)
        			{
        				string diggStr = diggNode.InnerText?.Trim();
        				if (!string.IsNullOrEmpty(diggStr))
        				{
        					int.TryParse(diggStr, out diggCount);
        				}
        			}
        			string publisher = string.Empty;
        			DateTime? publishTime = null;
        			int comment = 0, view = 0;
        			var footNode = node.SelectSingleNode(".//div[@class='post_item_foot']");
        			if (footNode != null)
        			{
        				//獲取發布時間
        				var publishTimeStr = footNode.InnerText;
        				publishTime = MatchDate(publishTimeStr);
        				//獲取發布者
        				var publisherNode = footNode.SelectSingleNode("./a[@href]");
        				publisher = publisherNode?.InnerText?.Trim() ?? string.Empty;
        				//獲取閱讀數
        				var commentNode = footNode.SelectSingleNode("./span[contains(@class, 'comment')]");
        				string commentStr = commentNode.InnerText;
        				comment = MatchNumber(commentStr);
        				//獲取評論數
        				var viewNode = footNode.SelectSingleNode("./span[contains(@class, 'view')]");
        				string viewStr = viewNode.InnerText;
        				view = MatchNumber(viewStr);
        			}
        			return new NewsEntity()
        			{
        				Title = title,
        				Url = url,
        				Roundup = roundup,
        				Publisher = publisher,
        				PublishTime = publishTime,
        				DiggCount = diggCount,
        				CommentCount = comment,
        				ViewCount = view,
        				CollectTime = DateTime.Now
        			};
        		}
        
        		private static DateTime? MatchDate(string text)
        		{
        			if (string.IsNullOrEmpty(text))
        			{
        				return null;
        			}
        
        			var match = Regex.Match(text, "(\\d{4}-\\d{2}-\\d{2})\\s(\\d{2}:\\d{2})");
        			if (!match.Success)
        			{
        				return null;
        			}
        			string dateStr = match.Value;
        			if (!DateTime.TryParse(dateStr, out var date))
        			{
        				return null;
        			}
        			return date;
        		}
        
        		private static int MatchNumber(string text)
        		{
        			if (string.IsNullOrEmpty(text))
        			{
        				return 0;
        			}
        
        			var match = Regex.Match(text, "\\d+");
        			if (!match.Success)
        			{
        				return 0;
        			}
        			int.TryParse(match.Value, out int result);
        			return result;
        		}
        	}
        

        可以看到我們獲取具體數據主要使用的API是SelectNodes、SelectSingleNode和GetAttributeValue,非常容易上手;再配合XPath以及局部小范圍使用正則表達式,就可以寫出健壯、清晰、易維護的代碼。以上代碼讀者可自行嘗試并驗證。

        • Json

        完全理解該節需要掌握:

        1. 知道什么是Json;
        2. 知道什么是JPath;
        3. 了解JPath的基本語法;
        4. 熟悉Newtonsoft.Json。

        Json格式的響應文本通常出現在前后端分離開發、前端渲染(通過Ajax請求、異步加載等方式載入頁面數據)的頁面。

        在.Net平臺,最廣為人知的Json解析工具包就是Newtonsoft.Json了。

        Newtonsoft.Json Nuget包

        我們假設某網站有一個接口以Json格式文本返回班級信息,如下:

        {
            "Id": 601,
            "TeacherName": "大木",
            "Data": [
                {
                    "Id": 1,
                    "Name": "菜須鯤"
                },
                {
                    "Id": 2,
                    "Name": "吾亦煩"
                },
                {
                    "Id": 3,
                    "Name": "路寒"
                }
            ]
        }

        一種反序列化的方式是定義一個結構相同的類:

                public sealed class Class
        	{
        		public int Id { get; set; }
        
        		public string TeacherName { get; set; }
        
        		public List<Student> Data { get; set; }
        	}
        
        	public sealed class Student
        	{
        		public int Id { get; set; }
        
        		public string Name { get; set; }
        	}
        

        然后只需要一行代碼就可以將Json文本轉化為Class類:

        using Newtonsoft.Json;
                        
        public static Class DeserializeClass(string json)
        {
            return JsonConvert.DeserializeObject<Class>(json);
        }
        

        但是爬蟲程序在絕大多數很多情況下,可能并不需要Json文本里的全部數據,亦或者是需要根據文本里的數據做一些處理再放入實體類。如果每一個Json格式的文本都要寫一個對應的類,代碼就會顯得非常臃腫(想一個有意義的命名也會令人頭疼)。所幸Newstonsoft.Json.Linq為我們提供了一些“模版類”,可以讓我們輕松地應對這種情況。

        using System.Collections.Generic;
        using Newtonsoft.Json;
        using Newtonsoft.Json.Linq;
        
        public static Class DeserializeClass(string json)
        		{
        			JToken source;
        			try
        			{
        				source = JsonConvert.DeserializeObject<JToken>(json);
        			}
        			catch
        			{
        				//Do something...
        				return null;
        			}
        			if (source is null)
        			{
        				return null;
        			}
        			int classId = source.Value<int>("Id");
        			string tName = source.Value<string>("TeacherName");
        			var result = new Class()
        			{
        				Id = classId,
        				TeacherName = tName
        			};
        			JArray datas = source.Value<JArray>("Data");
        			if (datas != null)
        			{
        				var students = new List<Student>(datas.Count);
        				foreach (var data in datas)
        				{
        					students.Add(GetStudent(data));
        				}
                                        result.Data = students;
        			}
        			return result;
        		}
        
        		private static Student GetStudent(JToken item)
        		{
        			int id = item.Value<int>("Id");
        			string name = item.Value<string>("Name");
        			return new Student() { Id = id, Name = name };
        		}
        

        雖然對該示例來說,使用JToken和JArray“模板類”使得解析的代碼變長了很多,但在更復雜的情況下,這樣寫更具有靈活性,不需要去寫僅用于Json反序列化的類型。另外,對于一些層層嵌套的復雜Json文本,我們也可以通過JPath來簡化代碼,比如如果我們只想獲取學生信息:

        using System.Collections.Generic;
        using System.Linq;
        using Newtonsoft.Json;
        using Newtonsoft.Json.Linq;
        
        public static List<Student> GetStudents(string json)
        		{
        			JToken source;
        			try
        			{
        				source = JsonConvert.DeserializeObject<JToken>(json);
        			}
        			catch
        			{
        				//Do something...
        				return null;
        			}
        			var datas = source?.SelectTokens("$.Data[*]");
        			if(datas is null || !datas.Any())
        			{
        				return null;
        			}
        			var result = new List<Student>();
        			foreach (var data in datas)
        			{
        				result.Add(GetStudent(data));
        			}
        			return result;
        		}
        

        對比上節HTML的解析,可以看到JPath與XPath的相近之處,使用它們可以簡化解析的步驟,讓代碼更簡便、重點更突出。以上代碼感興趣的讀者可以自行嘗試并驗證。

        • 小結

        本文介紹了開發領域常見的兩種文本結構——Html和Json,并介紹了爬蟲領域關注的反序列化工具和使用方法,同時示例代碼也涉及了一些常見的文本細節處理。

        筆者精力有限,不能面面俱到,有興趣的讀者可以自行研究,不要拘泥于本文代碼示例,應當舉一反三;如有紕漏錯誤,歡迎指正。

        最后,向本文提及的開源項目的所有貢獻者致敬!

        相關教程
                
        免费看成年人视频大全_免费看成年人视频在线观看