《C# 爬虫 破境之道》:第二境 爬虫应用 — 第一节:HTTP协议数据采集

首先欢迎您来到本书的第二境,本境,我们将全力打造一个实际生产环境可用的爬虫应用了。虽然只是刚开始,虽然路漫漫其修远,不过还是有点小鸡冻:P

本境打算针对几大派生类做进一步深耕,包括与应用的结合、对比它们之间的区别、综合共性、封装。One-By-One。

第一节,我们先来说说最重要、最常用的HttpWebRequest。撸码的细节就不说了,看不懂的童鞋可以去看看第一境就明白了:)

示例解决方案分为两个项目,一个应用程序(控制台),一个类库(取了个高大上的名字:System.Crawler)。所以,在应用程序中需要引用System.Crawler。应用程序可以是控制台、窗体项目、Win服务、WPF等,随便整,随你随地大小便……

先展示一下应用程序的最终的效果,然后我们再逐步引出类库的实现。

[Code 2.1.1]

 1 using System.Crawler;
 2
 3 {
 4     #region 以GET方式请求数据
 5     var ant = new WorkerAnt
 6     {
 7         WorkerId = (uint)Math.Abs(DateTime.Now.ToString("HHmmssfff").GetHashCode()),
 8     };
 9     var job = new JobContext
10     {
11         JobName = "Mike test job 1",
12         Uri = @"https://www.cnblogs.com/mikecheers/p/12090487.html",
13     };
14     ant.Work(job);
15     #endregion
16
17     #region 以POST方式请求数据
18     var requestDataBuilder = new StringBuilder();
19     requestDataBuilder.AppendLine("using System;");
20     requestDataBuilder.AppendLine("namespace HelloWorldApplication");
21     requestDataBuilder.AppendLine("{");
22     requestDataBuilder.AppendLine("    class HelloWorld");
23     requestDataBuilder.AppendLine("    {");
24     requestDataBuilder.AppendLine("        static void Main(string[] args)");
25     requestDataBuilder.AppendLine("        {");
26     requestDataBuilder.AppendLine("            Console.WriteLine(\"《C# 爬虫 破境之道》\");");
27     requestDataBuilder.AppendLine("        }");
28     requestDataBuilder.AppendLine("    }");
29     requestDataBuilder.AppendLine("}");
30
31     var requestData = Encoding.UTF8.GetBytes(
32         @"code=" + System.Web.HttpUtility.UrlEncode(requestDataBuilder.ToString())
33         + @"&token=4381fe197827ec87cbac9552f14ec62a&language=10&fileext=cs");
34
35     new WorkerAnt
36     {
37         WorkerId = (uint)Math.Abs(DateTime.Now.ToString("HHmmssfff").GetHashCode())
38     }.Work(new JobContext
39     {
40         JobName = "Mike test job 2",
41         Uri = @"https://tool.runoob.com/compile.php",
42         ContentType = @"application/x-www-form-urlencoded; charset=UTF-8",
43         Method = WebRequestMethods.Http.Post,
44         Buffer = requestData,
45     });
46     #endregion
47
48     Console.WriteLine("End of Main method.");
49     Console.ReadLine();
50 }

最终效果:应用代码部分

[Code 2.1.2]

Worker 471365603 JobStatus: WaitingForActivation
Worker 471365603 is starting a job named ‘Mike test job 1‘.
Worker 471365603 JobStatus: WaitingToRun
Worker 471365603 JobStatus: Running
Worker 1110506678 JobStatus: WaitingForActivation
Worker 1110506678 is starting a job named ‘Mike test job 2‘.
Worker 1110506678 JobStatus: WaitingToRun
Worker 1110506678 JobStatus: Running
End of Main method.
Totally 0 downloaded.
Totally 512 downloaded.
Totally 1024 downloaded.
Totally 1536 downloaded.
Totally 2048 downloaded.
Totally 2560 downloaded.
Totally 2624 downloaded.
Totally 3136 downloaded.
Totally 3648 downloaded.
Totally 4024 downloaded.
Totally 4028 downloaded.
Totally 4540 downloaded.
Totally 5052 downloaded.
Totally 5422 downloaded.
Totally 5934 downloaded.
Totally 6446 downloaded.
Totally 6822 downloaded.
Totally 7334 downloaded.
Totally 7846 downloaded.
Totally 8222 downloaded.
Totally 8734 downloaded.
Totally 9246 downloaded.
Totally 9758 downloaded.
Totally 10270 downloaded.
Totally 10782 downloaded.
Totally 10886 downloaded.

