<rp id="wnpn7"><ruby id="wnpn7"></ruby></rp>
<progress id="wnpn7"><track id="wnpn7"><rt id="wnpn7"></rt></track></progress>
<ruby id="wnpn7"></ruby>
<ruby id="wnpn7"><blockquote id="wnpn7"><div id="wnpn7"></div></blockquote></ruby>

    1. <em id="wnpn7"><ruby id="wnpn7"><input id="wnpn7"></input></ruby></em>
      1. <button id="wnpn7"><acronym id="wnpn7"></acronym></button><button id="wnpn7"><acronym id="wnpn7"></acronym></button>

        <rp id="wnpn7"><acronym id="wnpn7"></acronym></rp>

        <li id="wnpn7"><object id="wnpn7"><u id="wnpn7"></u></object></li>
        VB.net 2010 視頻教程 VB.net 2010 視頻教程 python基礎視頻教程
        SQL Server 2008 視頻教程 c#入門經典教程 Visual Basic從門到精通視頻教程
        當前位置:
        首頁 > 編程開發 > c#教程 >
        • DotNet應用之爬蟲入門系列(二):HttpClient的前世今生

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

        對想寫網頁爬蟲的人來說,最先要搞清楚的基礎知識就是HTTP/HTTPS,以及所使用的編程語言對應該領域的基礎庫。因此,本文將為有興趣的讀者介紹.Net基礎庫中涉及HTTP的基礎部分,并展示一些簡單易懂的示例(同時,我計劃在下期或下下期提供一個稍有難度、較為完整的爬蟲案例,讓新手更深刻地理解.Net庫對HTTP的封裝,以及如何處理編寫爬蟲過程中的意外情況)。另外須提示的是,看懂本文起碼需要知道Http相關的“概念性”基礎知識,例如知道GET、POST方法是什么,Http請求的Headers通常有哪些及分別表示什么等等;一些案例如果自己想嘗試和復現,需要知道瀏覽器調試抓包工具使用方面的知識(這些資料網上非常豐富,筆者不再贅敘,瀏覽器建議選擇谷歌或火狐,抓包工具有Windows適用的Fiddler或Mac上常用的Charles)。

        前世的HttpWebRequest/HttpWebResponse

        早先使用C#編寫一個Http請求,需要用到System.Net.HttpWebRequest類。這個類的設計可謂非常之原始,但這也意味著它非常貼合編寫Http請求的需要。

        HttpWebRequest APIs from .Net Core 2.2

        讓我們借一個很簡單的例子來看看編寫出的代碼。簡書,是一個非常簡潔、別致的創作社區(知乎別吃醋哦)。在它的首頁上,有一個“推薦作者”的區域(已用紅色方框標注),如圖:

        簡書首頁(2019.08.18)

        通過瀏覽器調試可以很輕松地發現,這個區域的數據是通過一個請求單獨獲取的:

        那么,我們按部就班地向HttpWebRequest實例填入網站所需的請求參數,就可以獲得我們想要的響應文本。控制臺代碼如下:

        			HttpWebRequest request = WebRequest.CreateHttp("https://www.jianshu.com/users/recommended?seen_ids=&count=5&only_unfollowed=true");
        			request.Method = "GET";
        			request.Accept = "application/json";
        			request.Referer = "https://www.jianshu.com/";
        			request.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36";
        			request.Host = "www.jianshu.com";
        			request.KeepAlive = false;
        			request.Timeout = 5000;
        			request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
        			request.Headers.Add(HttpRequestHeader.AcceptLanguage, "zh-CN,zh;q=0.9");
        			request.Headers.Add(HttpRequestHeader.AcceptEncoding, "br");
        			//request.Proxy = new WebProxy("127.0.0.1", 8888);//讓Fiddler抓包以查看實際發送出去的請求
        			var response = request.GetResponse() as HttpWebResponse;
        			if (response.StatusCode == HttpStatusCode.OK)
        			{
        				Stream rspStrm = response.GetResponseStream();
        				var reader = new StreamReader(rspStrm);
        				string rspText = reader.ReadToEnd();
        				Console.WriteLine(rspText);
        			}
        			Console.ReadKey();
        

        運行代碼后就可以在控制臺程序上打印出響應文本(很明顯,這是一個Json格式的文本):

        “推薦作者”響應文本

        讓我們回頭看一下這段代碼。如果讀者對Http有了基礎性認識的話,應該會覺得這樣的代碼很親切,無非就是復制粘貼,依次給類里的屬性賦值即可。這是抽象層次低的好處,非常方便理解。但這也意味著使用者要注意的地方變多了,例如:

        • Method屬性是一個String類型,如果不小心寫錯了,就會觸發異常。
        • GetResponse()方法返回的是一個父類——WebResponse類,每次都需要通過類型轉換變成HttpWebResponse對象。
        • 輸出文本并沒有相應的API,只能先通過HttpWebResponse對象的GetResponseStream()方法獲取Stream,然后通過StreamReader讀取出文本。
        • 示例代碼未啟用Cookie參數,如果啟用,需要自己創建并維護一個CookieContainer。
        • 包含很多不常用的屬性和配置,顯得臃腫、復雜。

        這還只是單純代碼層面而言。如果是嚴肅的項目代碼,還需要注意性能方面的問題,例如:

        • 單個HttpWebRequest默認只支持2個線程(.Net遵循了“Http協議規定,同個Http請求的并發連接數最大為2”),多線程環境下需要通過設置ServicePointManager類的DefaultConnectionLimit屬性來解決(對代碼的焦點產生了干擾)。
        • 每個請求都需要創建一個HttpWebRequest對象,過多的對象會導致資源來不及釋放直至端口耗盡。
        • 一些特殊場景下的性能問題(比如HttpWebRequest.GetRequestStreamAsync doesn't open the connection to the server - we always buffer the content #11873)。

        以及代碼之外的現實問題,例如:

        • .Net Framework和.Net Core的實現不太一樣了,可能會導致行為上有不同的結果(事實是,Github上.Net Core庫有不少與此相關的issues,比如HttpWebResponse.Cookies different behavior in .net framework and .net core #33122)。
        • 官方文檔網站Docs明確表示了“不建議使用HttpWebRequest進行新的開發”。
        來自官方文檔的注解

        可以說,對于.Net Core項目,HttpWebRequest/HttpWebResponse已經成為了“歷史遺產”。

        微軟工程師如是說

        不過,官方也是有兩手準備:Avoid creating a new HttpClient per HttpWebRequest in some cases #15460。未來可能會在.Net5(其實就是.Net Core 4.0,微軟改名部又發功了)又有一些優化工作。當然,作為一個新手來說,我們并不需要了解得那么詳細,以上只是為了說明:HttpWebRequest/HttpWebResponse的設計,作為一個基礎類庫來說,它僅僅滿足了讓你“能用”,卻還是“用起來不太舒服”。

        今生的HttpClient

        伴隨著時代發展,.Net重新整合設計了System.Net.Http.HttpClient(Java程序猿可能有話說:“這不是跟Java抄的嘛!”嗯……名字的確是一樣)。那么HttpClient又變成什么樣子了呢?

        HttpClient APIs from .Net Core 2.2

        對比一下“前世”,我們驚喜地發現HttpClient的API似乎更精簡了。它自帶了常用的Http請求方法,比如獲取百度首頁,我們現在可以寫成短短幾行代碼:

                                using (var client = new HttpClient())
        			{
        				//client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36");
        				string rspText = client.GetStringAsync("https://www.baidu.com").Result;//核心代碼就這么一行,注意這里為了示例簡單而未使用異步語法而是直接同步調用Result
        				Console.WriteLine(rspText);
        			}
        

        結合上圖,可以先簡單總結幾點改進:

        • HttpClient使用自帶的GetStringAsync()方法就可以獲取到響應文本,相應地還有GetStreamAsync()方法和GetAsync()方法,由淺入深,應有盡有,可以充分滿足不同場景的需求。
        • 同時很明顯的是,HttpClient的APIs全部是異步方法(且是線程安全的),這意味著HttpClient天生就是為高并發場景而設計。
        • 原本HttpWebRequest臃腫的各種屬性,現在分散到HttpMessageHandler/HttpRequestMessage/HttpResponseMessage/HttpContent中了,雖然對編程新手來說抽象的提高意味著更高的學習成本,但一旦理解就會發現這樣的設計使得代碼更精簡易讀、邏輯焦點更突出。

        現在我們嘗試加入代理:

                                var handler = new SocketsHttpHandler() { UseProxy = true, Proxy = new WebProxy("127.0.0.1", 8888), UseCookies = false, AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate };
        			using (var client = new HttpClient(handler))
        			{
        				client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36");
        				string rspText = client.GetStringAsync("https://www.baidu.com").Result;
        				Console.WriteLine(rspText);
        			}
        

        可以看到我們不需要去修改請求參數賦值的代碼塊,而是引入一個HttpMessageHandler的子類SocketsHttpHandler,替換HttpClient的重載構造方法即可。如果我們要存儲Cookie呢?只需要將UseCookie設為true即可——HttpClient在絕大多數情況下不需要手動維護Cookie。

        現在讓我們重寫上面簡書的例子:

        using System;
        using System.Net;
        using System.Net.Http;
        
        namespace ConsoleAppTest
        {
        	internal class Program
        	{
        		private static async void Main(string[] args)//.Net Core 2.1版本以上支持異步形式的Main方法
        		{
        			var handler = new SocketsHttpHandler() { UseProxy = true, Proxy = new WebProxy("127.0.0.1", 8888), UseCookies = false, AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate };
        			using (var client = new HttpClient(handler))
        			{
        				client.Timeout = TimeSpan.FromSeconds(5);
        				client.DefaultRequestHeaders.Host = "www.jianshu.com";
        				client.DefaultRequestHeaders.Referrer = new Uri("https://www.jianshu.com/");
        				client.DefaultRequestHeaders.Add("Accept", "application/json");
        				client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate, br");
        				client.DefaultRequestHeaders.Add("Accept-Language", "");
        				client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36");
        				string rspText = await client.GetStringAsync("https://www.jianshu.com/users/recommended?seen_ids=&count=5&only_unfollowed=true");
                                        Console.WriteLine(rspText);
                                        //也可以使用更底層且更靈活的方式,滿足不同的需求。如下:
                                        //HttpRequestMessage reqMsg = new HttpRequestMessage(HttpMethod.Get, "https://www.jianshu.com/users/recommended?seen_ids=&count=5&only_unfollowed=true");
        				//HttpResponseMessage rspMsg = await client.SendAsync(reqMsg);
        				//if(rspMsg.IsSuccessStatusCode)
        				//{
        					//string rspText = await rspMsg.Content.ReadAsStringAsync();
        					//Console.WriteLine(rspText);
        				//}
        			}
        			Console.ReadKey();
        		}
        	}
        }
        

        是否感覺這樣的代碼,結構層次更強,邏輯的焦點更明確?

        還有,我們經常會遇到一些類似翻頁的情況,還是以簡書首頁的“推薦作者”為例:

        簡書“推薦作者”

        當我們點擊換一批時,會發現請求變成了:

        “換一批”

        seen_ids記錄了我們看過的推薦作者id,所以我們需要之前所有請求得到的作者id,拼成新的URL再做新的請求。如果使用HttpWebRequest,那么每次都要生成新的對象,并為它賦值后再請求。而HttpClient推薦的用法是盡量使用單例,而此案例只需使用1個HttpClient,就足夠滿足我們的需求。參考上面注釋區域的代碼,我們只需要每次生成一個極輕量的HttpRequestMessage,通過SendAsync()方法依次請求即可,最大程度地復用了HttpClient的DefaultRequestHeaders部分。我想從這一點,就足以令讀者發現分離設計的好處。

        HttpClient的發展之路還遠遠沒有結束,它還在不斷地被.Net社區討論和優化。關于HttpClient更多的詳細信息,可以閱讀官方文檔的“注解”:

        HttpClient Class (System.Net.Http)?docs.microsoft.com圖標

        小結

        筆者僅以此文向讀者粗略介紹了.Net上Http基礎庫的基本用法——是的,以上還只是“粗略”、“基本”——但是起碼我們可以寫一些像樣的爬蟲了。爬到了數據,我們就可以嘗試挖掘數據的價值,就可以像有些文章作者一樣,貼出各種好看的圖表來炫耀自己的所得了。希望這種獲得和挖掘的樂趣能夠不斷驅動著讀者們的技術人生,讓我們下期再會!

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