爬蟲概要及web微信請求分析

1、爬蟲概要

一、網絡爬蟲是什麼

百度百科:網絡爬蟲(又被稱爲網頁蜘蛛,網絡機器人,在FOAF社區中間,更常常的稱爲網頁追逐者),是一種按照必定的規則,自動地抓取萬維網信息的程序或者腳本。另一些不常使用的名字還有螞蟻、自動索引、模擬程序或者蠕蟲。javascript

通俗的講,爬蟲就是可以自動訪問互聯網並將網站內容下載下來的的程序或腳本,相似一個機器人,能把別人網站的信息弄到本身的電腦上,再作一些過濾,篩選,概括,整理,排序等等。java

網絡爬蟲的英文即Web Spider,是一個很形象的名字。把互聯網比喻成一個蜘蛛網,那麼Spider就是在網上爬來爬去的蜘蛛。網絡蜘蛛是經過網頁的連接地址來尋找網頁,從網站某一個頁面(一般是首頁)開始,讀取網頁的內容,找到在網頁中的其它連接地址,而後經過這些連接地址尋找下一個網頁,這樣一直循環下去,直到把這個網站全部的網頁都抓取完爲止。若是把整個互聯網當成一個網站,那麼網絡蜘蛛就能夠用這個原理把互聯網上全部的網頁都抓取下來。python

二、網絡爬蟲的本質及其基本流程

通常狀況下,用戶獲取網絡數據有如下兩種方式:
   (1)藉助瀏覽器:輸入url --> 提交請求 --> 下載網頁代碼 --> 渲染成頁面
   (2)代碼模擬:模擬瀏覽器發送請求(獲取網頁代碼) --> 提取有用的數據 --> 存儲數據
  • 網絡爬蟲的本質
    • 經過代碼模仿瀏覽器發送請求,即上述方式(2)
  • 基本流程web

    向目標站點發送http請求 --> 獲取響應內容 --> 解析內容 --> 保存數據
  • 從底層剖析爬蟲本質:chrome

瀏覽器本質,socket客戶端遵循Http協議(經過\r\n分割的規範+請求響應斷開鏈接=>無狀態、短鏈接)
            
        url = 'www.cnblogs.com'
            
        sk = socket.socket()
        # 鏈接的過程是:阻塞
        sk.connect((url,80))

        # HTTP協議
        content = "GET /wupeiqi HTTP/1.1\r\nHost: %s\r\nUser-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36\r\n\r\n" %url
        sk.sendall(content.encode('utf-8'))

        # 等待服務端返回內容:阻塞
        data = sk.recv(8096)
        print(data)
        sk.close()

三、Python爬蟲必備的知識

  • python
    • 基礎
    • 網絡編程
  • WEB知識
    • HTML基礎
    • CSS基礎
    • javascript基礎
  • HTTP協議
    • 請求頭(通常狀況下):
      • user-agent:代指用戶的訪問設備
      • cookie:服務端在客戶端(瀏覽器)保留的標記
      • refer:請求來源(從哪裏請求的)
      • host:
      • content-type:
    • 請求體(數據格式)
      • name=alex&age=18
      • {'name':alex,'age':18}
    • 狀態碼(重定向302)
      • 響應頭中的Location
  • 分析HTTP請求(抓包)
    • chrome
    • fiddler

2、web微信基本通訊過程分析

一、微信服務器返回一個會話ID(uuid)

web微信採用掃描二維碼登陸,不使用用戶名密碼登陸,所以微信服務器須要分配一個惟一的會話ID,用來標識當前的一次登陸。GET請求的host及參數以下:
qcode_host = "https://login.wx2.qq.com/jslogin"

# GET請求參數
params = {
    'appid':"wx782c26e4c19acffb",
    'redirect_uri':"https%3A%2F%2Fwx2.qq.com%2Fcgi-bin%2Fmmwebwx-bin%2Fwebwxnewloginpage",
    'fun':"new",
    'lang':"zh_",
    '_':ctime
}

# 發送GET請求
res = requests.get(
    url=qcode_host,
    params=params
)
請求成功,服務器會返回以下的字符串:

- window.QRLogin.code = 200; window.QRLogin.uuid = "SCrc8SK6ZG=="

- 200表示請求成功,字符串"SCrc8SK6ZG=="是此次請求微信服務器返回的會話ID(uuid)。

二、經過會話ID得到登陸二維碼

經過瀏覽器或者代碼發送以下GET請求,獲取登陸二維碼:https://login.weixin.qq.com/l/SCrc8SK6ZG==

三、經過長輪詢檢測是否已掃描二維碼,經過響應狀態判斷是否確認登陸

得到二維碼後,須要用戶在手機端掃描二維碼,並確認是否登陸,此時瀏覽器或代碼並不知道用戶什麼時候操做,所以只有輪詢,爲了提升輪訓效率,web微信採用長輪詢的方式,即向以下地址發送GET請求:

check_host = "https://login.wx.qq.com/cgi-bin/mmwebwx-bin/login"
params = {
    'loginicon':'true',
    'uuid':qcode,
    'tip':'0',
    'r':'-1036255891',
    '_':ctime
}

res = requests.get(
    url=check_host,
    params=params
)

- 服務器返回分析:
   - window.code=201 說明用戶已經完成掃碼,但還沒確認登陸
   - window.code=200 window.redirect_uri="https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage?ticket=A5ncNUM2NJBYNOpJ49Jd38m2@qrticket_0&uuid=Ia7HTPkEdQ==&lang=zh_CN&scan=1485320697"  說明用戶已確認登陸,保存window.redirect_uri跳轉地址

   - window.code=408  說明用戶未掃碼,等待超時,繼續輪詢