<!DOCTYPE html>
<html lang="zh-cn">
<head>
    <meta charset="utf-8" />
    <meta name="v...
/* ********************** using 000.75ms / request  ******************** */

Worker 471365603 JobStatus: RanToCompletion
Totally 0 downloaded.
Totally 81 downloaded.
{"output":"\u300aC# \u722c\u866b \u7834\u5883\u4e4b\u9053\u300b\n","errors":"\n"}
/* ********************** using 012.32ms / request  ******************** */

Worker 1110506678 JobStatus: RanToCompletion

最终效果:应用输出显示

在[Code 2.1.1]中,主要涉及两个类:

  • WorkerAnt(工蚁):在我的爬虫世界里,工蚁就是最小的工作单位了,它们勤勤恳恳,辛勤劳作,任劳任怨,颇为辛苦!致敬!
  • JobContext(任务上下文):主要承载了任务的描述(参数信息),任务的状态信息,一个计时器(统计任务消耗的时间),还有Request相关的对象、缓存等。

首先,我生产了两只小工蚁,并为他们分配了不同的任务,一个以GET方式采集数据,另一个以POST方式采集数据。工蚁提供了一个Work方法,用来向小工蚁发号施令,开工喽。

在[Code 2.1.2]中,我们可以跟踪到每只小工蚁的运行状态,采集到的数据以及耗时。同时也可以看到“End of Main method.”出现在比较靠前的位置,这说明我们的小工蚁还是有点儿小聪明的,可以采用异步的方式采集数据。

好了,应用的部分比较简单,相信大家都能够理解,接下来,我们主要介绍一下类库中的两个类(工蚁和任务上下文)。

