如何写一个爬虫 - 第二篇

这可能是一篇教你如何写爬虫的博客。

HTTP 的格式

由 TCP 组成的 HTTP

MDN 中对 HTTP 的介绍:(HTTP) Though often based on a TCP/IP layer, it can be used on any reliable transport layer; that is, a protocol that doesn't lose messages silently, such as UDP.

在这篇博客中,我们只介绍由 TCP 组成的 HTTP。

TCP 的格式

关于 TCP 的原理与组成,这里不做讲解,我们只需要知道 TCP 的基本格式。

TCP 是一种流传输协议,由 TCP 传输得到的数据可以认为是字节串。在 HTTP 中,我们可能会获得这样的一串数据:

1
0x48 0x54 0x54 0x50 0x2F 0x31 0x2E 0x31 0x20 0x32 0x30 0x30 0x20 0x4F 0x4B 0x0D 0x0A 0x43 0x6F 0x6E 0x74 0x65 0x6E 0x74 0x2D 0x4C 0x65 0x6E 0x67 0x74 0x68 0x3A 0x20 0x37 0x0D 0x0A 0x0D 0x0A 0x68 0x65 0x6C 0x6C 0x6F 0x0D 0x0A

将其以字符串的形式显示出来就是这样:

1
"HTTP/1.1 200 OK\r\nContent-Length: 7\r\n\r\nhello\r\n"

HTTP 对字符串的分割

我们以两个连续的 \r\n 将 TCP 数据进行分割,结果的前半部分再以单个 \r\n 进行分割。上面的字符串就转换为了这样:

1
2
3
4
HTTP/1.1 200 OK
Content-Length: 7

hello\r\n

分割的后半部分全部是数据,不对其做进一步的解析。

请求报文格式

1
2
3
4
5
6
[请求方法] [Path] [协议版本]
[header 字段]: [header 值]
...
[header 字段]: [header 值]

[数据]

比如:

1
2
GET /index.html HTTP/1.1
Host: localhost

响应报文格式

1
2
3
4
5
6
[协议版本] [响应状态码] [响应消息]
[header 字段]: [header 值]
...
[header 字段]: [header 值]

[数据]

比如:

1
2
3
4
HTTP/1.1 200 OK
Content-Length: 7

Hello\r\n

请求流程

HTTP 是无状态的协议,一次 HTTP 请求的过程可以大概看做这样:

  1. 用户产生请求,客户端(浏览器)根据 HTTP 协议构建一个请求字符串发送给服务器
  2. 服务器解析字符串,产生对应响应,同样封成字符串返回客户端
  3. 客户端解析字符串,获得数据,渲染成可见的页面(如果是爬虫则不需要)

基本 HTTP 页面的获取

HTTP 协议设计的初衷是在网络上传输静态的页面,我们就先试着来获取一个静态的页面。

我使用的工具是 curl,这是一个用 C 语言编写的功能强大的网络库,很多语言内置 curl 支持。有关 curl 的使用可以戳这里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ curl http://localhost/2/index.html -v
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET /2/index.html HTTP/1.1
> Host: localhost
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Host: localhost
< Date: Fri, 02 Nov 2018 19:58:22 +0800
< Connection: close
< Content-Type: text/html; charset=UTF-8
< Content-Length: 7
<
hello
* Closing connection 0

(有关的代码可以在 github.com/dogest/trick 获取。)

使用 curl,我们可以清晰地看到发送的请求与响应以及请求的流程。

请求

1
2
3
4
GET /2/index.html HTTP/1.1
Host: localhost
User-Agent: curl/7.58.0
Accept: */*

第一行中的三项分别指明请求类型、请求路径和协议版本,二三四行都是附带的 header:

  • Host: 现代的服务器可以在一台机器上承载多个网站,Host 可以用来指明用户具体要访问哪个网站。
  • User-Agent: 指明用户使用的浏览器或软件。
  • Accept: 用户端可以接受的响应类型,*/* 代表可以接受任何类型。

响应

1
2
3
4
5
6
7
8
HTTP/1.1 200 OK
Host: localhost
Date: Fri, 02 Nov 2018 19:58:22 +0800
Connection: close
Content-Type: text/html; charset=UTF-8
Content-Length: 7

hello\r\n

(curl 将数据打印了出来,'\r\n' 就表现为了换行)

  • Date: 该请求返回的时间。
  • Connection: 标示是否保持 TCP 连接以供后续使用。
  • Content-Type: 响应的类型。
  • Content-Length: 响应的长度,一些动态的页面可能没有这个设置。

更多有关 headers 的信息,请参阅 MDN

页面解析

显然,只要对返回的数据稍做解析,就可以获得页面的数据了。

总结

这篇博客讲了 HTTP 的格式与获取一个静态页面的流程,以及实际获得了一个页面。至此,我们正式开启了爬虫之路。