Go 请求响应循环详解

在你可以建立一个网络刮板之前,你必须花点时间思考一下互联网是如何工作的。互联网的核心是一个连接在一起的计算机网络,可以通过域查找系统DNS服务器)发现。当你想访问网站时,你的浏览器会将网站 URL 发送到 DNS 服务器,URL 被翻译成 IP 地址,然后,您的浏览器向该 IP 地址的计算机发送请求。这台称为 web 服务器的机器接收并检查请求,并决定将什么发送回浏览器。然后,浏览器解析服务器发送的信息,并根据数据格式在屏幕上显示内容。web 服务器和浏览器能够进行通信,因为它们遵守一组称为 HTTP 的全局规则。在本章中,您将了解 HTTP 请求和响应循环的一些关键点。

本章涵盖以下主题:

当客户端(如浏览器)从服务器请求网页时,它会发送 HTTP 请求。此类请求的格式定义了操作、资源和 HTTP 协议的版本。一些 HTTP 请求包括服务器要处理的额外信息,例如查询或特定元数据。根据操作的不同,您也可能会向服务器发送新信息以供服务器处理。

当前有九种 HTTP 请求方法,它们定义了客户端所需的一般操作。对于服务器应该如何处理请求,每个方法都有特定的含义。九种申请方式如下:

  • GET
  • POST
  • PUT
  • DELETE
  • HEAD
  • CONNECT
  • TRACE
  • OPTIONS
  • PATCH

您需要的最常见的请求方法是GETPOSTPUTGET请求用于从网站检索信息。POSTPUT请求用于向网站发送信息,如用户登录数据。这些类型的请求通常仅在提交某种类型的表单数据时发送,我们将在本书后面的章节中介绍。

在构建 web scraper 时,绝大多数情况下,您将向服务器发送 HTTPGET请求以获取网页。GET请求的最简单示例 http://example.com/index.html 看起来像这样:

GET /index.html HTTP/1.1
Host: example.com

客户端通过GET动作将此消息发送给服务器,以使用1.1版本的 HTTP 协议获取index.html资源。HTTP 请求的第一行称为请求行,是 HTTP 请求的核心

请求行下面是一系列键值对,它们提供了描述如何处理请求的元数据。这些元数据字段称为 HTTP 头。在前面提出的简单请求中,我们有一个 HTTP 头,它定义了我们试图访问的目标主机。HTTP 协议不需要此信息;然而,发送请求几乎总是为了澄清谁应该收到请求。

如果要检查 web 浏览器发送的 HTTP 请求,您将看到更多的 HTTP 头。以下是谷歌 Chrome 浏览器发送到同一example.com网站的示例:

GET /index.html HTTP/1.1
Host: example.com
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
If-None-Match: "1541025663+gzip"
If-Modified-Since: Fri, 09 Aug 2013 23:54:35 GMT

HTTP 请求的基本原理是相同的,但是,您的浏览器提供了更多的请求头,主要与如何处理缓存的 HTML 页面有关。我们将在下面的章节中更详细地讨论其中的一些标题。

服务器读取请求并处理所有头,以决定如何响应您的请求。在最基本的场景中,服务器将响应说您的请求是确定的,并交付index.html的内容。

对于某些 HTTP 请求,客户机需要提供额外的信息以优化请求。这通常以两种不同的方式完成。对于 HTTPGET请求,有一种已定义的方法可以使用 URL 在请求中包含额外信息。在 URL 末尾放置一个?定义 URL 资源的结尾,下一节定义查询参数。这些参数是定义发送到服务器的额外信息的键值对。键值对编写如下:

key1=value1&key2=value2&key3 ...

在执行搜索时,您会经常看到这种情况。作为一个假设的例子,如果您在一个网站上搜索鞋子,您可能会遇到一个分页的结果页面,URL 可能如下所示:

https://buystuff.com/product_search?keyword=shoes&page=1

注意,资源是product_search,后面是keywordpage的查询参数。这样,您可以通过调整查询从所有页面收集产品。

查询参数由网站定义。没有任何标准参数是所有网站都必须具备的,因此需要根据您正在抓取的网站进行一些调查。

查询参数通常仅用于 HTTPGET请求。对于向服务器发送数据的请求,例如POSTPUT请求,您将发送一个包含所有额外信息的请求正文。在 HTTP 请求中,请求主体放在 HTTP 头之后,它们之间有一行空格。以下是一个假设的POST登录虚拟网站的请求:

POST /login HTTP/1.1
Host: myprotectedsite.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 38

username=myuser&password=supersecretpw