[Code 2.1.3]

  1 namespace System.Crawler
  2 {
  3     using System;
  4     using System.Diagnostics;
  5     using System.IO;
  6     using System.Net;
  7     using System.Security.Cryptography.X509Certificates;
  8     using System.Text;
  9     using System.Threading.Tasks;
 10
 11     public class JobContext
 12     {
 13         /// <summary>
 14         /// 任务名称
 15         /// </summary>
 16         public String JobName { get; set; }
 17         /// <summary>
 18         /// 任务状态
 19         /// </summary>
 20         public TaskStatus JobStatus { get; set; }
 21         /// <summary>
 22         /// 跑表,计时器。
 23         /// </summary>
 24         public Stopwatch Watch { get; set; }
 25
 26         public WebRequest Request { get; set; }
 27         public WebResponse Response { get; set; }
 28         public Stream RequestStream { get; set; }
 29         public Stream ResponseStream { get; set; }
 30         public MemoryStream Memory { get; set; }
 31         public byte[] Buffer { get; set; }
 32
 33         /// <summary>
 34         /// 请求的目标Uri
 35         /// </summary>
 36         public String Uri { get; set; }
 37
 38         /// <summary>
 39         /// 设置509证书集合
 40         /// </summary>
 41         public X509CertificateCollection ClientCertificates { get; set; }
 42         /// <summary>
 43         /// Headers
 44         /// </summary>
 45         public WebHeaderCollection Headers { get; set; }
 46         /// <summary>
 47         /// 代理
 48         /// </summary>
 49         public IWebProxy Proxy { get; set; }
 50         /// <summary>
 51         /// 权限认证信息
 52         /// </summary>
 53         public ICredentials Credentials { get; set; }
 54
 55         /// <summary>
 56         /// 获取或设置用于请求的 HTTP 版本。返回结果:用于请求的 HTTP 版本。默认为 System.Net.HttpVersion.Version11。
 57         /// </summary>
 58         public Version ProtocolVersion { get; set; }
 59
 60         /// <summary>
 61         /// 获取或设置一个 System.Boolean 值,该值确定是否使用 100-Continue 行为。如果 POST 请求需要 100-Continue 响应,则为 true;否则为 false。默认值为 true。
 62         /// </summary>
 63         public bool Expect100Continue { get; set; }
 64
 65         /// <summary>
 66         /// 设置Request请求方式
 67         /// </summary>
 68         public String Method { get; set; }
 69
 70         // Summary:
 71         //     Gets or sets the time-out value in milliseconds for the System.Net.HttpWebRequest.GetResponse()
 72         //     and System.Net.HttpWebRequest.GetRequestStream() methods.
 73         //
 74         // Returns:
 75         //     The number of milliseconds to wait before the request times out. The default
 76         //     value is 100,000 milliseconds (100 seconds).
 77         //
 78         // Exceptions:
 79         //   System.ArgumentOutOfRangeException:
 80         //     The value specified is less than zero and is not System.Threading.Timeout.Infinite.
 81         public TimeSpan Timeout { get; set; }
 82
 83         // Summary:
 84         //     Gets or sets a time-out in milliseconds when writing to or reading from a
 85         //     stream.
 86         //
 87         // Returns:
 88         //     The number of milliseconds before the writing or reading times out. The default
 89         //     value is 300,000 milliseconds (5 minutes).
 90         //
 91         // Exceptions:
 92         //   System.InvalidOperationException:
 93         //     The request has already been sent.
 94         //
 95         //   System.ArgumentOutOfRangeException:
 96         //     The value specified for a set operation is less than or equal to zero and
 97         //     is not equal to System.Threading.Timeout.Infinite
 98         public TimeSpan ReadWriteTimeout { get; set; }
 99
100         // Summary:
101         //     Gets or sets the value of the Accept HTTP header.
102         //
103         // Returns:
104         //     The value of the Accept HTTP header. The default value is null.
105         public string Accept { get; set; }
106
107         // Summary:
108         //     Gets or sets the value of the Content-type HTTP header.
109         //
110         // Returns:
111         //     The value of the Content-type HTTP header. The default value is null.
112         public string ContentType { get; set; }
113
114         // Summary:
115         //     Gets or sets the value of the User-agent HTTP header.
116         //
117         // Returns:
118         //     The value of the User-agent HTTP header. The default value is null.NoteThe
119         //     value for this property is stored in System.Net.WebHeaderCollection. If WebHeaderCollection
120         //     is set, the property value is lost.
121         public string UserAgent { get; set; }
122
123         /// <summary>
124         /// 返回数据编码默认为NUll,可以自动识别,一般为utf-8,gbk,gb2312
125         /// </summary>
126         public Encoding Encoding { get; set; }
127
128         /// <summary>
129         /// 请求时的Cookie
130         /// </summary>
131         public string Cookie { get; set; }
132
133         public CookieCollection Cookies { get; set; }
134
135         /// <summary>
136         /// 来源地址
137         /// </summary>
138         public string Referer { get; set; }
139
140         /// <summary>
141         /// 是否允许自动跳转
142         /// </summary>
143         public bool AllowAutoRedirect { get; set; }
144
145         /// <summary>
146         /// 最大连接数
147         /// </summary>
148         public int ConnectionLimit { get; set; }
149
150         public JobContext()
151         {
152             Uri = null;
153             ClientCertificates = null;
154             Headers = null;
155             Proxy = null;
156             ProtocolVersion = System.Net.HttpVersion.Version11;
157             Expect100Continue = true;
158             Method = WebRequestMethods.Http.Get;
159             Timeout = TimeSpan.FromSeconds(100);
160             ReadWriteTimeout = TimeSpan.FromMinutes(5);
161             Accept = null;
162             ContentType = null;
163             UserAgent = null;
164             Encoding = null;
165             Cookie = null;
166             Cookies = null;
167             Referer = null;
168             AllowAutoRedirect = true;
169             ConnectionLimit = 100;
170             Credentials = null;
171         }
172     }
173 }

任务上下文(JobContext)

这个类中主要汇集了任务描述相关的信息属性们、异步操作相关的属性们、以及构建一个HttpWebRequest所需的所有属性们;并且在构造函数中,给出了部分属性的默认值,我们也可以在构造中,自动生成WorkerId和/或JobName等。

虽然属性繁多,但没有什么逻辑,还好还好~

