Python爬蟲實戰之(五)| 模擬登陸wechat

做者:xiaoyunode

微信公衆號:Python數據科學linux

知乎:Python數據分析師git


不知什麼時候,微信已經成爲咱們不可缺乏的一部分了,咱們的社交圈、關注的新聞或是公衆號、還有我的信息或是隱私都被綁定在了一塊兒。既然它這麼重要,若是咱們能夠利用爬蟲模擬登陸,是否是就意味着咱們能夠獲取這些信息,甚至能夠根據須要來對它們進行有效的查看和管理。是的,沒錯,這徹底能夠。本篇博主將會給你們分享一下如何模擬登陸網頁版的微信,並展現模擬登陸後獲取的好友列表信息github

微信模擬登陸的過程比較複雜,固然無論怎麼樣方法都是萬變不離其宗,咱們仍是使用fiddler抓包工具來模擬登陸的過程。 好了,下面讓咱們一步一步的詳細講解一下如何實現的這個複雜的過程。web

用fiddler模擬登陸的請求

首先,咱們在瀏覽器上打開微信網頁版(fiddler已經在這以前打開了),而後咱們會看到一個二維碼的界面。json

而後咱們使用手機微信掃描並確認,這時候網頁版的微信就登錄了。windows

好,咱們去看看fiddler都給咱們抓取了什麼信息包。因爲過程當中發出的請求有點多,這裏把抓包按操做進行分解並逐一分析。瀏覽器

1.打開微信網頁

這一步驟的抓包是這樣的,發現其中login.wx.qq.com的兩個連接是咱們須要的。bash

因而點開詳細分析一下。微信

第一個連接以下,是一個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 = 0
    self.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)  # 正則提取uuid
    code = ob.group(1)
    self.uuid = ob.group(2)
    if code == '200':  # 判斷請求是否成功
        return True
    return False
複製代碼

使用正則對相應進行提取獲取uuid,經過code判斷請求是否成功,響應以下:

window.QRLogin.code = 200; window.QRLogin.uuid = "Idf_QdW1OQ==";
複製代碼

3.模擬獲取二維碼

def showQRImage(self):
    url = 'https://login.weixin.qq.com/qrcode/' + self.uuid
    response = self.session.get(url)
    self.tip = 1
    with 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 = 0
    elif 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':  # 超時
        pass
    return 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].data
        elif node.nodeName == 'wxsid':
            self.wxsid = node.childNodes[0].data
        elif node.nodeName == 'wxuin':
            self.wxuin = node.childNodes[0].data
        elif node.nodeName == 'pass_ticket':
            self.pass_ticket = node.childNodes[0].data
    if not all((self.skey, self.wxsid, self.wxuin, self.pass_ticket)):
        return False
    self.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.headers
    h['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 False
    return 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.headers
    h['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失敗')
        return
    self.showQRImage()
    time.sleep(1)
    while self.checkLogin() != '200':
        pass
    os.remove(self.QRImgPath)
    if not self.login():
        print('登陸失敗')
        return
    # 登陸完成, 下面查詢好友
    if not self.webwxinit():
        print('初始化失敗')
        return
    MemberList = 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 人工智能 學習資料。

相關文章
相關標籤/搜索