在此请求中,我们将我们的usernamepassword发送到myprotectedsite.com/login。此请求的标题必须描述请求主体,以便服务器能够处理它。在本例中,我们声明请求主体采用x-www-form-urlencoded格式,该格式与查询参数部分中的查询参数使用的格式相同。我们可以使用其他格式,例如JSONXML甚至纯文本,但只有在服务器支持的情况下。x-www-form-urlencoded格式是最广泛支持的格式,通常是安全的。我们在标头中定义的第二个参数是请求正文的长度(以字节为单位)。这允许服务器有效地准备处理数据,或者在数据太大时完全拒绝请求。

至少如果您熟悉该结构,Go 标准库对构建 HTTP 请求有很好的支持。我们将在本章后面重新讨论如何实现这一点。

在大多数情况下,当服务器响应您的请求时,它将提供一个状态代码、一些响应头以及资源的内容。继续我们之前的请求 http://www.example.com/index.html ,您将能够看到一个典型的响应是什么样子的,一节一节

HTTP 响应的第一行称为状态行,通常如下所示:

HTTP/1.1 200 OK

首先,它告诉您服务器使用的 HTTP 协议的版本。这应始终与客户端 HTTP 请求发送的版本相匹配。在本例中,我们的服务器使用的是版本1.1,下一部分是 HTTP 状态码。这是用于指示响应状态的代码。大多数情况下,您会看到状态代码为 200,表示请求成功,随后会出现响应正文。情况并非总是如此,我们将在下一节更深入地研究 HTTP 状态代码。OK 是状态代码的可读描述,仅用于您自己的参考。

HTTP 响应头跟随状态行,看起来与 HTTP 请求头非常相似。它们还提供特定于响应的元数据,就像请求头一样。以下是我们example.com响应的标题:

Accept-Ranges: bytes
Cache-Control: max-age=604800
Content-Type: text/html; charset=UTF-8
Date: Mon, 29 Oct 2018 13:31:23 GMT
Etag: "1541025663"
Expires: Mon, 05 Nov 2018 13:31:23 GMT
Last-Modified: Fri, 09 Aug 2013 23:54:35 GMT
Server: ECS (dca/53DB)
Vary: Accept-Encoding
X-Cache: HIT
Content-Length: 1270

在这个响应中,您可以看到一些描述页面内容、如何缓存页面以及剩余数据大小的标题。此信息对于接收数据后的处理非常有用。

响应的其余部分是呈现index.html的实际网页。您的浏览器将获取这些信息,并为网页本身绘制文本、图像和样式,但为了进行爬取,不需要这样做。响应主体的缩写形式与此类似:

<!doctype html>
<html>
<head>
 <title>Example Domain</title>
 <meta charset="utf-8" />
 <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
 <meta name="viewport" content="width=device-width, initial-scale=1" />
 <!-- The <style> section was removed for brevity -->
</head>
<body>
 <div>
  <h1>Example Domain</h1>
<p>This domain is established to be used for illustrative examples in
   documents. You may use this domain in examples without prior 
   coordination or asking for permission.</p>
<p><a href="http://www.iana.org/domains/example">More information...</a></p>
 </div>
</body>
</html>

大多数情况下,您将处理来自状态代码为 200 的 web 服务器的响应,这意味着请求正常。但是,您不时会遇到 web scraper 应该知道的其他状态代码。

HTTP 状态代码用于通知 HTTP 客户端 HTTP 请求的状态。在某些情况下,HTTP 服务器需要通知客户端请求未被理解,或者需要采取额外的操作以获得完整响应。HTTP 状态代码分为四个不同的范围,每个范围涵盖特定类型的响应。

这些代码用于向 HTTP 客户端提供有关如何传递请求的信息。这些代码通常由 HTTP 客户机本身处理,并在您的 web scraper 需要担心它们之前进行处理。

例如,客户机可能希望使用 HTTP 2.0 协议发送请求,并请求服务器进行更改。如果服务器可以支持 HTTP 2.0,它将以 101 状态代码响应,这意味着交换协议。这样的案件将由客户秘密处理,因此您无需担心。

状态代码的200-299范围表示请求已成功处理,没有问题。这里需要注意的最重要的代码是状态代码 200。这意味着你有一个反应体朝你走来,一切都很完美!

在某些情况下,您可能正在下载一个大文件的块(以 GB 为单位),其中您请求从服务器下载的字节范围。在这种情况下,成功的响应应该是 206,这意味着服务器正在从原始文件返回部分内容。