[Code 2.1.4]

  1 namespace System.Crawler
  2 {
  3     using System;
  4     using System.Diagnostics;
  5     using System.IO;
  6     using System.Net;
  7     using System.Net.Security;
  8     using System.Security.Cryptography.X509Certificates;
  9     using System.Text;
 10     using System.Threading.Tasks;
 11
 12     /// <summary>
 13     /// 一个爬虫的最小任务单位,一只小工蚁。
 14     /// </summary>
 15     public class WorkerAnt
 16     {
 17         public UInt32 WorkerId { get; set; }
 18
 19         public void Work(JobContext context)
 20         {
 21             Console.WriteLine($"Worker { WorkerId } JobStatus: " + (context.JobStatus = TaskStatus.WaitingForActivation).ToString());
 22
 23             if (null == context)
 24                 throw new ArgumentNullException($"Worker { WorkerId } can not start a job with no context.");
 25
 26             if (null == context.Method)
 27                 throw new ArgumentNullException($"Worker { WorkerId } can not start a job with no method.");
 28
 29             if (null == context.Uri || !Uri.IsWellFormedUriString(context.Uri, UriKind.RelativeOrAbsolute))
 30                 throw new FormatException($"Worker { WorkerId } can not start a job with uri ‘{context.Uri}‘ is not well formed.");
 31
 32             if (string.IsNullOrEmpty(context.JobName))
 33                 Trace.WriteLine($"Worker {WorkerId} is starting a job with no name.");
 34             else
 35                 Trace.WriteLine($"Worker {WorkerId} is starting a job named ‘{context.JobName}‘.");
 36
 37             Console.WriteLine($"Worker { WorkerId } JobStatus: " + (context.JobStatus = TaskStatus.WaitingToRun).ToString());
 38             context.Watch = new Stopwatch();
 39             context.Watch.Start();
 40
 41             //这一句一定要写在创建连接的前面。使用回调的方法进行证书验证。
 42             if (null != context.ClientCertificates && 0 < context.ClientCertificates.Count)
 43                 ServicePointManager.ServerCertificateValidationCallback =  (sender, certificate, chain, errors) => true;
 44
 45             var Request = (context.Request = WebRequest.CreateHttp(context.Uri)) as HttpWebRequest;
 46
 47             if (null != context.ClientCertificates && 0 < context.ClientCertificates.Count)
 48                 foreach (X509Certificate item in context.ClientCertificates)
 49                     Request.ClientCertificates.Add(item);
 50
 51             if (null != context.Headers && context.Headers.Count > 0)
 52                 Request.Headers.Add(context.Headers);
 53
 54             Request.Proxy = context.Proxy;
 55
 56             if (null != context.ProtocolVersion)
 57                 Request.ProtocolVersion = context.ProtocolVersion;
 58
 59             Request.ServicePoint.Expect100Continue = context.Expect100Continue;
 60
 61             Request.Method = context.Method;
 62
 63             Request.Timeout = (Int32)context.Timeout.TotalMilliseconds;
 64
 65             Request.ReadWriteTimeout = (Int32)context.ReadWriteTimeout.TotalMilliseconds;
 66
 67             Request.Accept = context.Accept;
 68
 69             Request.ContentType = context.ContentType;
 70
 71             Request.UserAgent = context.UserAgent;
 72
 73             if (!string.IsNullOrEmpty(context.Cookie))
 74                 Request.Headers[HttpRequestHeader.Cookie] = context.Cookie;
 75
 76             if (null != context.Cookies)
 77             {
 78                 Request.CookieContainer = new CookieContainer();
 79                 Request.CookieContainer.Add(context.Cookies);
 80             }
 81
 82             Request.Referer = context.Referer;
 83
 84             Request.AllowAutoRedirect = context.AllowAutoRedirect;
 85
 86             if (0 < context.ConnectionLimit)
 87                 Request.ServicePoint.ConnectionLimit = context.ConnectionLimit;
 88
 89             Console.WriteLine($"Worker { WorkerId } JobStatus: " + (context.JobStatus = TaskStatus.Running).ToString());
 90
 91             if (null != context.Buffer && 0 < context.Buffer.Length)
 92             {
 93                 Request.ContentLength = context.Buffer.Length;
 94                 Request.BeginGetRequestStream(acGetRequestStream =>
 95                 {
 96                     var contextGetRequestStream = acGetRequestStream.AsyncState as JobContext;
 97                     contextGetRequestStream.RequestStream = contextGetRequestStream.Request.EndGetRequestStream(acGetRequestStream);
 98                     contextGetRequestStream.RequestStream.BeginWrite(context.Buffer, 0, context.Buffer.Length, acWriteRequestStream =>
 99                     {
100                         var contextWriteRequestStream = acWriteRequestStream.AsyncState as JobContext;
101                         contextWriteRequestStream.RequestStream.EndWrite(acWriteRequestStream);
102                         contextWriteRequestStream.RequestStream.Close();
103                         GetResponse(contextWriteRequestStream);
104                     }, contextGetRequestStream);
105                 }, context);
106             }
107             else
108                 GetResponse(context);
109         }
110
111         private void GetResponse(JobContext context)
112         {
113             context.Request.BeginGetResponse(new AsyncCallback(acGetResponse =>
114             {
115                 var contextGetResponse = acGetResponse.AsyncState as JobContext;
116                 using (contextGetResponse.Response = contextGetResponse.Request.EndGetResponse(acGetResponse))
117                 using (contextGetResponse.ResponseStream = contextGetResponse.Response.GetResponseStream())
118                 using (contextGetResponse.Memory = new MemoryStream())
119                 {
120                     var readCount = 0;
121                     if (null == contextGetResponse.Buffer) contextGetResponse.Buffer = new byte[512];
122                     IAsyncResult ar = null;
123                     do
124                     {
125                         if (0 < readCount) contextGetResponse.Memory.Write(contextGetResponse.Buffer, 0, readCount);
126                         ar = contextGetResponse.ResponseStream.BeginRead(
127                             contextGetResponse.Buffer, 0, contextGetResponse.Buffer.Length, null, contextGetResponse);
128                         Console.WriteLine($"Totally {contextGetResponse.Memory.Length} downloaded.");
129                     } while (0 < (readCount = contextGetResponse.ResponseStream.EndRead(ar)));
130
131                     contextGetResponse.Request.Abort();
132                     contextGetResponse.Response.Close();
133                     contextGetResponse.Buffer = null;
134
135                     var content = new UTF8Encoding(false).GetString(contextGetResponse.Memory.ToArray());
136                     Console.WriteLine(content.Length > 100 ? content.Substring(0, 90) + "..." : content);
137
138                     contextGetResponse.Watch.Stop();
139                     Console.WriteLine("/* ********************** using {0}ms / request  ******************** */"
140                         + Environment.NewLine + Environment.NewLine, (contextGetResponse.Watch.Elapsed.TotalMilliseconds / 100).ToString("000.00"));
141                     Console.WriteLine($"Worker { WorkerId } JobStatus: " + (contextGetResponse.JobStatus = TaskStatus.RanToCompletion).ToString());
142                 }
143             }), context);
144         }
145     }
146 }

