zoukankan      html  css  js  c++  java
  • 《C# 爬虫 破境之道》:第一境 爬虫原理 — 第四节:同步与异步请求方式

    前两节,我们对WebRequest和WebResponse这两个类做了介绍,但两者还相对独立。本节,我们来说说如何将两者结合起来,方式有哪些,有什么不同。

    1.4.1 说结合,无非就是我们如何发送一个Request以及如何得到一个Response。

    WebRequest提供了三组方法(Framework 4.6.1+,其它版本没去细看)

    [Code 1.4.1]

    1          public virtual WebResponse GetResponse();
    2 ----------------------------------------------------------------------------------
    3          public virtual IAsyncResult BeginGetResponse(AsyncCallback callback, object state);
    4          public virtual WebResponse EndGetResponse(IAsyncResult asyncResult);
    5 ----------------------------------------------------------------------------------
    6          public virtual Task<WebResponse> GetResponseAsync();

    这三组方法,都是用来获取Response的,而且,WebRequest也是在调用它们的时候,发送出去的。

    这就是关于如何结合的全部内容,没有很复杂,复杂的内容在发送前的准备工作和接收后的处理工作:)

    1.4.2 说方式,无非就是同步和异步。

    还是看[Code 1.4.1],第1行,是同步方式,第3、4、6行是异步方式;不过除了这两组外,WebRequest还提供了对RequestStream获取的同步与异步操作方式:

    [Code 1.4.2]

    1         public virtual Stream GetRequestStream();
    2 ----------------------------------------------------------------------------------
    3         public virtual IAsyncResult BeginGetRequestStream(AsyncCallback callback, object state);
    4         public virtual Stream EndGetRequestStream(IAsyncResult asyncResult);
    5 ----------------------------------------------------------------------------------
    6         public virtual Task<Stream> GetRequestStreamAsync();

    RequestStream是做什么用的?

    举个栗子,我是个俊才,要向我心仪的姑娘提亲,于是呢,就要发起一个WebRequest了,Uri就相当于姑娘家的详细地址,甚至姑娘家有多位姑娘,我要向哪位姑娘提亲呢,也可以在Uri中点明。但是,既然是提亲,总得准备点儿礼物吧,可是礼物要么数量多(Uri里装不下)、要么珍贵,不想让外人看到,怎么办,于是乎,就有了RequestStream的用武之地,它就像是提亲的随行车队,我把礼物藏到车队里,这样我就可以给礼物编个码、压个缩、打个包、加个密、装个箱、上个锁(好像成本比礼物本身都高了,谁让咱穷呢)。

    那为什么需要异步的方式呢,还是拿上面的栗子来解释吧,车队虽好,不过我可没有那么多车,要么跟亲戚朋友借,要么找婚庆公司预约,同步的方式就是我亲自去借车,其它什么都不干了,就等着车队到齐(先不考虑别人不借给我的尴尬场面)。异步方式呢,就是我打电话,告诉各位,我要借车,赶紧把车开过来,撂下电话,我也不闲着,赶紧给礼物编个码、压个缩、打个包、加个密、装个箱、上个锁……等车来齐了,就可以装车出发了~

    好,理呢,就是这么个理,开始动手干。

    先拿同步方式开刀。

     1     using System;
     2     using System.IO;
     3     using System.Net;
     4     using System.Text;
     5 
     6     class Program
     7     {
     8         static void Main(string[] args)
     9         {
    10             var request = WebRequest.Create(@"https://www.cnblogs.com/mikecheers/p/12090487.html");
    11             request.Method = WebRequestMethods.Http.Get;
    12             using (var response = request.GetResponse()) // 结合点,同步方式获取Response
    13             {
    14                 using (var stream = response.GetResponseStream())
    15                 {
    16                     using (var reader = new StreamReader(stream, new UTF8Encoding(false)))
    17                     {
    18                         var content = reader.ReadToEnd();
    19                         Console.WriteLine(content);
    20                     }
    21                 }
    22                 response.Close();
    23             }
    24             request.Abort();
    25             Console.ReadLine();
    26         }
    27     }

    细心的同学,会发现,这不就是第二节开篇的示例麽。没错,就是它,你能拿我怎么滴……

    重点是异步怎么实现:P

    为了能够同时做一些对比,对上边的同步方式也做了一点改造,对比的步骤是每种方式,执行一百次请求发送,统计一下平均每请求消耗时间;虽然意义不大,只能看个大概,当玩儿了吧:)

     1 {
     2     Stopwatch watch = new Stopwatch();
     3     Console.WriteLine("/* ********************** 同步请求方式 * GetResponse() **********************/");
     4     watch.Restart();
     5     for (int i = 0; i < 100; i++)
     6     {
     7         var request = WebRequest.Create(@"https://www.cnblogs.com/mikecheers/p/12090487.html");
     8         request.Method = WebRequestMethods.Http.Get;
     9         using (var response = request.GetResponse())
    10         {
    11             using (var stream = response.GetResponseStream())
    12             {
    13                 using (var reader = new StreamReader(stream, new UTF8Encoding(false)))
    14                 {
    15                     var content = reader.ReadToEnd();
    16                     Console.WriteLine(content.Length > 100 ? content.Substring(0, 90) + " ..." : content);
    17                 }
    18             }
    19             response.Close();
    20         }
    21         request.Abort();
    22     }
    23     watch.Stop();
    24     Console.WriteLine("/* ********************** using {0}ms / request  ******************** */"
    25         + Environment.NewLine + Environment.NewLine, (watch.Elapsed.TotalMilliseconds / 100).ToString("000.00"));
    26 }   /* ********************** using 034.10ms / request  ******************** */
    同步请求方式 * GetResponse()
     1 {
     2     int sum = 100;
     3     Stopwatch watch = new Stopwatch();
     4     Console.WriteLine("/* ********** 异步请求方式 * BeginGetResponse() & EndGetResponse() **********/");
     5     watch.Start();
     6     for (int i = 0; i < 100; i++)
     7     {
     8         var request = WebRequest.Create(@"https://www.cnblogs.com/mikecheers/p/12090487.html");
     9         request.Method = WebRequestMethods.Http.Get;
    10         request.BeginGetResponse(new AsyncCallback(ar =>
    11         {
    12             using (var response = (ar.AsyncState as WebRequest).EndGetResponse(ar))
    13             {
    14                 using (var stream = response.GetResponseStream())
    15                 {
    16                     using (var reader = new StreamReader(stream, new UTF8Encoding(false)))
    17                     {
    18                         var content = reader.ReadToEnd();
    19                         Console.WriteLine(content.Length > 100 ? content.Substring(0, 90) + " ..." : content);
    20                     }
    21                 }
    22                 response.Close();
    23             }
    24             if (0 == System.Threading.Interlocked.Decrement(ref sum))
    25             {
    26                 watch.Stop();
    27                 Console.WriteLine("/* ********************** using {0}ms / request  ******************** */"
    28                     + Environment.NewLine + Environment.NewLine, (watch.Elapsed.TotalMilliseconds / 100).ToString("000.00"));
    29             }
    30         }), request);
    31     }
    32 }   /* ********************** using 008.45ms / request  ******************** */
    异步请求方式 * BeginGetResponse() & EndGetResponse()
     1 {
     2     Stopwatch watch = new Stopwatch();
     3     Console.WriteLine("/* ******************* 异步请求方式 * GetResponseAsync() ****************** */");
     4     watch.Start();
     5     System.Threading.Tasks.Task[] tasks = new System.Threading.Tasks.Task[100];
     6     for (int i = 0; i < tasks.Length; i++)
     7     {
     8         var request = WebRequest.Create(@"https://www.cnblogs.com/mikecheers/p/12090487.html");
     9         request.Method = WebRequestMethods.Http.Get;
    10         tasks[i] = request.GetResponseAsync().ContinueWith(t =>
    11         {
    12             var response = t.Result;
    13             using (var stream = response.GetResponseStream())
    14             {
    15                 using (var reader = new StreamReader(stream, new UTF8Encoding(false)))
    16                 {
    17                     var content = reader.ReadToEnd();
    18                     Console.WriteLine(content.Length > 100 ? content.Substring(0, 90) + " ..." : content);
    19                 }
    20             }
    21             response.Close();
    22         });
    23     }
    24 
    25     System.Threading.Tasks.Task.WaitAll(tasks);
    26 
    27     watch.Stop();
    28     Console.WriteLine("/* ********************** using {0}ms / request  ******************** */"
    29         + Environment.NewLine + Environment.NewLine, (watch.Elapsed.TotalMilliseconds / 100).ToString("000.00"));
    30 }   /* ********************** using 010.06ms / request  ******************** */
    异步请求方式 * GetResponseAsync()

    为了能让代码尽量简短一些,撒了一些糖,有不明所以的同学,可以去找找Lambda表达式相关的东东,不是本文的重点。

    本人也是经过多轮测试,发现第二种方式略优一些。

    前面为什么说这个耗时的对比,意义不大?这里从大的方面稍微解释一下,有几个因素在里面:

    1. 服务器性能导致响应时间不固定。目标服务器也有忙有闲,处理每一个请求的时间是不固定的,所以,即使使用相同的方式,网络零消耗的情况下,每请求的时间也是不固定的,甚至会有很大差异;
    2. 缓存导致响应时间不固定。当目标服务器和/或客户端使用缓存技术,缓存有增加的那一刻,也有减少的那一刻,所以,即使使用相同的方式,网络零消耗的情况下,每请求的时间也是不固定的,甚至会有很大差异;
    3. 网络环境导致响应时间不固定。网络资源有限,时忙时闲,所以,即使使用相同的方式,每请求的时间也是不固定的,甚至会有很大差异;(路由学习导致改道、交换机产生回环、网络丢包等因素,都有存在的可能。)
    4. 客户端机器性能导致响应时间不固定。刨除以上几点差异,在不同的设备上,机器对I/O的处理能力也是有差别的,所以,即使使用相同的方式,每请求的时间也是不固定的,甚至会有很大差异;

    那么,有没有最好的方式?没有!条件不固定,一切尚未可知!那么,有没有推荐的方式,有!第二种,异步方式。

    看以上的问题列表,目标服务器的性能问题、缓存问题、网络问题,都是基本不可控的,(有同学说他的都可控,服务器就在自己家,网络什么的随便改造,那么建议你用[复制]+[粘贴]:P,就不要捣乱了)。唯一可控的就是对客户端(也就是运行爬虫的机器)的把控,这里的把控也不是说完全能够随意造,毕竟也都是银子,是说,我们能够很清楚的了解客户端的性能、瓶颈、优势劣势,我们不能改变,那么就可以想办法去适应,让他的性能最优化。客户端上是不是还跑着其他重要的任务?内存压力?单U还是多U?CPU的处理能力和网卡处理能力是不是均衡?网卡的吞吐能力?等等,都将影响我们的很多决策。这里说爬虫,也影射很多其他系统的开发。

    我们不能说哪种方式最好,只能是选一个比较折中的方式。做人留一线,曰后好相奸~

    这节就到这里吧,主要是了解一下几种请求的方式。

    下一节,打算趁热打铁,聊一聊数据流那些事儿~敬请期待~

    喜欢本系列丛书的朋友,可以点击链接加入QQ交流群(994761602)【C# 破境之道】
    方便各位在有疑问的时候可以及时给我个反馈。同时,也算是给各位志同道合的朋友提供一个交流的平台。
    需要源码的童鞋,也可以在群文件中获取最新源代码。

  • 相关阅读:
    山寨 《寻找房祖名》
    css3 弹性效果上下翻转demo
    CSS3 Hover 动画特效
    判断一个字符串通过变化字符的位置,是否可以组成回文
    获取多个字符串中的共同字符
    转换为回文的步数
    IOS中图片的一些处理方法
    python django的一点笔记
    一个图片切割的例子
    一个批量修改文件夹中文件名的命令
  • 原文地址:https://www.cnblogs.com/mikecheers/p/12144790.html
Copyright © 2011-2022 走看看