Skip to content

Latest commit

 

History

History
379 lines (273 loc) · 19.2 KB

File metadata and controls

379 lines (273 loc) · 19.2 KB

二、请求/响应循环

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

本章涵盖以下主题:

  • HTTP 请求看起来像什么?
  • HTTP 响应是什么样子的?
  • 什么是 HTTP 状态码?
  • Go 中的 HTTP 请求/响应是什么样子的?

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 头,它定义了我们试图访问的目标主机。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 响应是什么样子的?

在大多数情况下,当服务器响应您的请求时,它将提供一个状态代码、一些响应头以及资源的内容。继续我们之前的请求 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 状态代码分为四个不同的范围,每个范围涵盖特定类型的响应。

100–199 范围

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

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

200–299 范围

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

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

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

300–399 范围

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

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–499 范围

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

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

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

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

500–599 范围

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

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

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

Go 中的 HTTP 请求/响应是什么样子的?

现在,您已经熟悉了 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/

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