工蚁(WorkerAnt)

重点就是小工蚁类了,这个类一共提供了一个属性,一个公有方法,一个私有方法,没有提供构造函数,将使用默认构造函数,我们来详细解释一下:

  • WorkerId:给每只小工蚁分配一个编号,就像人的身份证一样,没准等它老了,也能凭此编号领个社保什么的……
  • Work(JobContext context):对小工蚁发号施令,要它去完成某采集任务。
  • GetResponse(JobContext context):处理收到的回复数据。

属性没什么好说的了,主要说两个方法。

Work(JobContext context)方法

在第21、37、89行,分别重置了context.JobStatus的状态,其实,每次重置状态,都寓意着流程上的层次划分,这里借用了枚举System.Threading.Tasks.TaskStatus来做状态标记位,可能在过去或将来某些Framework中将不支持,我们也可以定义自己的状态标记位,使得意义更加精确;

在第23~35行,对JobContext做了简单的校验,为了篇幅短小一点,这里并没有列出所有的校验规则,可以根据应用的实际情况来进行校验,示例中演示了如何检查空对象以及可选对象,当然还可以加入一些逻辑校验,比如:

  • 当JobContext.Method="GET"的时候,就不能指定JobContext.Buffer,否则会抛出异常;
  • 提供了JobContext.Credentials对象,但对象的属性是否合法;
  • 提供了JobContext.Method,但它的值是否有效;
  • 提供了JobContext.Headers,但其值是否可以加入到Request.Headers中,因为有些Key是只能通过Request的属性指定的,请参考:WebHeaderCollection.IsRestricted 方法

在第38、39行,开启跑表,到这里,就要开始构建Request对象,并对对象的属性进行赋值了。

在第41~87行,就是属性的赋值操作,不一一讲解了,大的原则就是判断JobContext中是否提供了相应的属性值,如果提供了,那么就赋值;