此范围内的其他代码表示请求成功,但服务器正在后台处理信息,或者根本没有内容。这些通常不会出现在网页抓取中。

如果您遇到此范围内的状态代码,则表示请求已被理解,但需要额外的步骤才能访问实际内容。您在这里遇到的最常见的情况是重定向。

301、302、307 和 308 状态代码都表示您正在查找的资源可以在其他位置找到。在该响应的标头中,服务器应指示响应标头中的最终位置。例如,301 响应可能如下所示:

HTTP/1.1 301 Moved Permanently
Location: /blogs/index.html
Content-Length: 190

<html>
<head><title>301 Moved Permanently</title></head>
<body bgcolor="white">
<h1>301 Moved Permanently</h1>
Please go to <a href="/blogs/index.html">/blogs/index.html</a>
</body>
</html>

服务器包含一个Location头,告诉客户端资源的位置被移动到了哪里,客户端应该向该位置发送下一个请求。在大多数情况下,此处的内容可以忽略。

此范围内的其他状态代码与代理和缓存信息的使用有关,我们将在以后的章节中讨论这两个问题。

当您遇到此范围内的状态代码时,您应该予以关注。400范围表示您的请求有问题。有许多不同的问题可以触发这些响应,例如格式不良、身份验证问题或异常请求。服务器将这些代码发送回其客户机,告诉他们他们将不会满足请求,因为某些内容看起来很粗略。

您可能已经熟悉的一个状态代码是 404 未找到。当您的请求是服务器似乎找不到的资源时,就会发生这种情况。这可能是由于资源拼写错误或页面根本不存在。有时,网站会更新服务器上的文件,可能会忘记用新位置更新网页中的链接。这会导致链接断开,尤其是当页面链接到外部网站时。

在此范围内,您可能会遇到的其他常见状态代码有 401 未经授权和 403 禁止。在这两种情况下,这意味着您正试图访问需要正确身份验证凭据的页面。web 有许多不同的身份验证形式,本书将在以后的章节中仅介绍基本内容。

我想在此范围内强调的最后一个状态代码是 429 个请求过多。某些 web 服务器配置了速率限制,这意味着您只能在特定时间段内维护特定数量的请求。如果你超过了这个速度,那么你不仅对 web 服务器施加了不合理的压力,而且还暴露了你的 web 刮板,这使它有被列入黑名单的风险。遵循正确的网络抓取礼仪对你和你的目标网站都是有益的。

此范围内的状态代码通常表示与服务器本身有关的错误。虽然这些错误通常不是你的错,但你仍然需要意识到它们并适应这种情况。

状态代码 502 Bad Gateway(坏网关)和 503 Service(服务暂时不可用)表示由于服务器内部的问题,服务器无法生成资源。这并不一定意味着资源不存在,或者不允许您访问它。遇到这些代码时,最好将请求放在一边,稍后再试。如果您经常看到这些代码,您可能希望停止所有请求并允许服务器解决其问题。

在某些情况下,web 服务器中的某些内容会因没有特定原因而中断。在这种情况下,您将收到 500 内部服务器错误的状态代码。这些错误是通用的,通常是服务器代码崩溃的原因。在这种情况下,重试请求或将刮板收回的相同建议也是相关的。

现在,您已经熟悉了 HTTP 请求和响应的基本知识,现在是时候看看 Go 中的情况了。Go 中的标准库提供了一个名为net/http的包,其中包含构建客户机所需的所有工具,该客户机能够从 web 服务器请求页面并轻松处理响应。

让我们看一下本章开头的例子,在这里我们访问了在 www. t2>的网页。http://www.example.com/index.html 。底层 HTTP 请求指示位于example.com的 web 服务器向GET发送index.html资源:

GET /index.html HTTP/1.1
Host: example.com

使用 Gonet/http包,您将使用以下代码行:

r, err := http.Get("http://www.example.com/index.html")

Go 编程语言允许从单个函数返回多个变量。这也是通常抛出和处理错误的方式。

