Websocket 爬虫简单实践
故常无欲,以观其妙,常有欲,以观其徼。——道德经
前言:说起爬虫,笔者在之前也简单的写过一些,由于并没有深入,所以也不好展开写“爬虫大全”此类的标题,只是把我遇到的问题和解决办法总结记录下来。 在采集某些数据实时性很高的网站时,通过爬虫分析该网站的网页似乎不是较好的解决办法,一方面耗时长,另一方面容易给服务提供方造成线路拥堵。当我们对网页进行分析时,容易发现一些实时性很高的数据都是通过websocket方式由服务器发送至网页端(客户浏览器)的。因此,如果我们可以直接和被采集服务器进行websocket通信,那么就能省下很长时间。 开始编写具体代码之前,我们需要明白websocket是一种建立在TCP连接上的一种全双工通信协议。首先,websocket和HTTP都可以保持长连接,但是有以下区分:
- HTTP的长连接:HTTP/1.1通过使用
Connection:keep-alive
进行长连接。在一次 TCP 连接中可以完成多个 HTTP 请求,但是对每个请求仍然要单独发 header,Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。这种长连接是一种“伪链接”,而且只能由客户端发送请求,服务端响应。 - WebSocket的长连接,是一个全双工的连接,可由服务端主动发起信息。长连接第一次TCP链路建立之后,后续数据可以双方都进行发送,不需要发送请求头。
HTTP/1.1中双方并没有建立正真的连接会话,服务端可以在任何一次请求完成后关闭。WebSocket 它本身就规定了是正真的、双工的长连接,两边都必须要维持住连接的状态。 由于以上特性,websocket可以在客户端不不进行操作的情况下收到服务端发来的信息。 在此之前,仍有很多网站采用ajax轮询(长轮询)的方式请求数据。但是这种方式会存在资源占用高,数据实时性低的问题。因此websocket是目前传输实高实时性数据的较好的选择方法。 建立一个websocket连接的步骤是:建立连接-发送数据-断开连接 建立连接的过程中客户端要发送请求报文,服务端回复相应报文,相当于双方进行了约定,此约定下双方都表明“我和你创建的是websocket连接”。采用python进行数据爬取的时候我们并不需要去关注这个连接是怎么创建的,以及双方的报文是什么,只需要导入依赖库以及知道目标连接即可。websocket的连接形式为:ws://xxxxx或者wss://xxxxx,以下介绍适用于websocket爬虫
import websocket def connect(): ws=websocket.WebsocketApp(target url,on_message=onmessage,on_open=onopen,on_error=onerror,…..) ws.send(‘hello’) ws.run_forever() #表示一直运行 def on_message(ws,message): print(message) def on_open(ws): print(“connected”) ……
以上代码中是通过websocket库的WebsocketApp类来创建连接,该类中的on_message,on_open,on_error,on_ping,on_pong等都是一系列的操作,此处主要了解on_open,on_message,ws.send()即可。 on_open为连接建立可以开始通信后的执行函数,常见的连接中有以下两种功能: 1,认证:由于websocket是由客户端发送的,理论上所有的客户端都可以任意链接,但是某些服务端只给某些客户提供服务,因此会加一个认证环节,类似于网站的密码输入等。 2,订阅:某些服务器在建立连接后一定时间内如果没有订阅,则会主动关闭连接。(这个环节通过ws.send()来完成也可以) on_message是在收到服务器的数据后的执行函数,注:一般情况下简单的操作可以放在这里面直接执行,但是如果是某些耗时长,资源占用大的数据请可以使用queue来顺序执行。 ws.send() 发送数据函数,在建立连接后可以通过此函数发送数据,通常是dict的str形式,类似于:str({“name”:”wicos”}) 建立连接后一个重要的任务就是对数据进行处理,如果你做到了这一步可能会遇到以下两种情况: 1,接收的数据为字符串 处理办法:如果是类似json的字符串,可以json.loads()处理,总结为一句话:该怎么办就怎么办 2,接收的数据为 Binary Message (二进制消息) 处理办法:
import zlib #假设我们接收到的消息为message content = zlib.decompress(message,16 + zlib.MAX_WBITS) to_str = str(content, encoding=”utf-8”)
通过zlib库可以进行转换,此处不得不感叹,万能的大蟒蛇(python)啊…… 做到这里有些朋友可能会问,为什么一段时间后连接会莫名其妙的断开?那么你需要了解websocket的心跳包,心跳包的工作就是在连接过程中,如果长时间没有数据往来,那么则发送心跳包告诉服务端“我想保持这个连接”。这个询问客户端是否在线的操作叫:ping,客户端回复“我在线”的操作叫:pong。往往是服务端主动发送ping客户端被动回复pong。有人可能会问:为什么在我有数据通信的情况下仍然会收到服务端发送的ping?其实在目前的实际应用中,服务端如果仅仅在客户端长时间无数据发送的情况下发送ping,反而会耗费资源,不如服务端规定在一定时间内循环发送ping,客户端如果回复,则代表在线,如果不回复则代表掉线。 以某网站为例,在建立连接,订阅接收数据后,每隔一秒会收到服务端发来的ping,该ping的内容是{“ping”:”xxx”},其中xxx代表时间戳。客户端回复的pong为:{“pong”:”xxx”},其中的xxx代表收到ping的时间戳。 总的websocket流程可以由下图简单的呈现 wicos 2020/4/23