这里有几个需要注意的属性,说一下:

  • ServicePointManager.ServerCertificateValidationCallback = (sender, certificate, chain, errors) => true; :这一句一定要写在创建连接的前面。使用回调的方法进行证书验证。作为爬虫,我们就不要为难自己去验证证书了,但如果我们做的是浏览器程序,那么,还是守点儿规矩吧:)
  • Request.Proxy = context.Proxy; :Proxy属性比较特殊,当我们Create一个Request后,这个属性的默认值并不是Null,这也导致我们在使用过程中,总感觉第一个请求的耗时很长,后面的请求就很快。作为优化的一种方式,就是不去判断JobContext.Proxy是不是Null,都给这个属性赋值,即使JobContext.Proxy是Null,也要明确地给Request.Proxy赋值。
  • Request.ProtocolVersion = context.ProtocolVersion; :ProtocolVersion属性的类型是Version,但在赋值的时候,不要自己随便new一个Version,而是使用System.Net.HttpVersion进行赋值,参考JobContext的构造函数。目前支持的值有Version10和Version11,分别对应HTTP 1.0和HTTP 1.1。
  • Request.Timeout与Request.ReadWriteTimeout的区别在于:Timeout是用来限制GetResponse和GetRequestStream的超时时间,通俗点理解就是Timeout是单次采集的整体(包括Socket.Connect()、Request和Response、读写流)超时时间,默认值是100秒,通常我们会把这个时间设置的短一点,比如5~8秒,但需要注意的是,MSDN上说它只对同步操作有效,异步操作是不起作用的,所以,JobContext中的初始值并没有修改,还是保留了原来的初始值,如果你的小虫子是同步方式工作的,那么就需要注意它们的意义了;ReadWriteTimeout则是限制实际数据流读写操作的时间,同理,也是同步有效,异步无效。
  • Request.ContinueTimeout:这个属性是在Framework 4.5加入的,它表示在接收到 100-Continue 之前要等待的超时(以毫秒为单位),如果在超时过期之前接收到 100-Continue 响应,则可以发送实体正文。如果我们配置了Request.ServicePoint.Expect100Continue = true,这个timeout就会起作用。因为100Continue的报文很小,所以这个超时的默认时间也很短,仅为350毫秒。这也算是Http协议的优化吧,本节结尾会贴一小段100Continue的报文,让大家有个直观的赶脚:)
  • 还有示例中没有列举的其他属性,包括在新版本中加入的属性,大家可以参考MSDN文档的相关主题。也可以直接在VS中跳转到HttpWebRequest的定义,了解您当前版本适用的属性、方法。

在第91~108行,这里要真正开启采集工作,请求数据了。分了两条路走,一条是有PostData的,另一条是没有PostData的。有PostData的,就会先获取RequestStream,把数据写进去,注意,写完数据要关闭RequestStream,比第一境中示例的位置提前了。之后两条路都汇总到了GetResponse(JobContext)方法去获取回复信息了。

这样,发送请求的处理就完成了,接下来我们看最后一个方法,处理回复。

GetResponse(JobContext context)方法:

由于是私有方法,入口点只有Work方法,同时也是为了文章代码稍微短一点,我就没有再做参数校验了,如果以后扩展了,使用的地方多了,进行参数校验还是要做的。

这个方法与第一境中处理回复的部分没有大的变动,就不细说了,只是在第141行增加了一个设置任务状态的处理,标示任务处理完成了。

异常的处理:

在本示例中,并没有对异常进行捕获和处理,就像我将这个程序集命名为System.Crawler一样,我希望它能像System.xxx一样处理异常(就是不处理:),为什么这样设计呢,因为作为通用框架、组件、控件,本身并不知道一旦发生异常,应该怎么处理,是重试?还是默不作声?还是记录个日志了事?可能都不是业务逻辑所希望看到的。业务逻辑应该有自己的流程流转,应该有处理错误的能力,甚至是业务逻辑本身出错了,收到异常会帮助业务逻辑更加完善。

当然,示例中的应用也没有捕获异常,是我的不对,就是为了篇幅短小:)

另外就是附上说好的100 Continue报文:(通过Wireshar捕获)

[Code 2.1.5]

>>> 1. 发送HTTP报头,包含Expect: 100-continue ----------------------------------------------
POST /xxxxxxxxxxxxxx.ashx HTTP/1.1
Content-Type: application/x-www-form-urlencoded
encode_key: Ha1P29PAhyzRRmBiBkTJ6Q==
Host: 192.168.216.22:9080
Content-Length: 480
Expect: 100-continue
Connection: Keep-Alive

<<< 2. 收到100Continue ----------------------------------------------
HTTP/1.1 100 Continue
Content-Length: 0
Date: Sat, 21 Sep 2019 01:27:18 GMT
Server: WebSphere Application Server/8.0

