如今的平常生活已經離不開微信,不免會生出微信有沒有什麼API可使用的想法。php
那樣就能夠拿本身微信作個消息聚合、寫個vote什麼的,能夠顯然沒有這種東西。html
不過還好,有網頁版微信不就等於有了API麼,這個項目就是出於這個想法出現的。node
##目標python
看完這一系列教程,你就能從頭開始實現本身關於微信以及相似工具的想法,例如一個完善的微信機器人。git
固然,若是你只對使用微信的API感興趣,能夠直接跳到下一篇教程,直接使用我已經完成的框架。github
本文爲該教程的第一部分,主要講述抓包與僞造,將會以最簡單的方法介紹使用Python模擬登錄抓取數據等內容。web
Python與基本的網絡基礎都不困難,因此即便沒有這方面基礎輔助搜索引擎也徹底能夠學習本教程。正則表達式
關於本教程有任何建議或者疑問,都歡迎郵件與我聯繫,或者在github上提出(i7meavnktqegm1b@qq.com)json
##教程流程簡介瀏覽器
教程將會從如何分析微信協議開始,第一部分將教你如何從零開始獲取並模擬擴展我的微信號所須要的協議。
第二部分將會就這些協議進行利用,以微信機器人爲例介紹我給出的項目基本框架與存儲、任務識別等功能。
第三部分就項目基本框架完成插件,以消息聚合等功能爲例對框架作進一步介紹與擴展。
##簡單成果展現:
目前的樣例微信號被擴展爲了可以完成信息上傳下載的機器人,用於展現信息交互功能。
其支持文件、圖片、語音的上傳下載,能夠掃碼嘗試使用。
##本部分所需環境
本文是這一教程的第一部分,須要配置抓包與Python環境。
本教程使用的環境以下:
###Wireshark配置
Wireshark是常見的抓包軟件,這裏經過一些配置抓取微信網頁端的流量。
因爲微信網頁端使用https,須要特殊的配置才能看到有意義的內容,具體的配置見這裏。
配置完成之後開始抓包,載入https://www.baidu.com
後若能看到http請求則配置成功。
##分析並模擬掃碼,並獲取登陸狀態
微信網頁端登錄分爲不少步,這裏以第一步掃碼爲例講解如何從抓包開始完成模擬。
###分析過程
在抓包之前,咱們須要先想清楚這是一個什麼樣的過程。
咱們都登陸過網頁端微信,沒有的話能夠如今作一個嘗試:微信網頁端。
這個過程簡單而言能夠分爲以下幾步:
有了這些概念之後就能夠開始將這四步和包對應起來。
###對應過程與實際的包
開啓wireshark抓包後登錄網頁端微信,完成掃碼登錄,而後關閉wireshark抓包。
篩選http請求(就是菜單欄下面輸入的那個http),能夠看到這樣的界面。
這裏須要講的就是第一列「No.」列的數字就是後文說的幾號包,例如第一行就是30號包。數據包的類型則在Info列中能夠看到,是GET,POST或是別的請求。
那麼咱們能夠開始分析抓到的包了,咱們先粗略的瀏覽一下數據包。
第325號包引發了個人注意,由於登錄過程中很是有特徵的一個過程是二維碼的獲取,因此咱們嘗試打開這一數據包的圖片的內容。
325號包是由292號包的請求獲取的,292號包又是一個普通的get請求,因此咱們嘗試直接在瀏覽器中訪問這一網址。(訪問本身抓到的網址)
具體的網址經過雙擊打開292號包便可找到。如須要能夠點擊這裏看圖。
咱們發現直接在瀏覽器中獲取了一張二維碼,因此這頗有可能就是上述1、二步的過程了。
那麼咱們是向服務器提供了哪些數據獲取了二維碼呢?
4ZtmDT6Opg==
獲取到了這一二維碼。那麼這一標識是隨機生成的仍是服務器獲取的呢?
window.QRLogin.code = 200; window.QRLogin.uuid = "4ZtmDT6OPg==";
(見下方截圖)那麼284號包須要傳遞給服務器哪些數據?
https://login.weixin.qq.com/jslogin?appid=wx782c26e4c19acffb&redirect_uri=https%3A%2F%2Fwx.qq.com%2Fcgi-bin%2Fmmwebwx-bin%2Fwebwxnewloginpage&fun=new&lang=en_US&_=1453725386008
。appid, redirect_uri, fun, lang, _
。到了這裏,1,2步的過程咱們已經可以對應上相應的包了。
3,4部的最顯著特徵是在掃描成功之後會獲取掃描用的微信號的頭像。
咱們仍是首先大體的瀏覽一下服務器返回的數據包,試圖找到包含圖片的數據包。
window.code=201
顯然就是表示狀態的代碼。(見下方截圖)200:登錄成功 201:掃描成功 408:圖片過時
咱們很容易的找到了在登陸過程中不斷出現的請求,那麼要怎麼模擬呢?
loginicon, uuid, tip, r, _
至此你應該已經能將四個過程所有與具體的數據包對應。爲了不有遺漏的過程,咱們將沒有使用到的與服務器交互的數據包標識出來(右鍵Mark)。通過簡單的瀏覽,認爲其中並無必須的數據包交互。但值得注意的是,若是以後模擬數據包沒有問題卻沒法登錄的話應當再回到這些數據包中搜尋。
這裏作一個簡單的小結,這一部分簡單的介紹了分析數據包的基本思路,以及一些小的技巧。固然這些僅供參考,在具體的抓包中徹底能夠根據具體的交互過程自由發揮。而目前留下來的問題有:第一步時的appid與第三步時的r,留待模擬時在作研究。
###使用Python模擬掃碼
這一部分咱們使用python的requests模塊,能夠經過pip install requests
安裝。
咱們先來簡單的講述一下這個包。
import requests # 新建一個session對象(就像開了一個瀏覽器同樣) session = requests.Session() # 使用get方法獲取https://www.baidu.com/s?wd=python url = 'https://www.baidu.com/s' params = { 'wd': 'python', } r = session.get(url = url, params = params) with open('baidu.htm') as f: f.write(r.content) # 存入文件,可使用瀏覽器嘗試打開 # 舉例使用post方法 import json url = 'https://www.baidu.com' data = { 'wd': 'python', } r = session.get(url = url, data = json.dumps(data)) with open('baidu.htm') as f: f.write(r.content) # 以上代碼與下面的代碼不連續
若是想要更多的瞭解這個包,能夠瀏覽requests快速入門。
你能夠嘗試獲取一個你熟悉的網站來測試使用requests,在測試時能夠打開抓包,查看你發送的數據包與想要發送的數據包是否同樣。
那麼咱們開始模擬第1、二個過程,向服務器提供一些用於獲取二維碼的數據,服務器返回二維碼。
284號包
咱們須要模擬的地址爲:https://login.weixin.qq.com/jslogin?appid=wx782c26e4c19acffb&redirect_uri=https%3A%2F%2Fwx.qq.com%2Fcgi-bin%2Fmmwebwx-bin%2Fwebwxnewloginpage&fun=new&lang=en_US&_=1453725386008 ,因此咱們模擬的代碼以下:
#coding=utf8 import time, requests session = requests.Session() url = 'https://login.weixin.qq.com/jslogin' params = { 'appid': 'wx782c26e4c19acffb', 'redirect_uri': 'https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage', 'fun': 'new', 'lang': 'en_US', '_': int(time.time()), } r = session.get(url, params = params) print('Content: %s'%r.text)
固然,將模擬的地址所有寫在url裏面效果徹底同樣。
值得一提的是requests會幫咱們自動urlencode,若是不須要urlencode(/變爲了%2F)能夠將全部內容都寫在url裏面。
提取出uuid
這裏使用re,若是不瞭解正則表達式的話能夠直接拿來用,畢竟和這一個教程並不相關。
# 上接上一段程序 import re regx = r'window.QRLogin.code = (\d+); window.QRLogin.uuid = "(\S+?)";' # 咱們能夠看到返回的量是上述的格式,括號內的內容被提取了出來 data = re.search(regx, r.text) if data and data.group(1) == '200': uuid = data.group(2) print('uuid: %s'%uuid)
若是沒能成功獲取到uuid能夠嘗試再運行一次。
292號包
咱們須要模擬的url爲:https://login.weixin.qq.com/qrcode/4ZtmDT6OPg== ,因此咱們模擬的代碼以下:
# 上接上一段程序 url = 'https://login.weixin.qq.com/qrcode/' + uuid r = session.get(url, stream = True) with open('QRCode.jpg', 'wb') as f: f.write(r.content) # 如今你能夠在你存儲代碼的位置發現一張存下來的圖片,用下面的代碼打開它 import platform, os, subprocess if platform.system() == 'Darwin': subprocess.call(['open', 'QRCode.jpg']) elif platform.system() == 'Linux': subprocess.call(['xdg-open', 'QRCode.jpg']) else: os.startfile('QR.jpg')
因爲咱們須要獲取圖像,因此須要以二進制數據流的形式獲取服務器返回的數據包,因此增長stream = True
。
而將二進制數據流寫入也須要在打開文件時設定二進制寫入,即open('QRCode.jpg', 'wb')
。
固然,若是獲取失敗能夠再運行一次。
同理的3、四步也能夠按照這個方法寫出,這裏就再也不贅述,只給出代碼。
而通過測試咱們發現,第一步時的appid實際是一個固定的量,第三步時的r甚至不輸入也能夠登陸。
# 上接上一段代碼 import time while 1: url = 'https://login.weixin.qq.com/cgi-bin/mmwebwx-bin/login' # 這裏演示一下不使用自帶的urlencode params = 'tip=1&uuid=%s&_=%s'%(uuid, int(time.time())) r = session.get(url, params = params) regx = r'window.code=(\d+)' data = re.search(regx, r.text) if not data: continue if data.group(1) == '200': # 下面一段是爲了以後獲取登陸信息作準備 uriRegex = r'window.redirect_uri="(\S+)";' redirectUri = re.search(uriRegex, r.text).group(1) r = session.get(redirectUri, allow_redirects=False) redirectUri = redirectUri[:redirectUri.rfind('/')] baseRequestText = r.text break elif data.group(1) == '201': print('You have scanned the QRCode') time.sleep(1) elif data.group(1) == '408': raise Exception('QRCode should be renewed') print('Login successfully')
當你看到Login successfully時,說明至此咱們已經成功從零開始,經過抓包分析,用python成功模擬了python登錄。
不過是否是看上去沒有什麼反饋呢?那是由於咱們尚未模擬會產生反饋的包,但其實差的只是研究發文字、發圖片什麼的包了。
爲了體現咱們已經登錄了,加上後面這段代碼就能夠看到登錄的帳號信息:
# 上接上一段代碼 import xml.dom.minidom def get_login_info(s): baseRequest = {} for node in xml.dom.minidom.parseString(s).documentElement.childNodes: if node.nodeName == 'skey': baseRequest['Skey'] = node.childNodes[0].data.encode('utf8') elif node.nodeName == 'wxsid': baseRequest['Sid'] = node.childNodes[0].data.encode('utf8') elif node.nodeName == 'wxuin': baseRequest['Uin'] = node.childNodes[0].data.encode('utf8') elif node.nodeName == 'pass_ticket': baseRequest['DeviceID'] = node.childNodes[0].data.encode('utf8') return baseRequest baseRequest = get_login_info(baseRequestText) url = '%s/webwxinit?r=%s' % (redirectUri, int(time.time())) data = { 'BaseRequest': baseRequest, } headers = { 'ContentType': 'application/json; charset=UTF-8' } r = session.post(url, data = json.dumps(data), headers = headers) dic = json.loads(r.content.decode('utf-8', 'replace')) print('Log in as %s'%dic['User']['NickName'])
這裏作一個簡單的小結:
##小結
到如今爲止我展現了一個完整的抓包、分析、模擬的過程完成了模擬登錄,其餘一些事情其實也都是相似的過程,想清楚每一步要作些什麼便可。
這裏用到的軟件都只介紹了最簡單的一些方法,進一步的內容這裏給出一些建議:
那麼作一個小練習好了,測試一下學到的東西:讀取命令行的輸入併發送給本身。(這部分的源碼放在了文末)
##具體運用時可能遇到的難點
###命令行登陸一段時間後沒法與服務器正常交互
這是由於微信網頁端存在心跳機制,一段時間不交互將會斷開鏈接。
另外,每次獲取數據時(webwxsync)記得更新SyncKey。
###某個特定請求不知道如何模擬
在項目中已經模擬好了幾乎全部的請求,你能夠經過參考個人方法與數據包。
若是以後微信網頁版出現更新我會在本項目中及時更新。
項目中的微信網頁端接口見這裏
###沒法上傳中文文件名的文件與圖片
這是由於使用requests包會自動將中文文件名編碼爲服務器端沒法識別的格式,因此須要修改requests包或者使用別的方法上傳文件。
最簡單的方法即將requests包的packages/urlib3中的fields.py中的format_header_param
方法改成以下內容:
def format_header_param(name, value): if not any(ch in value for ch in '"\\\r\n'): result = '%s="%s"' % (name, value) try: result.encode('ascii') except UnicodeEncodeError: pass else: return result if not six.PY3: # Python 2: value = value.encode('utf-8') value = email.utils.encode_rfc2231(value, 'utf-8') value = '%s="%s"' % (name, value.decode('utf8')) return value
###登陸時出現不安全的提示
建議更新Python版本至2.7.11
##小練習源碼
源碼可在該地址下載:這裏
##結束語
但願讀完這篇文章能對你有幫助,有什麼不足之處萬望指正(鞠躬)。
有什麼想法或者想要關注個人更新,歡迎來Github上***Star***或者***Fork***。
160426
LittleCoder
EOF