这是使用net/http包的默认 HTTP 客户端请求index.html资源,该资源返回两个对象:HTTP 响应(r和错误(err。在 Go 中,错误作为值返回,而不是被其他代码抛出和捕获。如果err等于nil,那么我们知道与 web 服务器的通信没有问题。

让我们看看本章开头的反应。如果请求成功,服务器将返回如下内容:

HTTP/1.1 200 OK
Accept-Ranges: bytes
Cache-Control: max-age=604800
Content-Type: text/html; charset=UTF-8
Date: Mon, 29 Oct 2018 13:31:23 GMT
Etag: "1541025663"
Expires: Mon, 05 Nov 2018 13:31:23 GMT
Last-Modified: Fri, 09 Aug 2013 23:54:35 GMT
Server: ECS (dca/53DB)
Vary: Accept-Encoding
X-Cache: HIT
Content-Length: 1270

<!doctype html>
<html>
<head>
 <title>Example Domain</title>
 <meta charset="utf-8" />
 <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
 <meta name="viewport" content="width=device-width, initial-scale=1" />
 <!-- The <style> section was removed for brevity -->
</head>
<body>
 <div>
 <h1>Example Domain</h1>
 <p>This domain is established to be used for illustrative examples in
    documents. You may use this
    domain in examples without prior coordination or asking for
    permission.</p>
 <p><a href="http://www.iana.org/domains/example">More information...</a></p>
 </div>
</body>
</html>

所有这些信息都包含在r变量中,该变量是从http.Get()函数返回的*http.Response。让我们看看 GO 中对象 T3 的定义。Go 标准库中定义了以下struct

type Response struct {
    Status string
    StatusCode int
    Proto string
    ProtoMajor int
    ProtoMinor int
    Header Header
    Body io.ReadCloser
    ContentLength int64
    TransferEncoding []string
    Close bool
    Uncompressed bool
    Trailer Header
    Request *Request
    TLS *tls.ConnectionState
}

http.Response对象包含处理 HTTP 响应所需的所有字段。最值得注意的是,StatusCodeHeaderBody在爬取中很有用。让我们将请求和响应放在一个简单的示例中,将index.html文件保存到您的计算机中。

在您设置的$GOPATH/src文件夹中,创建一个名为simplerequest的文件夹。在simplerequest中,创建一个名为main.go的文件。将main.go的内容设置为以下代码:

package main

import (
 "log"
 "net/http"
 "os"
)

func main() {
 // Create the variables for the response and error
 var r *http.Response
 var err error

 // Request index.html from example.com
 r, err = http.Get("http://www.example.com/index.html")

 // If there is a problem accessing the server, kill the program and print the error the console
 if err != nil {
  panic(err)
 }

 // Check the status code returned by the server
 if r.StatusCode == 200 {
  // The request was successful!
  var webPageContent []byte

  // We know the size of the response is 1270 from the previous example
  var bodyLength int = 1270

  // Initialize the byte array to the size of the data
  webPageContent = make([]byte, bodyLength)

  // Read the data from the server
  r.Body.Read(webPageContent)

  // Open a writable file on your computer (create if it does not 
     exist)
  var out *os.File
  out, err = os.OpenFile("index.html", os.O_CREATE|os.O_WRONLY, 0664)

  if err != nil {
   panic(err)
  }

  // Write the contents to a file
  out.Write(webPageContent)
  out.Close()
 } else {
  log.Fatal("Failed to retrieve the webpage. Received status code", 
  r.Status)
 }
}

这里给出的示例有点冗长,以便向您展示 Go 编程的基础知识。当您阅读本书时,您将了解使代码更简洁的提示和技巧。

您可以在终端窗口中键入以下命令,从simplerequest文件夹中运行此代码:

go run main.go 

如果一切顺利,您不应该看到打印的消息,应该有一个名为index.html的新文件,其中包含响应主体的内容。您甚至可以使用 web 浏览器打开该文件!

考虑到这些基础知识,您应该在 Go 中创建一个 web scraper,它只需几行代码即可创建 HTTP 请求和读取 HTTP 响应。

在本章中,我们介绍了 HTTP 请求和响应的基本格式。我们还了解了 Go 中 HTTP 请求是如何发出的,以及http.Response结构与真实 HTTP 响应的关系。最后,我们创建了一个小程序,向发送 HTTP 响应 http://www.example.com/index.html 并处理 HTTP 响应。关于完整的 HTTP 规范,我鼓励您访问https://www.w3.org/Protocols/

第三章网络抓取礼仪中,我们来看看做一个网络好公民的最佳实践。

教程来源于Github,感谢apachecn大佬的无私奉献,致敬!

技术教程推荐

深入浅出区块链 -〔陈浩〕

Java并发编程实战 -〔王宝令〕

网络编程实战 -〔盛延敏〕

Kafka核心源码解读 -〔胡夕〕

说透芯片 -〔邵巍〕

Vue 3 企业级项目实战课 -〔杨文坚〕

手把手教你落地DDD -〔钟敬〕

快速上手C++数据结构与算法 -〔王健伟〕

结构思考力 · 透过结构看问题解决 -〔李忠秋〕