四、獲取uin、sid、pass_ticket、skey參數值(注意此處構造的請求地址)

確認登陸後,經過向以下redirect_uri地址發送GET請求,獲取uin、sid、pass_ticket、skey參數值,便於後續發送請求時使用
  - redirect_uri = window.redirect_uri + "&fun=new&version=v2"

五、初使化用戶等信息

上述已完成登陸過程,下面須要獲取用戶信息、好友列表、公衆號等信息,向以下地址發送POST請求:
init_host = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxinit"

# POST請求參數
params = {
    'r': '-1039465096',
    'lang': 'zh_CN',
    'pass_ticket': ticket_dict.get('pass_ticket')
}

# POST請求體
form_data = {
    "BaseRequest":{
        "DeviceID":"e626652155210212",
        "Sid":ticket_dict.get('wxsid'),
        "Uin":ticket_dict.get('wxuin'),
        "Skey":ticket_dict.get('skey'),
    }
}

init_res = requests.post(
    url=init_host,
    params=params,

    # 第1中json格式數據
    # json=data_dict,

    # 第2中data格式,須要帶請求頭
    data=json.dumps(form_data),
    headers={
        'Content-Type':'application/json'
    }
)
服務器返回用戶信息以及同步鍵值,SyncKey是用戶與服務器同步的鍵值,User是當前登陸用戶信息。

六、得到全部的好友列表

在上一步驟中已經得到部分好友和公衆賬號,若是須要得到完整的好友信息,須要請求以下地址:
- https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxgetcontact?pass_ticket=gzP3GEH0awvApwIttyTwxzAvA27%2BwVAp9tQ1osM%2FLL90XWWU5JeIdNLLVjN%2BJ9bq&skey=@crypt_dcaca546_c69b06b40828731a0acb3235758c0ea6&r=1486119544662

保持以前訪問的Cookies不變,在返回的數據中,MemberList包含了全部的好友信息。

七、發送消息

經過向以下地址發送POST請求,發送消息:
https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxsendmsg?pass_ticket=gzP3GEH0awvApwIttyTwxzAvA27%2BwVAp9tQ1osM%2FLL90XWWU5JeIdNLLVjN%2BJ9bq

# POST請求體
form_data = {
    "BaseRequest": {
        "DeviceID": "e626652155210212",
        "Sid": ticket_dict.get('wxsid'),
        "Uin": ticket_dict.get('wxuin'),
        "Skey": ticket_dict.get('skey'),
    },
    'Msg':{
        'ClientMsgId':ctime,
        'LocalID':ctime,
        'FromUserName':from_user,
        'ToUserName':to,
        "Content":content,
        'Type':1
    },
    'Scene':0
}

send_msg_res = requests.post(
        url=send_host,
        params =params,
        # 如果字符串中含有中文,request傳遞數據時,將中文轉換爲字節,沒法爲咱們自動轉換編碼。直接encoding編碼,傳遞字節,不讓requests自動轉換
        # data能夠是字典,字符串,字節,對於字典,字符串直接含有中文不正確,直接轉字節傳送,py3默認是utf-8,直接傳送字節就能夠,若是使用json,會將中文轉換爲Unicode
        data=bytes(json.dumps(form_data,ensure_ascii=False),encoding='utf-8')
    )

其中BaseRequest都是受權相關的值,與上述獲取的值對應。

八、接收消息

檢測是否有新消息到來,發送GET請求的地址及請求參數以下:
check_msg_host = "https://webpush.wx2.qq.com/cgi-bin/mmwebwx-bin/synccheck"

check_list = []

for item in user_info['SyncKey']['List']:
    tmp = "%s_%s" % (item['Key'], item['Val'])
    check_list.append(tmp)

check_str = "|".join(check_list)

# GET請求參數
params = {
    'r': int(time.time()),
    'skey': ticket_dict.get('skey'),
    'sid': ticket_dict.get('wxsid'),
    'uin': ticket_dict.get('wxuin'),
    'deviceid': "e626652155210212",
    'synckey': check_str,
    '_':int(time.time())
}

check_msg_res = requests.get(
    url=check_msg_host,
    params=params,
    cookies=all_cookies
)

響應的內容以下:
  - window.synccheck={retcode:」0」,selector:」2」}

若是retcode不爲0,則說明通訊有問題。根據selector值,客戶端須要做出進一步處理。當selector=2時表示有新消息,此時須要訪問另外一個接口獲取新消息內容,向以下地址發送POST請求:
        
recv_msg_host = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxsync"

# POST請求參數
params = {
    'sid': ticket_dict.get('wxsid'),
    'skey': ticket_dict.get('skey'),
    'lang':'zh_CN',
}


# POST請求數據
form_data = {
    'BaseRequest': {
        "DeviceID": "e626652155210212",
        "Sid": ticket_dict.get('wxsid'),
        "Skey": ticket_dict.get('skey'),
        "Uin": ticket_dict.get('wxuin'),
    },
    'SyncKey': user_info.get('SyncKey'),
    'rr': int(time.time())   #數據類型
}

# 發送POST請求
recv_msg_res = requests.post(
    url=recv_msg_host,
    params=params,
    json=form_data
)
相關文章
相關標籤/搜索