文章所使用Python版本為py3.5
1.微信服務器返回一個會話ID
微信Web版本不使用用戶名和密碼直接登錄,而是采用二維碼登錄,所以服務器需要首先分配一個唯一的會話ID,用來標識當前的一次登錄。
通過查看網絡請求我們找到了這個 二維碼圖片代表的隨機字符串,(IcelandB9Entig==),
2.通過會話ID獲得二維碼
然后找到該隨機字符串的來源請求
請求方式為 GET形式 , 具體連接為:
https://login.wx.qq.com/jslogin?
appid=wx782c26e4c19acffb
&redirect_uri=https%3A%2F%2Fwx.qq.com%2Fcgi-bin%2Fmmwebwx-bin%2Fwebwxnewloginpage
&fun=new&lang=zh_CN
&_=1492591577859
我們只需要改變最后一個變量 _值即可獲得新的字符串序列: (這個值是當前距離林威治標準時間的毫秒)可以自行構造!
通過分割,我們就可以獲得隨機字符串,自己在前端頁面上構造一個二維碼出來.
3.輪詢手機端是否已經掃描二維碼并確認在Web端登錄
當我們還沒進行掃碼登錄時,發(fā)現微信web網頁會自動向服務器 輪詢手機端是否已經掃碼并且確認登錄!!
每一分鐘發(fā)送一次,如果沒有登錄,請求會返回
1 | window.code = 408 ; |
掃碼后為
1 | window.code = 201 ; |
確認登陸后為:
1 2 | window.code = 200 ; window.redirect_uri = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage?ticket=A_ToQqd_AFXpsrLMH6RNgi3W@qrticket_0&uuid=we6XJEZYDg==&lang=zh_CN&scan=1492592531" ; |
需要獲得其中的跳轉url,對rensponse進行分割后取得。
redirect_url = re.split('=', http_res_code.text, 2)[-1].strip().replace('"', '').replace(';', '')
4.訪問登錄地址,獲得uin和sid
在接下來的連接中,我們發(fā)現服務器發(fā)給了我們skey,sid,pass_ticket 的重要信息, 分析發(fā)現該請求的發(fā)送地址為: GET方式,
https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage?ticket=A_ToQqd_AFXpsrLMH6RNgi3W@qrticket_0&uuid=we6XJEZYDg==&lang=zh_CN&scan=1492592531
&fun=new&version=v2
發(fā)現,該請求地址為 跳轉url + &fun=new&version=v2 構造而成!
直接GET發(fā)送請求,我們就拿到返回信息,發(fā)現是 xml 格式的數據!
構造一個方法方便獲取我們的連接信息
def auth_analysis(self,str_xml): ''' xml中取出數據,返回字典 :param http_res_ticket: :return: ''' from xml.etree import ElementTree as ET root = ET.XML(str_xml) ret = {} for child in root: ret[child.tag] = child.text return ret
我們還需要獲得該請求下的cookies參數,直接通過requests模塊的 requests.cookies.get_dict()
# 獲取xml 登錄信息user_ticket_url = redirect_url+ '&fun=new&version=v2'user_xml_ticket = requests.get(user_ticket_url)TICKET_COOKIR = user_xml_ticket.cookies.get_dict()ALL_COOKIE_DICT.update(TICKET_COOKIR) #記錄cookies# 獲取xml中信息user_ticket_dic = self.auth_analysis(user_xml_ticket.text)USER_RESULT_DICT.update(user_ticket_dic) #記錄xml中的具體信息
5.初使化微信信息
# 初始化Url :USER_INIT_URL = 'https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxinit?pass_ticket={0}&skey={1}&r={2}'
pass_ticket,skey為上一步驟獲得的xml信息樹中,r為時間戳
這一步的發(fā)送請求,使用了urllib.requests來發(fā)送,注意格式轉換!
此時我們已經獲得了服務端返回的部分數據.如果需要進一步獲取全部信息,......如下
6.獲得所有的好友列表
該請求返回了全部的用戶信息.
https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxgetcontact?
pass_ticket=YK57xcgn4qT0n0qoGtxvtLvsV6XXzTDka7Z9DOFAIcGDK0wtZ6GrNpGdHGIHKiiu
&r=1492592546748
&seq=0
&skey=@crypt_8226323c_fd634988b8e43769cb3b7b9fc99ca549
構造請求并發(fā)送:
7.保持與服務器的信息同步
與服務器保持同步需要在客戶端做輪詢,該輪詢的URL如下:
https://webpush.wx2.qq.com/cgi-bin/mmwebwx-bin/synccheck?
r=1492594498787
&skey=%40crypt_8226323c_fd634988b8e43769cb3b7b9fc99ca549
&sid=mM%2BFZGbHxGcO3x93
&uin=976834800
&deviceid=e738560216565557
&synckey=1_661566297%7C2_661566394%7C3_661565574%7C11_661566180%7C13_661374753%7C201_1492594315%7C203_1492582669%7C1000_1492594202%7C1001_1492563271
&_=1492592515065
skey,sid,uin,與上面步驟的值相對應此處的synkey是上步步驟獲得的同步鍵值,但需要按一定的規(guī)則組合成以下的字符串:
1_124125|2_452346345|3_65476547|1000_5643635
| 被URL編碼成%7C,通過對上面的地址發(fā)送請求:,
1 | res_sync = requests.get(synccheck_url,params = payloads,cookies = ALL_COOKIE_DICT) |
會返回如下的字符串:
1 | window.synccheck = {retcode: "0" ,selector: "0" } |
當有人發(fā)送信息給你時, :
1 | window.synccheck = {retcode: "0" ,selector: "2" } |
8.獲得別人發(fā)來的消息
我們通過該URL地址獲取發(fā)送給我們的消息:
https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxsync?sid=XQvf1MmZ7W8O2BU2&skey=@crypt_75efaa00_3afa77c01d45461eef7eac88da37f70d&pass_ticket=YArFw%252BDpJHCpRtKCTTabpe2ytETBDmYsp%252BB7ywe%252BtplmzxQZ6ohX7d14sJgjgk6T
制造相應的payload 發(fā)送GET請求,獲得返回response數據
9.向用戶發(fā)送消息
用戶主動發(fā)送消息,通過以下的URL地址:
https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxsendmsg?pass_ticket=mRfzOKMZdUAfqrZVaT7VZeroYNX6SgTtO7WzDwDXmiZvwWC0iWlln2fBuGa8oeld
上面的pass_ticket參數不再解釋了,訪問該URL采用POST方式,payload如以下的格式:
BaseRequest都是授權相關的值,與上面的步驟中的值對應,Msg是對消息的描述,包括了發(fā)送人與接收人,消息內容,消息的類型(1為文本),ClientMsgId和LocalID由本地生成。rr可用當前的時間。在返回JSON結果中BaseResponse描述了發(fā)送情況,Ret為0表示發(fā)送成功。
注意:
該流程在py3環(huán)境下,使用了Tornado框架 和 requests 包 和 urllib.requests 包
urllib.requests 包 具體用法參考如下示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 | # payload 轉換成bytes data = (json.dumps(payload)).encode() request = urllib.request.Request(url = user_init_url, data = data) request.add_header( 'ContentType' , 'application/json; charset=UTF-8' ) response = urllib.request.urlopen(request) # 獲得數據 data = response.read() obj = json.loads(data.decode( 'utf-8' )) # 保存數據 INIT_RESULT_DICT.update(obj) |
完整參考代碼: