URL和URI
我们先了解一下 URI和 URL。URI的全称为 Uniform Resource ldentifer,即统一资源标志符;URL的全称为 Universal Resource Locator,即统一资源定位符。它们是什么意思呢?举例来说,https:/github.com/favicon.ico 既是一个 URI,也是一个 URL。即有 favicon.ico 这样一个图标资源,我们用上一行中的 URI/URL指定了访问它的唯一方式,其中包括访问协议 https、访问路径(即根目录)和资源名称。通过一个链接,便可以从互联网中找到某个资源,这个链接就是 URI/URL。
URL是URI的子集,也就是说每个 URL都是 URI,但并非每个 URI都是 URL。那么,怎样的 URI不是 URL呢?除了 URL.URI还包括一个子类,叫作URN,其全称为Universal Resource Name,即统一资源名称。URN只为资源命名而不指定如何定位资源,例如 urn:isbn:0451450523指定了一本书的ISBN,可以唯一标识这本书,但没有指定到哪里获取这本书,这就是URN,URL、URN和URI的关系如下图:
在目前的互联网中,URN使用得非常少,几乎所有的URI都是URL,所以对于一般的网页链接我们既可以称之为 URL,也可以称之为 URI,我个人习惯称 URL。
但 URL也不是随便写的,它也是需要遵循一定格式规范的,基本的组成格式如下:
scheme://[username:password@]hostname[:port][/path][;parameters][?query][#fragment]
其中,中括号包括的内容代表非必要部分,比如 htps://www.baidu.com 这个 URL,这里就只包含了
scheme 和 hostname 两部分,没有 port、path、parameters、query、fragment。这里我们分别介绍一下几部分代表的含义和作用。
- scheme:协议常用的协议有 http、hitps、fp等,另外 scheme 也被常称作 protocol,二者都
代表协议的意思。 - usemame、password:用户名和密码。在某些情况下 URL 需要提供用户名和密码才能访问,
这时候可以把用户名和密码放在host前面。比如 https://ssr3.scrape.center这个 URL需要用户名
和密码才能访问,直接写为 https://admin:admin@ssr3.scrape.center 则可以直接访问。 - hostname:主机地址。可以是域名或IP地址,比如 https://www.baidu.com这个 URL中的
hostname 就是 www.baidu.com,这就是百度的二级域名。比如 https://8.8.8.8这个 URL 中的
hostname 就是8.8.8.8,它是一个 IP 地址。 - port:端口。这是服务器设定的服务端口,比如https:/8.8.8.8:12345这个URL 中的端口就是
12345。但是有些 URL中没有端口信息,这是使用了默认的端口。http协议的默认端口是80
https 协议的默认端口是 443。所以 htps://www,baidu.com 其实相当于 https://www.baidu.com:443,而http://www.baidu.com 其实相当于 http://www.baidu.com:80。 - path:路径。指的是网络资源在服务器中的指定地址,比如htps://github.com/favicon.ico 中的path 就是 favicon.ico,指的是访问 GitHub 根目录下的favicon.ico。
- parameters:参数。用来指定访问某个资源时的附加信息,比如https://8.8.8.8:12345/hello:user 中的user 就是 parameters。但是 parameters现在用得很少,所以目前很多人会把该参数后面的 query部分称为参数,甚至把 parameters和query混用。严格意义上来说,parameters是分号(;)后面的内容。
- query:查询。用来查询某类资源,如果有多个查询,则用&隔开。query 其实非常常见,比如 https://www.baidu.com/s?wd=nba&ie=utf-8,其中的 query 部分就是 wd=nba&ie=utf-8,这里指定了 wd 是 nba,ie 是 utf-8。由于 query 比刚才所说的 parameters 使用频率高很多,所以平时我们见到的参数、GET请求参数、parameters、params等称呼多数情况指代的也是 query。从严格意义上来说,应该用 query 来表示。
- ftagment:片段。它是对资源描述的部分补充,可以理解为资源内部的书签。目前它有两个主要的应用,一个是用作单页面路由,比如现代前端框架 Vue、React 都可以借助它来做路由管理;另外一个是用作 HTML锚点,用它可以控制一个页面打开时自动下滑滚动到某个特定的位置。
HTTP和HTTPS
刚才我们了解了 URL的基本构成,其支持的协议有很多,比如 http、https、fp、sfp、smb 等。在爬虫中,我们抓取的页面通常是基于 htp 或 https 协议的,因此这里首先了解一下这两个协议的含义。HTTP的全称是 Hypertext Transfer Protocol,中文名为超文本传输协议,其作用是把超文本数据从网络传输到本地浏览器,能够保证高效而准确地传输超文本文档。HTTP是由万维网协会(World Wide Web Consortium)和 Internet 工作小组 IETF(Intermet Engineering Task Force)合作制定的规范,目前被人们广泛使用的是 HTTP1.1版本,当然,现在也有不少网站支持 HTTP 2.0。
HTTPS 的全称是 Hypertext Transfer Protocolover Secure Socket Layer,是以安全为目标的 HTTP 通
道,简单讲就是 HTTP 的安全版,即在 HTTP下加入SSL层,简称HTTPS。
HTTPS的安全基础是SSL,因此通过该协议传输的内容都是经过SSL加密的,SSL的主要作用有以下两种。
- 建立一个信息安全通道,保证数据传输的安全性。
- 确认网站的真实性。凡是使用了HTTPS协议的网站,都可以通过单击浏览器地址栏的锁头标志来查看网站认证之后的真实信息,此外还可以通过CA机构颁发的安全签章来查询。
HTTP请求过程
在浏览器地址栏中输人一个 URL,按下回车之后便可观察到对应的页面内容。实际上,这个过程是浏览器先向网站所在的服务器发送一个请求,网站服务器接收到请求后对其进行处理和解析,然后返回对应的响应,接着传回浏览器。由于响应里包含页面的源代码等内容,所以浏览器再对其进行解析,便将网页呈现出来。
为了更直观地说明上述过程,这里用Chrome浏览器开发者模式下的Network 监听组件来做一下演示。Network 监听组件可以在访问当前请求的网页时,显示产生的所有网络请求和响应。
打开 Chrome浏览器,访问百度,这时候单击鼠标右键并选择“检査"菜单( 或者直接按快捷键 F12)即可打开浏览器的开发者工具,如下图所示。
我们切换到 Network面板,然后重新刷新网页,这时候就可以看到在Network面板下方出现了很多个条目,其中一个条目就代表一次发送请求和接收响应的过程,如下图所示。
我们先观察第一个网络请求,即www.baidu.com,其中各列的含义如下:
- 第一列 Name:请求的名称。一般会用 URL的最后一部分内容作为名称。
- 第二列 Status:响应的状态码。这里显示为 200,代表响应是正常的。通过状态码,我们可以判断发送请求之后是否得到了正常的响应。
- 第三列 Type:请求的文档类型。这里为document,代表我们这次请求的是一个 HTML 文档内容是一些 HTML 代码。
- 第四列Initiator:请求源。用来标记请求是由哪个对象或进程发起的。
- 第五列 Size:从服务器下载的文件或请求的资源大小。如果资源是从缓存中取得的,则该列会显示 from cache.
- 第六列 Time:从发起请求到获取响应所花的总时间。
- 第七列 Waterfall:网络请求的可视化瀑布流。
我们单击某个条目,则可看到更详细的信息,如下图所示:
首先是 General部分,其中 Request URL,为请求的 URL,Request Method为请求的方法,Status Code为响应状态码,RemoteAddress为远程服务器的地址和端口,Referrer Policy 为 Referrer 判别策略。
继续往下可以看到 Response Headers 和 Request Headers,分别代表响应头和请求头。请求头中包含许多请求信息,如浏览器标识、Cookie、Host等信息,这些是请求的一部分,服务器会根据请求头里的信息判断请求是否合法,进而做出对应的响应。响应头是响应的一部分,其中包含服务器的类型文档类型、日期等信息,浏览器在接收到响应后,会对其进行解析,进而呈现网页内容。
请求
请求,英文为Request,由客户端发往服务器,分为四部分内容:请求方法(Request Method)、请求的网址(RequestURL)、请求头(RequestHeaders)、请求体(RequestBody)。下面我们分别予以介绍。
- 请求方法
请求方法,用于标识请求客户端请求服务端的方式,常见的请求方法有两种:GET和POST。
在浏览器中直接输入 URL并回车,便发起了一个GET请求,请求的参数会直接包含到 URL里。例如,在百度搜索引擎中搜索 Python就是一个GET请求,链接为 https://www.baidu.com/s?wd=Python,其中 URL中包含了请求的 query 信息,这里的参数 wd 表示要搜寻的关键字。POST 请求大多在提交表单时发起。例如,对于一个登录表单,输入用户名和密码后,单击“登录”按钮,这时通常会发起一个POST请求,其数据通常以表单的形式传输,而不会体现在URL中。GET和POST请求的区别:
- GET请求中的参数包含在URL里面,数据可以在URL中看到;而POST请求的URL不会包含这些数据,数据都是通过表单形式传输的,会包含在请求体中。
- GET请求提交的数据最多只有1024字节,POST方式则没有限制。
登录时一般需要提交用户名和密码,其中密码是敏感信息,如果使用 GET方式请求,密码就会暴露在 URL 里面,造成密码泄露,所以这时候最好以 POST 方式发送。上传文件时,由于文件内容比较大,因此也会选用 POST 方式。
我们平常遇到的绝大部分请求是 GET或 POST 请求。其实除了这两个,还有一些请求方法,如HEAD、PUT、DELETE、CONNECT、OPTIONS、TRACE 等,我们简单将请求方法总结为下表。
-
请求网址
请求的网址,它可以唯一确定客户端想请求的资源。关于 URL的构成和各个部分的功能我们在前文已经提及了,这里就不再赘述。 -
请求头
请求头,用来说明服务器要使用的附加信息,比较重要的信息有 Cookie、Referer、User-Agent等。
下面简要说明一些常用的请求头信息。
(1)Accept:请求报头域,用于指定客户端可接受哪些类型的信息。
(2)Accept-Language:用于指定客户端可接受的语言类型。
(3)Accept-Encoding:用于指定客户端可接受的内容编码。
(4)Host:用于指定请求资源的主机IP和端口号,其内容为请求 URL的原始服务器或网关的位置从 HTTP 1.1版本开始,请求必须包含此内容。
(5)Cookie:也常用复数形式 Cookies,这是网站为了辨别用户,进行会话跟踪而存储在用户本地的数据。它的主要功能是维持当前访问会话。例如,输入用户名和密码成功录某个网站后服务器会用会话保存登录状态信息,之后每次刷新或请求该站点的其他页面,都会发现处于登录状态,这就是 Cookie 的功劳。Cookie 里有信息标识了我们所对应的服务器的会话,每次浏览器在请求该站点的页面时,都会在请求头中加上Cookie 并将其发送给服务器,服务器通过Cookie识别出是我们自己,并且查出当前状态是登录状态,所以返回结果就是登录之后才能看到的网页内容。
(6)Referer:用于标识请求是从哪个页面发过来的,服务器可以拿到这一信息并做相应的处理,如做来源统计、防盗链处理等。
(7)User-Agent:简称UA,这是一个特殊的字符串头,可以使服务器识别客户端使用的操作系统及版本、浏览器及版本等信息。做爬虫时如果加上此信息,可以伪装为浏览器:如果不加,很可能会被识别出来。
(8)Content-Type:也叫互联网媒体类型(Internet Media Type)或者 MIME 类型,在 HTTP 协议消息头中,它用来表示具体请求中的媒体类型信息。例如,texthtml代表HTML格式,image/gif代表 GIF 图片,application/json 代表 JSON 类型。
请求头是请求的重要组成部分,在写爬虫时,通常都需要设定请求头。 -
请求体
请求体,一般承载的内容是 POST请求中的表单数据,对于GET请求,请求体为空。
例如,我登录 GitHub 时捕获到的请求和响应如下图所示:
登录之前,需要先填写用户名和密码信息,登录时这些内容会以表单数据的形式提交给服务器此时需要注意 Request Headers 中指定 Content-Type为 application/x-www-form-urlencoded。只有这样设置 Content-Type,内容才会以表单数据的形式提交。另外,也可以将 Content-Type设置为application/jison来提交 JSON 数据,或者设置为 multipart/form-data 来上传文件。
下表列出了 Content-Type 和 POST 提交数据方式的关系。
在爬虫中,构造 POST 请求需要使用正确的 Content-Type,并了解设置各种请求库的各个参数时使用的都是哪种 Content-Type,如若不然可能会导致 POST 提交后无法得到正常响应。
响应
响应,即 Response,由服务器返回给客户端,可以分为三部分:响应状态码( Response Status Code )、响应头(Response Headers)和响应体(Response Body )。
-
响应状态码
响应状态码,表示服务器的响应状态,如200代表服务器正常响应、404代表页面未找到、500代表服务器内部发生错误。在爬虫中,我们可以根据状态码判断服务器的响应状态,如状态码为 200,证明成功返回数据,可以做进一步的处理,否则直接忽略。下表列出了常见的错误状态码及错误原因。
-
响应头
响应头,包含了服务器对请求的应答信息,如 Content-Type、Server、Set-Cookie 等。下面简要说明一些常用的响应头信息。
(1) Date:用于标识响应产生的时间。
(2) Last-Modified:用于指定资源的最后修改时间,
(3) Content-Encoding:用于指定响应内容的编码。
(4) Server:包含服务器的信息,例如名称、版本号等。
(5) Content-Type:文档类型,指定返回的数据是什么类型,如 text/html 代表返回 HTML 文档,application/x-javascript 代表返回 JavaScript 文件,image/jpeg 代表返回图片。口 Set-Cookie:设置 Cookie。响应头中的 Set-Cookie 用于告诉浏览器需要将此内容放在 Cookie 中下次请求时将 Cookie 携带上。
(6) Expires:用于指定响应的过期时间,可以让代理服务器或浏览器将加载的内容更新到缓存中当再次访问相同的内容时,就可以直接从缓存中加载,达到降低服务器负载、缩短加载时间的目的。 -
响应体
响应体,这可以说是最关键的部分了,响应的正文数据都存在于响应体中,例如请求网页时,响应体就是网页的 HTML代码;请求一张图片时,响应体就是图片的二进制数据。我们做爬虫请求网页时,要解析的内容就是响应体。
在浏览器开发者工具中单击 Preview,就可以看到网页的源代码,也就是响应体的内容,这是爬虫的解析目标。在做爬虫时,我们主要通过响应体得到网页的源代码、JSON 数据等,然后从中提取相应内容。
本节我们了解了 HTTP的基本原理,大概了解了访问网页时产生的请求和响应过程。读者需要好好掌握本节涉及的知识点,在后面分析网页请求时会经常用到这些内容。
HTTP2.0
前面我们也提到了,HTTP 协议从 2015 年起发布了 2.0版本,相比 HTTP 1.1来说,HTTP 2.0变得更快、更简单、更稳定。HTTP2.0在传输层做了很多优化,它的主要目标是通过支持完整的请求与响应复用来减少延迟,并通过有效压缩 HTTP请求头字段的方式将协议开销降至最低,同时增加对请求优先级和服务器推送的支持,这些优化一笔勾销了 HTTP 1.1 为做传输优化想出的一系列“歪招”。
有读者这时候可能会问,为什么不叫 HTTP 1.2 而叫 HTTP 2.0呢?因为 HTTP 2.0内部实现了新的二进制分帧层,没法与之前 HTTP1x的服务器和客户端兼容,所以直接修改主版本号为 2.0。
下面我们就来了解一下 HTTP 2.0 相比 HTTP 1.1来说,做了哪些优化。
-
二进制分帧层
HTTP 2.0所有性能增强的核心就在于这个新的二进制分帧层。在 HTTP1x中,不管是请求Request)还是响应(Response ),它们都是用文本格式传输的,其头部(Headers)、实体(Body)之间也是用文本换行符分隔开的。HTTP2.0对其做了优化,将文本格式修改为了二进制格式,使得解析起来更加高效。同时将请求和响应数据分割为更小的帧,并采用二进制编码。
所以这里就引入了几个新的概念。
(1) 帧:只存在于 HTTP 2.0中的概念,是数据通信的最小单位。比如一个请求被分为了请求头帧(Request Headers Frame)和请求体/数据帧(Request Data Frame )。
(2) 数据流:一个虚拟通道,可以承载双向的消息,每个流都有一个唯一的整数 ID 来标识。
(3) 消息:与逻辑请求或响应消息对应的完整的一系列帧。
在 HTTP2.0中,同域名下的所有通信都可以在单个连接上完成,该连接可以承载任意数量的双向数据流。数据流是用于承载双向消息的,每条消息都是一条逻辑HTTP 消息(例如请求或响应)
它可以包含一个或多个帧。
简而言之,HTTP2.0将 HTTP协议通信分解为二进制编码帧的交换,这些帧对应着特定数据流中的消息,所有这些都在一个 TCP连接内复用,这是 HTTP2.0协议所有其他功能和性能优化的基础。 -
多路复用
在 HTTP1x中,如果客户端想发起多个并行请求以提升性能,则必须使用多个TCP连接,而且浏览器为了控制资源,还会对单个域名有6~8个TCP连接请求的限制。但在 HTTP2.0中,由于有了二进制分帧技术的加持,HTTP 2.0不用再以 TCP连接的方式去实现多路并行了,客户端和服务器可以将HTTP消息分解为互不依赖的帧,然后交错发送,最后再在另一端把它们重新组装起来,达到以下效果。
(1)并行交错地发送多个请求,请求之间互不影响。
(2) 并行交错地发送多个响应,响应之间互不干扰。
(3) 使用一个连接并行发送多个请求和响应。
(4)不必再为绕过 HTTP 1x限制而做很多工作。
(5) 消除不必要的延迟和提高现有网络容量的利用率,从而减少页面加载时间。
这样一来,整个数据传输性能就有了极大提升。
(6) 同域名只需要占用一个TCP 连接,使用一个连接并行发送多个请求和响应,消除了多个 TCP连接带来的延时和内存消耗。
(7) 并行交错地发送多个请求和响应,而且它们之间互不影响。口在HTTP2.0中,每个请求都可以带一个31位的优先值,0表示最高优先级,数值越大优先级越低。有了这个优先值,客户端和服务器就可以在处理不同的流时采取不同的策略了,以最优的方式发送流、消息和帧。 -
流控制
流控制是一种阻止发送方向接收方发送大量数据的机制,以免超出后者的需求或处理能力。可以理解为,接收方太繁忙了,来不及处理收到的消息了,但是发送方还在一直大量发送消息,这样就会出现一些问题。比如,客户端请求了一个具有较高优先级的大型视频流,但是用户已经暂停观看视频了,客户端现在希望暂停或限制从服务器的传输,以免提取和缓冲不必要的数据。再比如,一个代理服务器可能具有较快的下游连接和较慢的上游连接,并且也希望调节下游连接传输数据的速度以匹配上游连接的速度,从而控制其资源利用率等。
HTTP是基于 TCP 实现的,虽然 TCP 原生有流控制机制,但是由于 HTTP2.0数据流在一个TCP 连接内复用,TCP 流控制既不够精细,也无法提供必要的应用级API来调节各个数据流的传输。
为了解决这一问题,HTTP2.0提供了一组简单的构建块,这些构建块允许客户端和服务器实现它们自己的数据流和连接级流控制。
(1) 流控制具有方向性。每个接收方都可以根据自身需要选择为每个数据流和整个连接设置任意的窗口大小。
(2) 流控制的窗口大小是动态调整的。每个接收方都可以公布其初始连接和数据流流控制窗口(以字节为单位 ),当发送方发出 DATA帧时窗口减小,在接收方发出 WINDOW UPDATE 帧时窗口增大。
(3) 流控制无法停用。建立 HTTP2.0连接后,客户端将与服务器交换 SETTINGS 帧,这会在两个方向上设置流控制窗口。流控制窗口的默认值设为 65 535 字节,但是接收方可以设置一个较大的最大窗口大小(23-1字节),并在接收到任意数据时通过发送 WINDOW UPDATE 帧来维持这一大小。
(4)由此可见,HTTP 2.0 提供了简单的构建块,实现了自定义策略来灵活地调节资源使用和分配逻辑,同时提升了网页应用的实际性能和感知性能。
-
服务端推送
HTTP 2.0新增的另一个强大的功能是:服务器可以对一个客户端请求发送多个响应。换句话说,除了对最初请求的响应外,服务器还可以向客户端推送额外资源,而无须客户端明确地请求
如果某些资源客户端是一定会请求的,这时就可以采取服务端推送的技术,在客户端发起一次请求后,提前给客户端推送必要的资源,这样就可以减少一点延迟时间。如图 1-8所示,服务端接收到HTML 相关的请求时可以主动把 JS 和 CSS 文件推送给客户端,而不需要等到客户端解析 HTML时再发送这些请求。 -
HTTP 2.0发展现状
HTTP 2.0的普及是一件任重而道远的事情,一些主流的网站现在已经支持 HTTP 2.0了,主流浏览器现在都已经实现了对 HTTP 2.0的支持,但总体上,目前大部分网站依然以 HTTP 1.1为主。
另外,一些编程语言的库还没有完全支持 HTTP2.0,比如对于Python来说,hyper、httpx 等库已经支持了 HTTP 2.0,但广泛使用的 requests 库依然只支持 HTTP 1.1。