案例来源于北京大学出版社的《python 爬虫与反爬虫开发》

1.对于ssl验证问题的解决

采取firefox打开eos whales,点击小锁标志,查看证书,下载pem证书,然后利用openssl命令将pem证书转变为cer证书,并安装到本机中,之后使用AIOwebsocket的方法,就不会报出ssl验证失败的问题。

因为在Aiowebsocket的查询证书过程中,通过调试,发现其去windows的ca和root下寻找证书。

对于其他可能用到ssl验证的api,上述方法可能无法解决,需要关闭ssl验证,或者调试程序,看看去哪里寻找证书。

2.对于无法建立websocket握手问题

这个问题,暂时无法解决。

发现EOS whales网站的当前操作与书中案例执行时的操作不一致。

8 爬取EOS whales网站出现的ssl验证问题以及无法建立websocket握手问题(北京大学出版社的《python 爬虫与反爬虫开发》书中错误)

 

在模拟握手过程中,执行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。

8 爬取EOS whales网站出现的ssl验证问题以及无法建立websocket握手问题(北京大学出版社的《python 爬虫与反爬虫开发》书中错误)

 

同时,注意在cookie中要设置session id。

后续发现,每次刷新网页,里面都会有个t的参数存在变化。 

8 爬取EOS whales网站出现的ssl验证问题以及无法建立websocket握手问题(北京大学出版社的《python 爬虫与反爬虫开发》书中错误)

 

同时,观察发现,这些XHR交互过程中,第一个post将cid发送到服务器

 8 爬取EOS whales网站出现的ssl验证问题以及无法建立websocket握手问题(北京大学出版社的《python 爬虫与反爬虫开发》书中错误)

 

并获取到返回状态ok

8 爬取EOS whales网站出现的ssl验证问题以及无法建立websocket握手问题(北京大学出版社的《python 爬虫与反爬虫开发》书中错误)

 

继续执行get,会获得一个成功的返回状态

8 爬取EOS whales网站出现的ssl验证问题以及无法建立websocket握手问题(北京大学出版社的《python 爬虫与反爬虫开发》书中错误)

 

个人利用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”的推送,后续消息全无。

 心得

为了这个问题,我花了两天时间。而实际上大可不必。

对于一个人而言,他的经验有限,他看的书的经验也有限。当经验有限的时候,一定钻于一个问题,苦思而不得其解的时候,不如放下这个问题,继续学习经验,当眼光更高,视野更阔的时候,

可能就迎刃而解。

书的作者也是个小年轻,闻道有先后,术业有专攻,不回复就算了,拽老资格就没必要。

在一个错误的方法中苦思不得其解,如同坐井观天,与青蛙论道,与夏虫言冬。

以后要换一种学习思路,切不可浪费时间,切记切记。