8 爬取EOS whales网站出现的ssl验证问题以及无法建立websocket握手问题(北京大学出版社的《python 爬虫与反爬虫开发》书中错误)
案例来源于北京大学出版社的《python 爬虫与反爬虫开发》
1.对于ssl验证问题的解决
采取firefox打开eos whales,点击小锁标志,查看证书,下载pem证书,然后利用openssl命令将pem证书转变为cer证书,并安装到本机中,之后使用AIOwebsocket的方法,就不会报出ssl验证失败的问题。
因为在Aiowebsocket的查询证书过程中,通过调试,发现其去windows的ca和root下寻找证书。
对于其他可能用到ssl验证的api,上述方法可能无法解决,需要关闭ssl验证,或者调试程序,看看去哪里寻找证书。
2.对于无法建立websocket握手问题
这个问题,暂时无法解决。
发现EOS whales网站的当前操作与书中案例执行时的操作不一致。
在模拟握手过程中,执行send(2probe)操作后,可以收到3probe的回复。但是后续send(5)以及send一个42开头的字符串,都无法继续获取服务器端的推送数据。
其中42开头的数据中:
42["message","{\"_url\":\"/admin/subscribe_event\",\"_method\":\"POST\",\"_headers\":{\"content-type\":\"application/json\"},\"cid\":\"9f280170-2d9c-11ec-9d3f-f5367634451a\",\"event\":\"recent_performance_history\",\"lang\":\"zh-CN\"}"]
其中,包括一个cid代码。
后来查看common.js代码文件,发现有一个生成uuid的js代码片段。
var CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split(''); function uuid(len, radix){ var chars = CHARS, uuid = [], i; radix = radix || chars.length; if (len) { for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random()*radix]; } else { var r; uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-'; uuid[14] = '4'; for (i = 0; i < 36; i++) { if (!uuid[i]) { r = 0 | Math.random()*16; uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r]; } } } return uuid.join(''); }
通过在python中仿真出这段代码:
CHARS = split_str('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'); radix = len(CHARS) uuid={} uuid[8]=uuid[13] = uuid[18] = uuid[23] = '-' uuid[14] = '4'; print(uuid) for i in range(0,36): # print (i) if not (i in uuid.keys()): # print (i) r = 0 | int(random.random()*16) # print (r) if(i==19): uuid[i]=CHARS[(r&0x3)|0x8] else: uuid[i]=CHARS[r] # uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r]; # } # } # print (CHARS) # print (radix) # print(uuid) CID='' for i in range(0,36): CID=CID+uuid[i] # print (CID) return CID
后来实际运行时,发现我写的python生成的CID和Js中生成的UUID在风格上很不一致。
所以,这个CID的生成,目前较难通过分析JS去破解。
除了CID以外,WS链接中有一个sid的参数,wss://api-v1.eosflare.io/socket.io/?EIO=3&transport=websocket&sid=
K1ngCFhbUMrnQ-jMCevK,
我们通过分析XHR,发现是在第一个执行中,获取SID,后来通过python生成了SID。
同时,注意在cookie中要设置session id。
后续发现,每次刷新网页,里面都会有个t的参数存在变化。
同时,观察发现,这些XHR交互过程中,第一个post将cid发送到服务器
并获取到返回状态ok
继续执行get,会获得一个成功的返回状态
总结:
个人利用websocket发送握手数据,失败的两种可能原因是:
1.生成的CID不符合要求。
2.在建立ws链接之前,通过post和get,将cid发送到服务器,并获取返回状态。这一步使用requests的时候,无论如何设置cookie(主要是设置session id号)还是如何模拟xhr的操作,都无法返回成功状态。
报错的结果就是:500。可能是一种反爬措施,由于前述t这个参数造成。而t一直在变化。所以,你用requests去访问,去试图建立成功状态,都会由于t的变化,导致无法成功模拟。
可能是因为这一步不成功,就相当于cid未在服务端注册,因此导致握手无法成功。
对于js导致的爬虫失败和反爬措施,只能采取selenium,是绝杀了。虽然不太好,但是只能这样。
关于本书的看法:
花了钱买了书,也进了交流群,但是连续两天,对我提出的问题,作者不回应(作者看到了问题),这就让人很无语了。
书中第七章勘误代码:
案例1,乐鱼体育的正确调试代码
import asyncio from aiowebsocket.converses import AioWebSocket import websocket import requests import json import math import time def get_token(): """ 获取加密字符串,将其拼接到websocket协议的url上 :return: token """ url = "https://live.611.com/Live/GetToken" #设置header属性 header={ "User-Agent":"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3877.400 QQBrowser/10.8.4506.400" ,"Referer":"http://m.611.com/Match/Index" ,"Connection":"keep-alive" ,"Cookie":"UM_distinctid=17c7d64b47c3c2-09f412d2404148-3354427f-144000-17c7d64b47d549; CNZZDATA1279044329=299416022-1634189736-%7C1634189736; ASP.NET_SessionId=kkym2rc4wqj05jrrwwjrogzu" ,"Host":"m.611.com" } #response = requests.get("http://m.611.com/Match/Index",headers=header) response = requests.get(url,headers=header) if response.status_code == 200: data = json.loads(response.text) token = data["Data"] return token else: print("请求错误") async def startup(url): async with AioWebSocket(url) as aws: converse = aws.manipulator # # 客户端给服务端发送消息 _time = math.floor(time.time()) * 1000 info = {'chrome': 'true', 'version': '70.0.3538.25', 'webkit': 'true'} message1 = { "command": "RegisterInfo", "action": "Web", "ids": [], "UserInfo": { "Version": str([_time]) + json.dumps(info), "Url": "https://live.611.com/zq" } } message2 = { "command": "JoinGroup", "action": "SoccerLive", #"ids": [303499393], "ids":[] } message3 = { "command": "JoinGroup", "action": "BasketSoccerLive", #"ids": [303499393], "ids":[] } # # await (time.sleep(2)) print("------------向服务的发起握手") # await asyncio.sleep(3) # await send(converse,json.dumps(message1)) await converse.send(json.dumps(message1)) await converse.send(json.dumps(message2)) await converse.send(json.dumps(message3)) while True: mes = await converse.receive() print("---------正在接收服务端主动推送的信息") print(mes) # async def send(converse,message): # converse.send(message) if __name__ == '__main__': url = "wss://push.611.com:6119/{}".format(get_token()) print(url) try: #使用websocket # ws = websocket.create_connection(url) # # 客户端给服务端发送消息 # _time = math.floor(time.time()) * 1000 # info = {'chrome': 'true', 'version': '70.0.3538.25', 'webkit': 'true'} # message1 = { # "command": "RegisterInfo", # "action": "Web", # "ids": [], # "UserInfo": { # "Version": str([_time]) + json.dumps(info), # "Url": "https://live.611.com/zq" # } # } # message2 = { # "command": "JoinGroup", # "action": "SoccerLive", # "ids": [303499393] # } # message3 = { # "command": "JoinGroup", # "action": "BasketSoccerLive", # "ids": [303499393] # } # # await (time.sleep(2)) # print("------------向服务的发起握手") # # converse.send(str(message1)) # # converse.send(str(message2)) # # converse.send(str(message3)) # ws.send(json.dumps(message1)) # ws.send(json.dumps(message2)) # ws.send(json.dumps(message3)) # while True: # # mes = converse.receive() # result=ws.recv() # print(result) # print("---------正在接收服务端主动推送的信息") # aws=AioWebSocket(url) # converse = aws.manipulator loop=asyncio.get_event_loop() task = loop.create_task(startup(url)) loop.run_until_complete(task) # asyncio.run(startup(url)) except Exception as ex: print(ex) pass finally: pass # loop.stop(); # asyncio.set_event_loop(None)
注意:下面三句话是正确的。原书案例中的已经无法正确跑通。同时我的代码中提供了websocket的方式,有区别于Aiowebsocket
loop=asyncio.get_event_loop()
task = loop.create_task(startup(url))
loop.run_until_complete(task)
案例2,无法爬取eos whales网站的无法跑通代码(希望有人能够给它调通)
import asyncio from datetime import datetime from aiowebsocket.converses import AioWebSocket import ssl import websocket import requests import json import re import random from requests.cookies import RequestsCookieJar # ssl._create_default_https_context = ssl._create_unverified_context() def split_str(s): return [ch for ch in s] def get_CID(): # 下面是生成CID的JS代码。 # var CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split(''); # function uuid(len, radix){ # var chars = CHARS, uuid = [], i; # radix = radix || chars.length; # if (len) { # for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random()*radix]; # } else { # var r; # uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-'; # uuid[14] = '4'; # for (i = 0; i < 36; i++) { # if (!uuid[i]) { # r = 0 | Math.random()*16; # uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r]; # } # } # } # return uuid.join(''); # } ###需要将上面的代码改为python代码。 ###通过实际调试,发现len和radix都未传入值 CHARS = split_str('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'); radix = len(CHARS) uuid={} uuid[8]=uuid[13] = uuid[18] = uuid[23] = '-' uuid[14] = '4'; print(uuid) for i in range(0,36): # print (i) if not (i in uuid.keys()): # print (i) r = 0 | int(random.random()*16) # print (r) if(i==19): uuid[i]=CHARS[(r&0x3)|0x8] else: uuid[i]=CHARS[r] # uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r]; # } # } # print (CHARS) # print (radix) # print(uuid) CID='' for i in range(0,36): CID=CID+uuid[i] # print (CID) return CID def get_ready(SID): """ 建立会话的一些过程 """ url = "https://api-v1.eosflare.io/socket.io/?EIO=3&transport=polling&t=No3LAGI&sid="+SID #设置header属性 header={ "User-Agent":"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3877.400 QQBrowser/10.8.4506.400" ,"Referer":"hhttps://eosflare.io/" ,"Connection":"keep-alive" ,"Cookie":"_ga=GA1.2.73969542.1634216434; _gid=GA1.2.678520107.1634216434;_gat=1; io="+SID # ,"content-type":"text/plain;charset=UTF-8" # ,"content-length":"218" } #response = requests.get("http://m.611.com/Match/Index",headers=header) cookie_jar = RequestsCookieJar() cookie_jar.set("_ga", "GA1.2.73969542.1634216434", domain="https://eosflare.io") cookie_jar.set("_gid", "GA1.2.678520107.1634216434", domain="https://eosflare.io") cookie_jar.set("io",SID, domain="https://eosflare.io") cookie_jar.set("_gat","1", domain="https://eosflare.io") payload='214:42["message","{\"_url\":\"/admin/subscribe_event\",\"_method\":\"POST\",\"_headers\":{\"content-type\":\"application/json\"},\"cid\":\"de70974e-70be-4ee0-9668-1230f3a54490\",\"event\":\"info\",\"lang\":\"zh-CN\"}"]' print (payload) # response = requests.get(url,headers=header,cookies=cookie_jar) response = requests.post(url,headers=header,data=payload) if response.status_code == 200: #post数据 # url="https://api-v1.eosflare.io/socket.io/?EIO=3&transport=polling&t=No37Orm.0&sid=aHVo8UA0B0INjHtMCeQ4" print(response.text) else: print("请求错误") def get_SID(): """ 获取加密字符串,将其拼接到websocket协议的url上 :return: token """ url = "https://api-v1.eosflare.io/socket.io/?EIO=3&transport=polling&t=No3LACi" #设置header属性 header={ "User-Agent":"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3877.400 QQBrowser/10.8.4506.400" ,"Referer":"hhttps://eosflare.io/" ,"Connection":"keep-alive" ,"Cookie":"_ga=GA1.2.73969542.1634216434; _gid=GA1.2.678520107.1634216434; io=Y74WllNMsh0UAdQyCbZ5" } #response = requests.get("http://m.611.com/Match/Index",headers=header) response = requests.get(url,headers=header) if response.status_code == 200: p1 = re.compile(r'[{](.*?)[}]', re.S) #最小匹配 text=(re.findall(p1, response.text)) text='{'+text[0]+'}' # print(text) data = json.loads(text) token = data["sid"] return token else: print("请求错误") async def startup(uri): async with AioWebSocket(uri) as aws: # aws.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE}) converse = aws.manipulator # 客户端给服务端发送消息 await converse.send(2) while True: try: mes = await converse.receive() print(mes) except Exception as ex: print (ex) continue # print(mes) # print('{time}-Client receive: {rec}' # .format(time=datetime.now().strftime('%Y-%m-%d %H:%M:%S'), rec=mes)) if __name__ == '__main__': remote = 'wss://api-v1.eosflare.io/socket.io/?EIO=3&transport=websocket&sid=' # url="https://eosflare.io/" try: # xxx=ssl.get_server_certificate(("34.117.51.23",443),ca_certs="C:\\Users\\xiaojie\\anaconda3\\Lib\\site-packages\\certifi\\cacert.pem") print(remote) # 使用websocket # CID=get_CID() # CID='de70974e-70be-4ee0-9668-1230f3a54490' SID=get_SID() print(SID) get_ready(SID) # 客户端给服务端发送消息 remote=remote+SID # remote='wss://api-v1.eosflare.io/socket.io/?EIO=3&transport=websocket&sid=ZJLHtYunzyNXMRxdCb4d' print(remote) # ws = websocket.create_connection(remote,sslopt={"cert_reqs": ssl.CERT_NONE}) print("------------向服务的发起握手") ws.send("2probe") result=ws.recv() print(result) # message1 = '42["message","{\"_url\":\"/admin/subscribe_event\",\"_method\":\"POST\",\"_headers\":{\"content-type\":\"application/json\"},\"cid\":'+'\"'+CID+'\",\"event\":\"recent_performance_history\",\"lang\":\"zh-CN\"}"]' # print (message1) # ws.send(message1) # de70974e-70be-4ee0-a668-1230f3a54490 # 56338e60-2d6c-11ec-99a8-93befb71502e while True: ws.send("5") result=ws.recv() print(result) print("---------正在接收服务端主动推送的信息") # loop=asyncio.get_event_loop() # task = loop.create_task(startup(remote)) # loop.run_until_complete(task) except Exception as ex: pass
里面已经包括生成UUID操作,以及模拟ws前获取sid操作,和模拟ws前上传cid操作。但是如前所述,无法成功建立握手。只能收到“3probe”的推送,后续消息全无。
心得
为了这个问题,我花了两天时间。而实际上大可不必。
对于一个人而言,他的经验有限,他看的书的经验也有限。当经验有限的时候,一定钻于一个问题,苦思而不得其解的时候,不如放下这个问题,继续学习经验,当眼光更高,视野更阔的时候,
可能就迎刃而解。
书的作者也是个小年轻,闻道有先后,术业有专攻,不回复就算了,拽老资格就没必要。
在一个错误的方法中苦思不得其解,如同坐井观天,与青蛙论道,与夏虫言冬。
以后要换一种学习思路,切不可浪费时间,切记切记。