Python 3反爬虫原理与绕过实战
上QQ阅读APP看书,第一时间看更新

2.3 网络协议

网络协议是连接客户端与服务器端的桥梁。学习与网络协议相关的知识,能让我们对网络通信过程以及通信所需条件有一个全面的认识,可以帮助我们提升爬虫技术或者设计出更有效的反爬虫方案。

常见的网络协议有HTTP协议、WebSocket协议、FTP协议、SSH协议和View-Source协议等。在本节中,我们主要学习在Web应用中使用最多的HTTP协议和WebSocket协议。

2.3.1 认识HTTP

当我们在浏览器的地址栏中输入http://www.huawei.com并按下回车键时,我们就通过浏览器向服务器发起了一次HTTP请求,服务器会根据本次请求的信息将服务器上的资源响应给浏览器,浏览器按照规则进行渲染后,得到如图2-18所示的网页。

图2-18 浏览器渲染后的网页

超文本传输协议(Hyper Text Transfer Protocol,简称HTTP)是互联网中应用最为广泛的一种应用层协议,所有的超媒体文档都必须遵守这个标准。HTTP协议是为Web浏览器和Web服务器之间的通信设计的,但是也可以用于其他目的。HTTP协议是无状态协议,这意味着服务器不会在两个请求之间保留任何的数据或者状态。虽然它通常基于TCP/IP层,但是它可以在任何可靠的传输层上使用。

HTTP协议有多个版本,如HTTP/1.0、HTTP/1.1和HTTP/2.0,其中应用最广泛的是HTTP/1.1。本书介绍的HTTP协议相关知识基于HTTP/1.1版本,RFC文档编号为2616(详见https://tools.ietf.org/html/rfc2616)。

如图2-19所示,HTTP协议遵循传统的客户端−服务端器模型,客户端打开连接以发出请求,然后等待服务器端响应。

图2-19 客户端-服务器端模型

HTTP协议可以减少网络传输次数,使浏览器更加高效,它不仅保证了计算机正确快速地传输超文本文档,还能确定传输文档中的哪一部分或优先显示哪一部分(如文本先于图形)等。

2.3.2 资源与资源标识符

HTTP请求的目标称为资源,它可以是文档、照片、视频或其他任何东西。资源由统一资源标识符(URI)进行标识,在Web页面中,资源的标识和位置主要由单个URL(统一资源定位符)给出。URL通常被称为Web网址,是最常见和使用最多的URI。当我们在浏览器的地址栏中输入URL或者单击超链接时,就确定了要获取的资源的地址,比如我们在浏览器的地址栏中输入https://developer.mozilla.org,就是告知浏览器加载目标服务器上的对应资源。

URL由不同的部分组成,一些是强制性的,另一些是可选的。RFC2616给出的HTTP URL格式如下:

http_URL = "http:" "//" host [ ":" port ] [ abs_path [ "?" query ]]

URL由协议类型名称、域名、端口号、资源路径和查询参数组成,其中端口号的默认值为80。在实际场景中,我们看到的URL会更复杂,如http://www.exp.com:80/path/to/file.html?key=v&name=abc#python,它由协议类型名称、域名、端口号、资源路径、查询参数和锚点组成,对应的URL结构如图2-20所示。

图2-20 URL结构

每个组成部分的作用如下。

协议类型名称:表示浏览器必须使用的协议,通常是HTTP协议或其安全版本HTTPS。

域名:表示正在请求哪个Web服务器,也可以直接使用主机地址。

端口号:用于表示Web服务器上的资源入口,如Web服务器使用HTTP协议的标准端口号(HTTP为80,HTTPS为443)来授予对其资源的访问权限。

资源路径:Web服务器上资源的路径,在Web的早期,这样的路径代表了Web服务器上的物理文件位置,如今它主要是由Web服务器处理的抽象标识。

查询参数:用&符号分隔的键值对。在将资源返回给用户之前,Web服务器可以使用这些参数来执行额外的操作。每个Web服务器都有自己的参数规则,了解特定Web服务器如何处理参数的唯一可靠方法是询问Web服务器的所有者或应用程序的开发者。

锚点:资源内部的一种“书签”,浏览器会根据锚点将对应的内容呈现给用户,而不需要用户滑动页面来寻找内容。例如,在HTML文档中,浏览器将滚动到定义锚点的位置;在视频或音频文档中,浏览器将尝试转到锚点所代表的时间。值得注意的是,#之后的部分(也称片段标识符)永远不会随请求一起发送到服务器。

除了HTTP协议和HTTPS协议之外,服务器和浏览器也能够处理其他协议,如表2-13所示。

表2-13 协议名称及描述

URN是使用较少的URI,叫作统一资源名称,它用于在特定名称空间中按名称标识资源,比如urn:ietf:rfc:7230标识了IETF规范7230。要注意的是,URN仅仅用于标识资源,并不能像URL那样定位资源。

2.3.3 HTTP请求与响应

HTTP请求是指从客户端向服务器端发起的请求消息。以浏览器为例,它向Web服务器发出了一条请求其实就是向服务器传递了一个数据块,这个数据块也称为请求信息。HTTP请求信息由请求行、请求头和请求正文组成。

请求行以请求方法开头并以空格分隔,后面跟着请求URI和协议的版本号,最后以CRLF作为结尾,且在结尾处不允许出现单独的CR或LF字符。请求行的格式如下:

Method Request-URI HTTP-Version CRLF

示例:GET http://www.exp.com/user.html HTTP/1.1 (CRLF)。

除了GET之外,常见的请求方法还有POST、PUT、DELETE、OPTIONS和HEAD等。

请求头可以声明浏览器所支持的语言、浏览器版本以及希望接受的数据类型等。例如,我们使用浏览器访问http://www.example.com时,请求头的内容为:

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

下面简要说明一些常用头域。

Accept:客户端希望接受的数据类型,比如Accept: text/html代表客户端希望接受的数据类型是HTML类型。

Accept-*:指定客户端可接受的内容,比如Accept-Encoding用于指定可接受的编码, Accept-Language用于指定可接受的语言类型。

Content-Type:互联网媒体类型(简称MIME类型),代表具体请求的媒体类型信息(比如text/html代表HTML格式,image/gif代表GIF图片,application/json代表JSON类型)。

Host:指定请求资源的域名(或IP)和端口号,内容为请求URL的原始服务器或网关的位置。

Cookie:可以理解为在HTTP协议下,服务器或其他脚本语言维护客户端信息的一种方式,是保存在客户端(比如浏览器)的文本文件。Cookie中往往包含客户端或者用户的相关信息。

Referer:记录上一次访问的页面地址,也可以理解为标识此次请求的来源URL。

请求头和请求正文之间是一个空行,这个空行表示请求头已经结束,接下来的内容就是请求正文。请求正文包含本次请求提交的查询参数。GET请求的请求正文示例如下:

keyword=德玛西亚&time=1542398562015

上面的请求正文只有一行内容,包含keyword头域和time头域。很多开发者将头域称为字段,这两种叫法都是可以的,在本书中我们使用“头域”这一称呼。实际上,HTTP请求正文还可以包含更多内容,比如使用网页版有道翻译时,请求正文如下:

i: 德玛西亚之翼
from: AUTO
to: AUTO
smartresult: dict
client: fanyideskweb
salt: 15597905976630
sign: b054ae008ed4bd3499606a3cacc55140
ts: 1559790597663
bv: 81eda17dc48cc8bed5d01154f3dd0136
doctype: json
version: 2.1
keyfrom: fanyi.web
action: FY_BY_REALTlME

HTTP响应是指服务器端根据客户端的请求返回的信息。HTTP响应由状态码、响应头和响应正文组成。状态码是一个3位数字(如200),它的第一位代表了不同的响应状态。响应状态共有5种,含义如下。

❑ 1代表信息响应类,表示接收到请求并且继续处理,这类响应是临时响应。

❑ 2代表处理成功响应类,表示动作被成功接收、理解和接受。

❑ 3代表重定向响应类,为了完成指定的动作,必须接受进一步处理。

❑ 4代表客户端错误,表示客户请求包含语法错误或者是不能正确执行的请求。

❑ 5代表服务器端错误,服务器不能正确执行一个正确的请求。

爬虫工程师通常根据响应状态码判断本次请求的状态,并决定后续的处理逻辑。表2-14列出了常见的状态码及其含义。

表2-14 常见的状态码及其含义

要求通信双方都支持对响应头域的扩展,如果存在不支持的响应头域,一般会作为实体头域处理。表2-15列出了响应头域及其含义。

表2-15 响应头域及其含义

响应正文才是请求的最终目的,比如访问网页时响应正文为网页的HTML代码。爬虫爬取的网页数据其实就是服务器端解析请求URL后返回的文本资源,如访问华为官网的首页后,得到的响应正文如图2-21所示。

图2-21 华为官网响应正文部分截图

2.3.4 Cookie

Cookie可以理解为在HTTP协议下,服务器或其他脚本语言维护客户端信息的一种方式,是保存在客户端(比如浏览器)的文本文件,Cookie中往往包含客户端或者用户的相关信息。

1. 背景

在客户端与服务器能够进行动态交互的Web应用程序出现之前,Cookie还没有被熟知,是HTTP的无状态特性促进了Cookie的产生。HTTP的无状态指的是无状态协议,HTTP协议对于事务处理没有记忆能力,缺少状态意味着如果服务器需要与客户端进行多次交互,那么客户端必须在每一次交互时都主动提交身份信息。

面对这个问题,人们想出了两种能够保持HTTP连接状态的技术:Cookie和Session。Cookie是通过客户端来保持状态的解决方案。从定义上来说,Cookie就是由服务器发给客户端的特殊信息,这些信息以文本文件的形式存放在客户端。客户端每次向服务器发送请求的时候,都会携带这些特殊的信息。在登录网站的时候,经常能够看到登录框处有类似“记住我”的选项,如果勾选了它,那么在重新打开网站甚至第二天访问该网站时,就不需要进行烦琐的登录操作了。因为勾选了这个选项之后,网站会将用户的身份信息记录在Cookie中,在浏览器下一次向服务器发起请求的时候,会自动携带Cookie信息,从而实现自动登录的功能。

Cookie通过在客户端存储身份信息的方式与服务器保持状态,而Session通过服务器来保持状态。Session对象会存储特定用户会话所需的属性及配置信息。当用户在应用程序的Web页面之间跳转时,存储在Session对象中的变量不会丢失,会在整个用户会话中一直存在下去。如果用户在访问Web网页的时候还没有Session,则Web服务器将自动创建一个Session对象,当会话过期或被放弃后,服务器也会终止会话。

2. 生存周期与持久性

Cookie在生成时会被指定一个Expire值,该值就是Cookie的生存周期。Cookie在这个周期内是有效的,但是超出周期后就会被清除。如果将Cookie的生存周期设置为0或者负数,那么在你关闭浏览器的时候,Cookie就会被浏览器清除,下一次打开网页时再生成新的Cookie值。这个功能常用于金融类网站,因为需要在浏览器关闭时立即清除用户的身份信息,更好地保证信息安全。像这样即用即清除的Cookie通常称为会话Cookie,而刚才提到的类似“记住我”或“一周内免登录”选项,是将Cookie值保存起来,在生存周期内还可以继续使用,长时间保持用户状态的Cookie称为持久Cookie。

3. 属性和结构

Cookie由变量名称和值组成,其中变量既有标准的变量,也有自定义格式的变量,具体格式如下:

NAME=VALUE;Expires=DATE;Path=PATH;Domain=DOMAIN_NAME;SECURE

❑ NAME和VALUE是Cookie的名称与值。

❑ Expires通常是一个日期变量,格式为DD-MM-YY HH:MM:SS GMT。正如刚才所说,Expires决定了Cookie的生存周期,值为空就代表这是一个会话Cookie,即Cookie文件将随着浏览器的关闭而自动消失。

❑ Path属性规定了在Web服务器上,哪些路径下的页面可以获取服务器设置的Cookie。一般情况下,如果用户输入的URL的路径部分从第一个字符开始包含Path属性所定义的字符串,浏览器就认为通过检查。如果Path属性的值为“/”,则Web服务器上所有的WWW资源均可读取该Cookie。该项设置是可选的,如果省略,则Path的属性值为Web服务器传给浏览器的资源路径名。

❑ Domain指定了可以访问Cookie信息的域名并且它是一个只写变量。它规定了哪些Internet域中的Web服务器可以读取浏览器所存的Cookie,即只有来自这个域的页面才可以使用Cookie中的信息。这项设置是可选的,如果省略它,Cookie的属性值为该Web服务器的域名。比如将Domain的值设为taobao.com,那么所有类似m.taobao.com或者taobao.com的页面就可以访问Cookie中的信息。

❑ SECURE:如果在Cookie中标记该变量,表明只有当浏览器和Web服务器之间的通信协议为加密认证协议时,浏览器才向服务器提交相应的Cookie。当前这样的协议只有一种,即HTTPS。

小技巧Cookie的属性可以搭配使用,比如借助Domain和Path这两个属性,就可有效地控制Cookie文件被访问的范围。

4. 产生过程

在正常情况下,Cookie值可以由服务器端或JavaScript代码设定。在客户端第一次向服务器端发起请求之前,客户端是不会有Cookie值的。当客户端的请求到达服务器端后,服务器端可以将Cookie值写在响应头中并返回给客户端,或者客户端工具(如浏览器)在渲染页面时,由页面中的JavaScript代码生成Cookie值。双端交互及Cookie的产生过程如图2-22所示。

图2-22 双端交互及Cookie的产生过程

服务器生成Cookie值并将其添加到响应头中返回给浏览器,浏览器检测到响应头中的Set-Cookie头域后将对应的Cookie值保存起来,而后每一次请求都会自动携带对应的Cookie,除非Cookie过期或者被清除。

除了服务端在响应头中使用Set-Cookie头域向客户端发送Cookie这种方式外,还可以通过JavaScript代码设置Cookie,比如:

document.cookie = 'async=569cls8fs2';

5. 应用场景

在Web应用中,Cookie常常被用来记录和验证用户的身份信息,有些应用还会将Cookie作为过滤“垃圾流量”的验证条件。图2-23是一个登录框,当用户输入账号和密码并点击“登录”按钮时,用户身份信息就会被发送到服务器端进行校验。

图2-23 登录框

当用户身份通过校验后,服务器端会根据用户身份生成Cookie值,并存放在响应头的Set-Cookie头域返回给客户端。客户端在后续请求网页时无须重新登录,只需要携带这个Cookie值。

2.3.5 了解HTTPS

由于HTTP协议以明文方式发送请求正文,并且不提供任何方式的数据加密,所以攻击者可以截取Web浏览器和网站服务器之间的传输报文,从而读取传输的信息。因此,HTTP协议不适合传输敏感信息,如身份证号、登录密码等。

超文本传输安全协议(Hypertext Transfer Protocol Secure,简称HTTPS或HTTPS协议)正是为了解决HTTP协议的缺陷而生的。它在HTTP协议的基础上加入了安全套接层(Secure Sockets Layer,简称SSL协议)和传输层安全(Transport Layer Security,简称TLS)协议,SSL协议依靠证书来验证服务器的身份,并为浏览器和服务器之间的通信加密。HTTPS协议是以安全为目标的HTTP通道,被广泛应用于万维网上的安全敏感通信场景,例如资金交易和网络支付场景。HTTPS协议和HTTP协议的区别主要为以下几点。

❑ HTTP协议不需要证书,而HTTPS协议需要到CA申请证书,大部分证书不是免费的。

❑ HTTP是超文本传输协议,信息以明文方式传输,HTTPS则是具有安全性的SSL加密传输协议。

❑ HTTP协议和HTTPS协议使用的是完全不同的连接方式,所用端口也不一样,前者的默认端口为80,后者的默认端口为443。

❑ HTTP协议的连接很简单,并且是无状态的;HTTPS协议是由SSL协议和HTTP协议构建的可进行加密传输、身份认证的网络协议,也是无状态的。

SSL协议及其继任者TLS协议都是为网络通信提供安全服务的安全协议。TLS协议与SSL协议在传输层对网络连接进行加密。SSL协议提供的服务主要有以下几种。

❑ 认证客户端和服务器端,确保数据发送到正确的地方。

❑ 加密数据,防止数据中途被窃取。

❑ 维护数据的完整性,确保数据在传输过程中不被改变。

2.3.6 认识WebSocket

WebSocket是一种在单个TCP连接上进行全双工通信的协议,被广泛应用于对数据实时性要求较高的场景,如体育赛事播报、股票走势分析、在线聊天等。WebSocket通信协议于2011年被IETF定为标准RFC6455(详见https://tools.ietf.org/html/rfc6455),并由RFC7936补充规范。RFC6455共有14个部分,包括WebSocket协议背景与介绍、握手、设计理念、术语约定、双端要求、掩码以及连接关闭等内容。

WebSocket协议使客户端和服务器端之间的数据交换变得更加简单,它允许交互双方创建持久连接,同时支持服务器端主动向客户端推送数据。

在WebSocket协议出现之前,如果Web应用想要实现消息推送与实时数据展示功能,那么需要使用轮询的手段。轮询指的是客户端以特定的时间间隔向服务器端发出HTTP请求,服务器端返回最新的数据给客户端的过程。这种传统模式的缺点很明显,客户端需要不断地向服务器端发出请求,而HTTP请求可能包含较长的头部,但其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽资源。

在这种情况下,HTML5定义了WebSocket协议。WebSocket协议在实现相同功能的情况下可以更好地节省服务器资源,并且能够让双端进行实时通信。在数据传输方面,它对二进制的支持和数据压缩也比HTTP协议更好。

2.3.7 WebSocket握手

与HTTP协议不同的是,WebSocket协议只需要发送一次连接请求。连接请求的完整过程被称为握手,即客户端为了创建WebSocket连接而向服务器端发送特定的HTTP请求并声明升级协议。WebSocket是独立的、创建在TCP上的协议,双端通过HTTP/1.1协议进行握手,握手成功后才会转为WebSocket协议。

使用WebSocket协议进行通信时,客户端与服务器端的交互顺序如下。

(1) 客户端发起握手请求。

(2) 服务器端收到请求后验证并返回握手结果。

(3) 连接建立成功,可以互相发送消息。

关于握手的标准,在协议中有相关的说明:

The opening handshake is intended to be compatible with HTTP-based server-side software and intermediaries, so that a single port can be used by both HTTP clients talking to that server and WebSocket clients talking to that server. To this end, the WebSocket client’s handshake is an HTTP Upgrade request :

❑ GET /chat HTTP/1.1.

❑ Host: server.example.com.

❑ Upgrade: websocket.

❑ Connection: Upgrade.

❑ Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==.

❑ Origin: http://example.com.

❑ Sec-WebSocket-Protocol: chat, superchat.

❑ Sec-WebSocket-Version : 13.

In compliance with [RFC2616], header fields in the handshake may be sent by the client in any order, so the order in which different header fields are received is not significant.

上方协议内容约定,握手由客户端发起,握手时使用的协议是HTTP协议而非WebSocket协议。握手时发出的请求叫作升级请求,客户端在握手阶段通过Connection和Upgrade这两个头域告知服务器端,要求将通信的协议升级为WebSocket,对应头域为:

Connection: Upgrade
Upgrade: websocket

Sec-WebSocket-Version和Sec-WebSocket-Protocol这两个头域表明通信版本和协议约定, Sec-WebSocket-Key则作为一个防止无端连接的保障(其实在实际应用中并没有什么保障作用,因为key的值完全由客户端控制,服务器端并无验证机制),其他的头域与HTTP协议中头域的作用一致。

假设客户端发出一个符合握手约定的HTTP请求,那么服务器端需要先对信息进行验证,并将握手结果回复给客户端。服务器端返回的握手结果包含状态码和当前所用的协议,返回信息的含义如下。

❑ Status Code代表本次握手结果,状态码中的101表示连接成功。

❑ Connection和Upgrade表明当前所用协议。

❑ Sec-WebSocket-Accept是经过服务器确认并加密过后的Sec-WebSocket-Key。

如果客户端收到如下响应,那么说明握手成功:

Status Code: 101 Web Socket Protocol Handshake
Sec-WebSocket-Accept: T5ar3gbl3rZJcRmEmBT8vxKjdDo=
Upgrade: websocket
Connection: Upgrade

2.3.8 数据传输与数据帧

双方握手成功并确认协议后,就可以互相发送信息了。WebSocket协议对传输规范也作了对应的约定(其中…代表省略部分):

In the WebSocket Protocol, data is transmitted using a sequence of frames. To avoid confusing network intermediaries (such as intercepting proxies) and for security reasons

...

which together define the "Payload data".Certain bits and opcodes are reserved for future expansion of the protocol.

上方协议内容约定,在WebSocket协议中使用帧传输数据时,出于安全原因,客户端发送的消息必须进行掩码,如果服务器收到未掩码的帧,则关闭连接。服务器发送给客户端的消息则不进行掩码,如果客户端收到掩码的帧,就关闭连接。

帧协议用操作码定义帧类型、有效载荷长度、指定位置的扩展数据和应用程序数据,它们共同定义“有效载荷数据”,还保留了一些位和操作码,用于将来的扩展协议。帧协议允许将每个消息分成一帧或多帧,在发送到客户端后由客户端将信息拼接完整。数据帧的格式如图2-24所示。

图2-24 数据帧格式

数据帧由FIN、RSV1、RSV2、RSV3、opcode、MASK、Payload len、Masking-key和Payload Data等部分组成,含义如下。

❑ FIN:占1位(bit)。

■ 0:不是消息的最后一个分片。

■ 1:是消息的最后一个分片。

❑ RSV1、RSV2和RSV3:各占1位。

在一般情况下全为0。当客户端和服务器端协商采用WebSocket协议扩展时,这3个标志位可以不为0,且值的含义由扩展部分进行定义。如果出现非零的值,且并没有采用WebSocket协议扩展,则连接出错。

❑ opcode:占4位。

■ %x0:一个延续帧。当opcode为0时,表示本次数据传输采用了数据分片,当前收到的数据帧为其中一个数据分片。

■ %x1:这是一个文本帧(text frame)。

■ %x2:这是一个二进制帧(binary frame)。

■ %x3~%x7:保留的操作代码,用于后续定义的非控制帧。

■ %x8:连接断开。

■ %x9:这是一个心跳请求(ping)。

■ %xA:这是一个心跳响应(pong)。

■ %xB~%xF:保留的操作代码,用于后续定义的控制帧。

❑ MASK:占1位。

■ 0:不对数据载荷进行掩码异或操作。

■ 1:对数据载荷进行掩码异或操作。

❑ Payload len:占7位或(7 + 16)位或(7 + 64)位。

■ 0~126:数据载荷的长度等于该值。

■ 126:后续2字节代表一个16位的无符号整数,该无符号整数的值为数据的长度。

■ 127:后续8字节代表一个64位的无符号整数(最高位为0),该无符号整数的值为数据的长度。

❑ Masking-key:占0字节(Byte)或4字节。

■ 当MASK为1时,携带了4字节的Masking-key。

■ 当MASK为0时,没有Masking-key。

掩码算法:按位进行循环异或运算,先对该位的索引取模来获得Masking-key中对应的值x,然后将该位与x进行异或运算,而得到真实的数据。注意,掩码的作用并不是防止数据泄露,而是防止早期版本的协议中存在代理缓存污染攻击(proxy cache poisoning attacks)等问题。

❑ Payload Data:载荷数据。

双端接收到数据帧之后,就可以根据数据帧各个位置的值进行处理或信息提取。

服务器端与客户端都应该使用WebSocket协议规范中规定的格式,将数据转化为帧后再发送,而接收方在收到数据帧后需要按照规定的格式进行解包和数据提取。

2.3.9 WebSocket连接

WebSocket协议的数据传输是通过网络进行的,所以我们可以在Chrome浏览器的开发者工具中找到Network面板,然后查看WebSocket的连接信息及传输的数据。

ECHO TEST是基于WebSocket应用程序和服务器的测试平台,我们可以通过观察测试平台中的网络信息来加深对WebSocket的理解。测试平台的网址为http://www.websocket.org/echo.html,打开网址后唤起Chrome开发者工具并切换到Network面板,然后点击页面中的Connect按钮,页面会主动向测试平台的WebSocket服务器发起握手,此时可以看到网络请求记录中有一条状态码为101的记录,如图2-25所示。

图2-25 ECHO TEST页面及Network面板

接着点击Send按钮,此时客户端向服务器端发送一条信息,服务器端会立即返回内容相同的信息。我们可以在网络请求记录中选中此条请求,然后在右侧的面板中选择Frames,就可以看到WebSocket双端传输的数据内容了,如图2-26所示。

图2-26 双端传输的数据

图中箭头向上的数据是客户端发送给服务器端的,箭头向下的是服务器端推送给客户端的。除了可以使用浏览器连接以外,还可以使用其他的客户端连接,只要握手时发送的请求头符合要求即可。我们可以使用aiowebsocket这个第三方库来连接测试平台的WebSocket服务器。aiowebsocket是一个用Python代码编写的开源库,它非常轻量并且很灵活,GitHub网址为https://github.com/asyncins/aiowebsocket。使用前,我们可以通过Python包管理工具pip安装它,命令如下:

$ pip install aiowebsocket

然后新建一个名为wsocket.py的文件,并将以下代码写入文件:

import asyncio
import logging
from datetime import datetime
from aiowebsocket.converses import AioWebSocket

async def startup(uri):
    async with AioWebSocket(uri) as aws:
        # 初始化 aiowebsocket 库的连接类
        converse = aws.manipulator
        # 设定需要向服务器发送的信息
        message = b'AioWebSocket - Async WebSocket Client'
        while True:
            # 不断地向服务器发送信息,并打印输出信息的内容和时间
            await converse.send(message)
            print('{time}-Client send: {message}'.format(time=datetime.now()
                  .strftime('%Y-%m-%d %H:%M:%S'), message=message))
            # 不断地读取服务器推送给客户端的信息,并打印输出信息的内容和时间
            mes = await converse.receive()
            print('{time}-Client receive: {rec}'.format(time=datetime.now()
                  .strftime('%Y-%m-%d %H:%M:%S'), rec=mes))

if __name__ == '__main__':
    # 设定远程服务器地址
    remote = 'wss://echo.websocket.org'
    try:
        # 开启事件循环,调用指定的方法
        asyncio.get_event_loop().run_until_complete(startup(remote))
            except KeyboardInterrupt as exc:
            logging.info('Quit.')

保存后可以使用以下命令运行wsocket.py文件:

$pyhton wsocket.py

终端会输出如图2-27所示的双端互推消息。

图2-27 双端互推消息

在运行结果中,send代表客户端发送给服务器端的消息,而receive代表服务器端推送给客户端的消息。

2.3.10 连接保持

WebSocket协议可以保持双端持久连接,那么一旦连接就不会断开了吗?

服务器端开发者如果不希望所有连接都长期打开,可以定时给客户端发送一个Ping帧,客户端收到Ping帧后必须回复一个Pong帧。如果客户端不响应,那么服务器端就可以主动断开连接。

除此之外,还可以使用其他的方法,比如客户端连接后,需要先向服务器端发送验证信息,如果能够通过服务器端验证,则保持连接,否则立即关闭。

2.3.11 小结

WebSocket协议可以让服务器端与客户端双向通信,并且保持长久连接。握手时使用的是HTTP协议,握手成功后才升级为WebSocket协议。要注意的是,WebSocket协议规范只作为参考,并不是强制实现,所以服务器端和客户端的连接条件和消息格式通常由服务器端开发者决定。这意味着服务器端可以在握手时对客户端进行身份校验,在消息传递或数据帧方面也可以设计一些用于反爬虫的方法。服务器端可以以任何理由关闭连接,开发者常常利用这些特点限制爬虫连接或者获取数据。