Python爬虫实战之(五)| 模拟登录wechat
作者:xiaoyu
微信公眾號:Python數據科學
知乎:Python數據分析師
不知何時,微信已經成為我們不可缺少的一部分了,我們的社交圈、關注的新聞或是公眾號、還有個人信息或是隱私都被綁定在了一起。既然它這么重要,如果我們可以利用爬蟲模擬登錄,是不是就意味著我們可以獲取這些信息,甚至可以根據需要來對它們進行有效的查看和管理。是的,沒錯,這完全可以。本篇博主將會給大家分享一下如何模擬登錄網頁版的微信,并展示模擬登錄后獲取的好友列表信息。
微信模擬登錄的過程比較復雜,當然不管怎么樣方法都是萬變不離其宗,我們還是使用fiddler抓包工具來模擬登錄的過程。 好了,下面讓我們一步一步的詳細講解一下如何實現的這個復雜的過程。
用fiddler模擬登錄的請求
首先,我們在瀏覽器上打開微信網頁版(fiddler已經在這之前打開了),然后我們會看到一個二維碼的界面。
然后我們使用手機微信掃描并確認,這時候網頁版的微信就登陸了。
好,我們去看看fiddler都給我們抓取了什么信息包。由于過程中發出的請求有點多,這里把抓包按操作進行分解并逐一分析。
1.打開微信網頁
這一步驟的抓包是這樣的,發現其中login.wx.qq.com的兩個鏈接是我們需要的。
于是點開詳細分析一下。
第一個鏈接如下,是一個get請求,可以看到uri中攜帶了一些參數appid、redirect_uri、fun、lang、_。
GET /jslogin?appid=wx782c26e4 c19acffb&redirect_uri=https%3A%2F%2Fwx.qq.com%2Fcgi-bin%2Fmmwebwx-bin%2Fwebwxnewloginpage&fun=new&lang=zh_CN&_=1520350213674 HTTP/1.1 復制代碼經過多次抓取發現appid、redirect_uri、fun、lang參數都是固定的,而_是一串變化的數字,我們在之前模擬京東商城的文章提過,它其實是一個時間戳,如果不清楚可以回顧一下[Python爬蟲實戰之(四)| 模擬登錄京東商城][1]
知道這些參數,模擬get發送出去就可以了。那么我們為什么要模擬這一步呢?
是因為訪問這個鏈接會有如下的響應,而其中有我們后續需要的重要信息uuid(后面步驟會提到)。
window.QRLogin.code = 200; window.QRLogin.uuid = "Idf_QdW1OQ=="; 復制代碼2.模擬獲取二維碼
微信網頁提供的登錄方式是掃碼,我們模擬也無法避開,因此也要進行掃碼驗證。回到瀏覽器,使用開發者工具可以輕松找到二維碼的鏈接。
https://login.weixin.qq.com/qrcode/AdgAWNry-w== 復制代碼我們發現最后的字符串是變化的。等等,它和uuid一模一樣的。沒錯,它就是uuid,用來保證二維碼的唯一性。
因此,我們將上面提取的uuid拼接到后面就可以得到二維碼圖片了,然后進行掃碼確認操作。
3.識別登錄狀態
為了識別掃碼是否成功,這個步驟我們需要用到上面提到的第二個鏈接。
GET /cgi-bin/mmwebwx-bin/login?loginicon=true&uuid=Idf_QdW8OQ==&tip=1&r=68288473&_=1520050213675 HTTP/1.1 復制代碼這個鏈接也是個get請求,同樣攜帶了一些參數。
實際上在抓包過程發現只要我們不掃描二維碼,這個鏈接就會一直重復發送直到二維碼被掃描或者超時。
那么我們如何判斷二維碼是否被掃描或者已經登陸了呢?
還是通過響應的數據來進行判斷的。經分析發現如果二維碼一直沒被掃,那么響應是這樣的:
window.code=408; 復制代碼但是如果二維碼被掃描了,響應是這樣的:
window.code=201;window.userAvatar = ..... window.code=200; window.redirect_uri="https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage?ticket=AaL_Xd5muLPKNVY_Hzt_uoBs@qrticket_0&uuid=gbJqPdkNSQ==&lang=zh_CN&scan=1520353803"; 復制代碼code=201說明二維碼被掃描成功了。 code=200說明是登錄成功了。
4.登錄
掃描了二維碼之后,fiddler上會多出幾個新的請求。
你可能發現了,上一步驟中code=200后面有個重定向的uri,這個uri就是此步驟中跳轉的登錄鏈接。
GET https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage?ticket=AaL_Xd5muLPKNVY_Hzt_udBs@qrticket_0&uuid=gbJqPdfNSQ==&lang=zh_CN&scan=1520353803&fun=new&version=v2 HTTP/1.1 復制代碼通過上一步驟識別登錄成功的響應我們可以得到響應里面的所有參數。沒錯,這些參數正好可以用在正式登錄(即跳轉鏈接)的請求中。于是我們利用這些參數再進行一次get請求。攜帶參數如下:
當然,這個登錄請求同樣也會返回一些響應代碼,響應代碼如下:
<error><ret>0</ret><message>OK</message><skey>xxx</skey><wxsid>xxx</wxsid><wxuin>xxx</wxuin><pass_ticket>xxx</pass_ticket><isgrayscale>1</isgrayscale> </error> 復制代碼又是一堆參數,簡直沒完沒了啊。別著急,我們已經接近成功了。獲取這個響應我們一樣需要將其中的參數全部提取出來供下一請求使用。
5.初始化同步
好了,終于到了最后一步了,就是微信的初始化和同步的請求了,初始化信息鏈接如下:
POST https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxinit?r=64629109&pass_ticket=4dU5IS9EqtXt5cIV2Gni1tKG7m2V56PXk5XI%252BdjdrIk%253D HTTP/1.1 復制代碼contact聯系鏈接如下:
GET https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxgetcontact?pass_ticket=4dU5IS9EqtXt5cIV2Gni1tKG7m2V56PXk5XI%252BdjdrIk%253D&r=1520353806102&seq=0&skey=@crypt_a82dd73a_3885c878ae2f4590f7b2b5ee949dd1bd HTTP/1.1 復制代碼uri中參數pass_ticket,skey在上一步的響應中已獲取,直接發送請求即可完成。從這兩個鏈接的響應中,我們就可以得到一些真實有用的信息了。
還有一個同步的請求鏈接,所需參數可以從上面兩個鏈接響應中提取。但是至此我們通過上面兩個鏈接已經可以獲取我們想要的信息,因此可以不必請求這個同步鏈接。
GEThttps://webpush.wx.qq.com/cgi-bin/mmwebwx-bin/synccheck?r=1520353806125&skey=%40crypt_a82dd73a_3885c878ae2f4590f7b2b5ee949dd1bd&sid=O2Se5s2LJzPebME2&uin=254891255&deviceid=e289448639092966&synckey=1_694936977%7C2_694936979%7C3_694936982%7C1000_1520324882&_=1520353793581 HTTP/1.1 復制代碼基本的登錄過程就是這樣,有點復雜,博主總結了個流程圖供參考。
代碼實現
請求模擬使用requests模塊完成,解析使用re。這里需要注意一下,如果運行一直報ssl的錯,可以在request請求里面加上了verify=False跳過證書認證來解決。
1.初始化參數
def __init__(self):self.session = requests.session()self.headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 5.1; rv:33.0) Gecko/20100101 Firefox/33.0'}self.QRImgPath = os.path.split(os.path.realpath(__file__))[0] + os.sep + 'webWeixinQr.jpg'self.uuid = ''self.tip = 0self.base_uri = ''self.redirect_uri = ''self.skey = ''self.wxsid = ''self.wxuin = ''self.pass_ticket = ''self.deviceId = 'e000000000000000'self.BaseRequest = {}self.ContactList = []self.My = []self.SyncKey = '' 復制代碼定義一個類,初始化實例的所有請求參數,定義二維碼的路徑。
2.請求uuid
def getUUID(self):url = 'https://login.weixin.qq.com/jslogin'params = {'appid': 'wx782c26e4c19acffb','redirect_uri': 'https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage','fun': 'new','lang': 'zh_CN','_': int(time.time() * 1000), # 時間戳}response = self.session.get(url, params=params)target = response.content.decode('utf-8')pattern = r'window.QRLogin.code = (\d+); window.QRLogin.uuid = "(\S+?)"'ob = re.search(pattern, target) # 正則提取uuidcode = ob.group(1)self.uuid = ob.group(2)if code == '200': # 判斷請求是否成功return Truereturn False 復制代碼使用正則對相應進行提取獲取uuid,通過code判斷請求是否成功,響應如下:
window.QRLogin.code = 200; window.QRLogin.uuid = "Idf_QdW1OQ=="; 復制代碼3.模擬獲取二維碼
def showQRImage(self):url = 'https://login.weixin.qq.com/qrcode/' + self.uuidresponse = self.session.get(url)self.tip = 1with open(self.QRImgPath, 'wb') as f:f.write(response.content)f.close()# 打開二維碼if sys.platform.find('darwin') >= 0:subprocess.call(['open', self.QRImgPath]) # 蘋果系統elif sys.platform.find('linux') >= 0:subprocess.call(['xdg-open', self.QRImgPath]) # linux系統else:os.startfile(self.QRImgPath) # windows系統print('請使用微信掃描二維碼登錄') 復制代碼使用uuid請求二維碼圖片,并根據操作系統自動打開。
4.識別登錄狀態
def checkLogin(self):url = 'https://login.weixin.qq.com/cgi-bin/mmwebwx-bin/login?tip=%s&uuid=%s&_=%s' % (self.tip, self.uuid, int(time.time() * 1000))response = self.session.get(url)target = response.content.decode('utf-8')pattern = r'window.code=(\d+);'ob = re.search(pattern, target)code = ob.group(1)if code == '201': # 已掃描print('成功掃描,請在手機上點擊確認登錄')self.tip = 0elif code == '200': # 已登錄print('正在登錄中...')regx = r'window.redirect_uri="(\S+?)";'ob = re.search(regx, target)self.redirect_uri = ob.group(1) + '&fun=new'self.base_uri = self.redirect_uri[:self.redirect_uri.rfind('/')]elif code == '408': # 超時passreturn code 復制代碼響應如下:
window.code=200; window.redirect_uri="https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage?ticket=AaL_Xd5muLPKNVY_Hzt_uoBs@qrticket_0& 復制代碼根據響應中的code代碼識別登錄狀態。 408:超時 201:已掃描 200:已登錄
5.登錄
def login(self):response = self.session.get(self.redirect_uri, verify=False)data = response.content.decode('utf-8')doc = xml.dom.minidom.parseString(data)root = doc.documentElement# 提取響應中的參數for node in root.childNodes:if node.nodeName == 'skey':self.skey = node.childNodes[0].dataelif node.nodeName == 'wxsid':self.wxsid = node.childNodes[0].dataelif node.nodeName == 'wxuin':self.wxuin = node.childNodes[0].dataelif node.nodeName == 'pass_ticket':self.pass_ticket = node.childNodes[0].dataif not all((self.skey, self.wxsid, self.wxuin, self.pass_ticket)):return Falseself.BaseRequest = {'Uin': int(self.wxuin),'Sid': self.wxsid,'Skey': self.skey,'DeviceID': self.deviceId,}return True 復制代碼請求跳轉的登錄鏈接,提取響應代碼參數,響應如下:
<error><ret>0</ret><message>OK</message><skey>xxx</skey><wxsid>xxx</wxsid><wxuin>xxx</wxuin><pass_ticket>xxx</pass_ticket><isgrayscale>1</isgrayscale> </error> 復制代碼6.初始化獲取信息
def webwxinit(self):url = self.base_uri + \'/webwxinit?pass_ticket=%s&skey=%s&r=%s' % (self.pass_ticket, self.skey, int(time.time() * 1000))params = {'BaseRequest': self.BaseRequest}h = self.headersh['ContentType'] = 'application/json; charset=UTF-8'response = self.session.post(url, data=json.dumps(params), headers=h, verify=False)data = response.content.decode('utf-8')print(data)dic = json.loads(data)self.ContactList = dic['ContactList']self.My = dic['User']SyncKeyList = []for item in dic['SyncKey']['List']:SyncKeyList.append('%s_%s' % (item['Key'], item['Val']))self.SyncKey = '|'.join(SyncKeyList)ErrMsg = dic['BaseResponse']['ErrMsg']Ret = dic['BaseResponse']['Ret']if Ret != 0:return Falsereturn True 復制代碼請求初始化的鏈接,獲取初始化響應數據。
def webwxgetcontact(self):url = self.base_uri + \'/webwxgetcontact?pass_ticket=%s&skey=%s&r=%s' % (self.pass_ticket, self.skey, int(time.time()))h = self.headersh['ContentType'] = 'application/json; charset=UTF-8'response = self.session.get(url, headers=h, verify=False)data = response.content.decode('utf-8')# print(data)dic = json.loads(data)MemberList = dic['MemberList']# 倒序遍歷,不然刪除的時候出問題..SpecialUsers = ["newsapp", "fmessage", "filehelper", "weibo", "qqmail", "tmessage", "qmessage", "qqsync","floatbottle", "lbsapp", "shakeapp", "medianote", "qqfriend", "readerapp", "blogapp","facebookapp", "masssendapp","meishiapp", "feedsapp", "voip", "blogappweixin", "weixin", "brandsessionholder","weixinreminder", "wxid_novlwrv3lqwv11", "gh_22b87fa7cb3c", "officialaccounts","notification_messages", "wxitil", "userexperience_alarm"]for i in range(len(MemberList) - 1, -1, -1):Member = MemberList[i]if Member['VerifyFlag'] & 8 != 0: # 公眾號/服務號MemberList.remove(Member)elif Member['UserName'] in SpecialUsers: # 特殊賬號MemberList.remove(Member)elif Member['UserName'].find('@@') != -1: # 群聊MemberList.remove(Member)elif Member['UserName'] == self.My['UserName']: # 自己MemberList.remove(Member)return MemberList 復制代碼請求contact的鏈接,獲取聯系人、公眾號、群聊以及個人信息。響應代碼為json格式,如下:
{ "BaseResponse": { "Ret": 0, "ErrMsg": "" } , "Count": 11, "ContactList": [{ "Uin": 0, "UserName": "filehelper", "NickName": "文件傳輸助手", "HeadImgUrl": "/cgi-bin/mmwebwx-bin/webwxgeticon?seq=621637626&username=filehelper&skey=@crypt_a82dd73a_7e8e1054c011e8d71d0b542f39c7db85", "ContactFlag": 3, "MemberCount": 0, "MemberList": [], "RemarkName": "", "HideInputBarFlag": 0, "Sex": 0, "Signature": "", "VerifyFlag": 0, "OwnerUin": 0, "PYInitial": "WJCSZS", "PYQuanPin": "wenjianchuanshuzhushou", "RemarkPYInitial": "", "RemarkPYQuanPin": "", "StarFriend": 0, "AppAccountFlag": 0, "Statues": 0, "AttrStatus": 0, "Province": "", "City": "", "Alias": "", "SnsFlag": 0, "UniFriend": 0, "DisplayName": "", "ChatRoomId": 0, "KeyWord": "fil", "EncryChatRoomId": "", "IsOwner": 0 } ,{...} ... 復制代碼根據響應中字段信息做信息操作,這里是獲取好友列表,所以將其它字段如公眾號、群聊、自己都去掉了,只保留好友信息。
7.主函數運行
def main(self):if not self.getUUID():print('獲取uuid失敗')returnself.showQRImage()time.sleep(1)while self.checkLogin() != '200':passos.remove(self.QRImgPath)if not self.login():print('登錄失敗')return# 登錄完成, 下面查詢好友if not self.webwxinit():print('初始化失敗')returnMemberList = self.webwxgetcontact()print('通訊錄共%s位好友' % len(MemberList))for x in MemberList:sex = '未知' if x['Sex'] == 0 else '男' if x['Sex'] == 1 else '女'print('昵稱:%s, 性別:%s, 備注:%s, 簽名:%s' % (x['NickName'], sex, x['RemarkName'], x['Signature'])) 復制代碼模擬登錄結果
好友列表如下:
當然,好友列表只是個例子,我們也可以對其它信息進行查看和管理或者數據分析。
總結
本篇與大家分享了網頁版微信的模擬登錄過程。盡管過程中請求多有點復雜,但是只要我們仔細分析還是可以一步一步實現的,希望對大家有幫助,代碼已上傳到github:鏈接
完畢。
關注微信公眾號Python數據科學,獲取 120G 人工智能 學習資料。
總結
以上是生活随笔為你收集整理的Python爬虫实战之(五)| 模拟登录wechat的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 《Orange’s 一个操作系统的实现》
- 下一篇: python json.loads js