>>> 3. 发送请求的数据实体 ----------------------------------------------
LloRmU0xleMjr8VibuqgDUvL9++cFpBDwtRt89fbWw2UsHjS1+cCPVmn0t9y4NysUZXAYIAlS5odowFdI/h5HAsSNk7jjaVsEK9dFseNfN+TaIIlwagFwvEEZ6tjZ0pF90hmq90iiHzH5ylDjuSfC3OJUpPrDEfAogcq/nRe8TwVRtVVSZ20RH5o0hDc/ibMSOBI/qVW+c1Ala2xfknQHi5RRGXSd3NauL9Bd0Oxk4lDIbGcWxVByoU9oZCeB8in4KdbjQtiHebigTRNiyS6lglZXY482ArxRq2Gourld/9F/gFhSCExiiBGkfwy6nzmdB66/JxBk4GYiO9fEfjamQAt3hPs8cE7zEDnPN25dVvpwhP66e3c81aUigOi6+P6634CyoSjMqyivy5p9SJsdFLeZueqH7QhZUAkR4+o4lyHVcdfs2FXlZnl23AWyBEMlcrwwzuGEYzLJqzkoxWVJ9KJP5qRbjQM

<<< 4. 收到 Response ----------------------------------------------
HTTP/1.1 200 OK
Content-Language: zh-CN
Set-Cookie: JSESSIONID=0000Hc6wSQjAvFXM1m2GbqKaRSE:-1; Path=/; HttpOnly
Transfer-Encoding: chunked
Date: Sat, 21 Sep 2019 01:27:18 GMT
Server: WebSphere Application Server/8.0
Expires: Thu, 01 Dec 1994 16:00:00 GMT
Cache-Control: no-cache="set-cookie, set-cookie2"

168
1279tozwn5CTZnLt1r2pAYHxJ8HES8K0Sc0yhi5O2Tsk+/uZLPRraJlU9mqe/m6NwKRaWYraQmdz
oWGsKdAgWFge5tGXlr1mvQCZO4/fWXFxM117snEnBm5bfwB9Zq+NOiF2E3L1WmT2Ooet40WAvMoR
3ZznhdI5Fm6gS0H7nLaYeujOlzc/lZWIl29HQHdHbnIWqqxIXbvdb9wXIycgHwAecFNtAWT7iS9H
BQcqajo5he2h1ehDn/kJns9YMwCWVDQ7iQW/tqqlRzxFhpaAaHQXT+fZK/nhFbomFwdAekz32M6t
4qnHFXBsU6ABX50+bCj+QZ/e4t1M6On/nJXyQoytQwfKFJWt
0

100 Continue 参考报文

好了,关于HTTP部分的解毒,到这里就告一段落了,欢迎大家在评论区或加入下面的QQ群与笔者进行交流。

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

原文地址:https://www.cnblogs.com/mikecheers/p/12190160.html

时间: 2024-10-06 18:21:51

《C# 爬虫 破境之道》:第二境 爬虫应用 — 第一节:HTTP协议数据采集的相关文章

《C# 爬虫 破境之道》:概述

第一节:写作本书的目的 关于笔者 张晓亭(Mike Cheers),1982年出生,内蒙古辽阔的大草原是我的故乡. 没有高学历,没有侃侃而谈的高谈阔论,拥有的就是那一份对技术的执著,对自我价值的追求. 我是谁,其实并不重要,我是高级开发.我是架构师.我是技术经理,这些都是我,跟各位没有半毛钱关系.最重要的是,我能给读者带来什么.接下来的日子里,就看看本书能给各位带来什么惊喜,也许到最后,你都不会记得我的名字,没有关系,相信我,那并不重要. 关于本书 本书是<破境之道>系列技术丛书中的一部分,将

《C# 爬虫 破境之道》:第一境 爬虫原理 — 第五节:数据流处理的那些事儿

为什么说到数据流了呢,因为上一节中介绍了一下异步发送请求.同样,在数据流的处理上,C#也为我们提供几个有用的异步处理方法.而且,爬虫这生物,处理数据流是基础本能,比较重要.本着这个原则,就聊一聊吧. 我们经常使用到的流有文件流.内存流.网络流,爬虫与这三种流都有着密不可分的联系,可以联想以下这些场景: 当我们采集的数据,是一个压缩包或者照片,那么要存储它们到硬盘上,就需要使用到文件流了: 当我们采集的数据,是经过GZip等压缩算法压缩过的,那么要解压它,就需要使用到内存流了: 当我们的爬虫运行起

《ASP.NET MVC 5 破境之道》:第一境 ASP.Net MVC5项目初探 &mdash; 第二节:MVC5项目结构

第一境 ASP.Net MVC5项目初探 第一节:运行第一个MVC5项目 第二节:MVC5项目结构 第三节:View层简单改造 第四节:打造首页面 第二节:MVC5项目结构 接下来,我们来看看,VS为我们自动创建的项目,是什么样子的? 可以通过菜单中[View]->[Solution Explorer]项来打开解决方案资源管理器.这是一个树形结构的视图,根节点是解决方案,解决方案节点下,就是一个一个的项目了,目前,我们的解决方案里只有一个项目(HonorShop.Web). 接下来,展开(Hon

《C# GDI+ 破境之道》:第一境 GDI+基础 —— 第三节:画圆形

有了上一节画矩形的基础,画圆形就不要太轻松+EZ:)所以,本节在画边线及填充上,就不做过多的讲解了,关注一下画“随机椭圆”.“正圆”.“路径填充”的具体实现就好.与画矩形相比较,画椭圆与之完全一致,没有任何特别之处. 在画矩形时,我们使用: System.Drawing.Graphics.DrawRectangle(Brush brush, Rectangle rect); System.Drawing.Graphics.FillRectangle(Brush brush, Rectangle

&lt;MySQL管理之道第二版元旦截稿

<MySQL管理之道第二版>元旦截稿,这周即可送往机械工业出版社排版订正,在这里感谢沃趣科技公司高级DBA邱文辉提供"MariaDB 10 Hash Join索引优化"一文.

爬虫实例2——有道翻译

这个爬虫可以实现有道翻译的功能,支持中译英和英译中,主要使用了requests和json这两个模块. # -*- coding: utf-8 -*- import json import requests def translate(sentence):     url = 'http://fanyi.youdao.com/translate?smartresult=dict&smartresult=rule&smartresult=ugc'     data = {         &q

二维码已死?谁将是互联网+下的破局之道

原文标题:二维码已死?谁将是互联网+下的破局之道 随着科技的进步,互联网+和工业4.0的进程,增强现实和虚拟现实将会得到前所未有爆发性的增长,并将引领下一代互联网的走向,甚至取代目前人人离不开的手机.这已成为业内人士的共识,然而VR/AR的发展是否真如想象中的美呢? 黑科技大热!你到底抓住几分发展重心? 以智能手机的快速普及过程来看,硬件.内容和网络这三大基石成为信息革命的关键点,同样AR/VR亦是.随着第五代移动通信技术(5G)频繁进入大众视野并布局,其超过4G千倍的网速将能提供更大的容量,同

杨森翔:春节文化大观上编 第三章 春节古诗词 目录 第一节:春节诗词概述 一、 除夕诗词概述 二、元日诗词概述 三、 元宵诗词概述 第二节:春节古诗词拾萃

杨森翔:春节文化大观上编 第三章 春节古诗词 目录 第一节:春节诗词概述 一. 除夕诗词概述 二.元日诗词概述 三. 元宵诗词概述 第二节:春节古诗词拾萃 一.腊祭诗词 二.祭灶诗词 三.除夕诗词 四.元旦诗词 五.人日诗词 六.元宵诗词 第一节:春节古诗词概述 中国的春节,作为除旧迎新的节日,时间相当长,从年前的腊月二十三,天空中就似乎弥漫了节日的气息.这种节日的气氛,在保持传统风俗较好的地方,甚至会持续到二月二龙抬头的时候,但欢度春节的高潮,应该说是自除夕始一直到上元之夜.因此,历代歌咏和反

爬虫学习之一个简单的网络爬虫

这是一个网络爬虫学习的技术分享,主要通过一些实际的案例对爬虫的原理进行分析,达到对爬虫有个基本的认识,并且能够根据自己的需要爬到想要的数据.有了数据后可以做数据分析或者通过其他方式重新结构化展示. 什么是网络爬虫 网络爬虫(又被称为网页蜘蛛,网络机器人,在FOAF社区中间,更经常的称为网页追逐者),是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本.另外一些不常使用的名字还有蚂蚁.自动索引.模拟程序或者蠕虫.via 百度百科网络爬虫 网络蜘蛛(Web spider)也叫网络爬虫(Web c