1. 程式人生 > >新浪微博模擬登入分析(含驗證碼)

新浪微博模擬登入分析(含驗證碼)

實驗室專案結題需要爬取新浪微博的內容做實驗,師兄提供了一份已實現的微博爬蟲系統。本身可以輕鬆愉快的完成語聊收集這一部分,然而自己的微博賬號始終登入失敗。究其原因,結果是登入時需要驗證碼。而系統對於需要驗證碼登入的賬號只能GG了,谷歌“新浪微博爬蟲”相關內容後,發現多數文章(主要參考了豆瓣百度空間部落格園)都是重複討論模擬登入的過程。網上的文章並沒有提到解決需要驗證碼登入的問題,或許是因為api沒有返回相關的資訊,但自己發現最新的微博登入api確實返回了驗證碼相關的資訊,故實現了通過人工輸入驗證碼的方式進行模擬登入。

為了給大家一個對新浪微博登入過程完整的認識,本文也會重複已有文章的內容。

我們知道,對於需要登入驗證的網站,當用戶第一次登入後,瀏覽器通過儲存該網站伺服器返回的cookie值,以便使用者再次訪問的時候無需登入即可訪問。因此,爬蟲也是通過模擬一次登入獲取cookie值,並儲存,然後就能以此登入狀態進行資源獲取。

接下來開始解析新浪微博登入的過程。 

當輸入使用者名稱後,通過Chrome的工具可以看到,此時網站向伺服器傳送了一個請求。

返回的結果如下: 
 
這是一個js的回撥函式,裡面包含了一些伺服器返回的資訊(圖中pubkey的資訊因截斷未顯示完全)。此時,我們可能並不知道這些資訊的含義,對該url請求的引數也不清晰。通過檢視網站的js指令碼檔案,格式化程式碼後定位prelogin函式,見下圖。從中,我們可以看出,prelogin請求中的su是base64加密後的使用者名稱,callback是請求返回後應執行的回撥函式,rsakt、entry、client都是固定值,_

很容易猜到是當前的時間戳(其實沒有此引數的情況下,也能登入),而checkpin比較重要,與驗證碼認證有關,在以前的prelogin請求中沒有此引數。經驗證,當checkpin=1的時候,伺服器返回資訊中會有一個欄位showpin=0|1代表是否需要驗證碼認證。在preloginCallBack函式中可以看到,將伺服器返回的servertime,nonce,pubkey,rsakv,pcid等資訊儲存,從程式碼中可以發現與rsa加密有關,其實新浪微博目前正是採用了rsa演算法對密碼進行加密操作。 
如果showpin的值為1,此時會產生一個新的請求。這個請求返回的結果就是登入需要的驗證碼圖片。

依然檢視網站js程式碼,引數p其實就是prelogin返回的pcid值,而r是一個隨機數,a等於0。 

到這裡,登入的準備工作就完成了。當輸入完密碼(及驗證碼)後,點選登入,網站將post一些資料至以下url。其中client引數只是簡單指明登入api的版本。

通過Chrome的除錯工具可以看到post資料主要包括以下欄位。主要的引數已有prelogin返回,在需要輸入驗證碼的時候,則會有door欄位,它的值就是從伺服器獲取的驗證碼圖片的值,susp分別代表加密後的使用者名稱和密碼。 
通過檢視相關js程式碼,可以知道使用者名稱和密碼的加密過程。這裡,使用者名稱同樣是base64加密,而密碼則是根據loginType的值進行相應演算法加密。不過,由於程式碼全域性設定了this.loginType = rsa;,目前的登入均採用的是rsa加密。從程式碼中可以看到,密碼的加密過程確實使用了prelogin返回的servertime,nonce,pubkey等資料。 
這一步請求返回的結果是包含了網站跳轉的html文件。 
上圖是登入失敗的結果,跳轉的url是:

其中retcode(=0時表示成功)是錯誤程式碼,reason是錯誤提示資訊。(自己實現登入的時候,當retcode!=0時可以直接輸出錯誤資訊,無需跳轉。) 
在賬號資訊正確的情況下,請求返回的結果如下圖。其中包含了真實登入的跳轉連結。當請求此連結時,伺服器會返回用於認證的cookie值,此連結請求一次後便失效,該請求成功後則代表此次登入成功。因此,自己實現模擬登入的時候,需要在這一步儲存cookie資訊,然後就可以利用獲取的cookie資訊訪問登入後的資源。 

以上就是微博登入整個過程。下面提供一個用Python實現的微博登入供大家參考。

# coding=utf8
import base64
import binascii
import cookielib
import json
import os
import random
import re
import rsa
import time
import urllib
import urllib2
import urlparse
from pprint import pprint
 
 
__client_js_ver__ = 'ssologin.js(v1.4.18)'
 
 
class Weibo(object):
 
    """"Login assist for Sina weibo."""
 
    def __init__(self, username, password):
        self.username = self.__encode_username(username).rstrip()
        self.password = password
 
        cj = cookielib.LWPCookieJar()
        self.opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
 
    @staticmethod
    def __encode_username(username):
        return base64.encodestring(urllib2.quote(username))
 
    @staticmethod
    def __encode_password(password, info):
        key = rsa.PublicKey(int(info['pubkey'], 16), 65537)
        msg = ''.join([
            str(info['servertime']),
            '\t',
            str(info['nonce']),
            '\n',
            str(password)
        ])
        return binascii.b2a_hex(rsa.encrypt(msg, key))
 
    def __prelogin(self):
        url = ('http://login.sina.com.cn/sso/prelogin.php?'
               'entry=weibo&callback=sinaSSOController.preloginCallBack&rsakt=mod&checkpin=1&'
               'su={username}&_={timestamp}&client={client}'
               ).format(username=self.username, timestamp=int(time.time() * 1000), client=__client_js_ver__)
 
        resp = urllib2.urlopen(url).read()
        return self.__prelogin_parse(resp)
 
    @staticmethod
    def __prelogin_parse(resp):
        p = re.compile('preloginCallBack\((.+)\)')
        data = json.loads(p.search(resp).group(1))
        return data
 
    @staticmethod
    def __process_verify_code(pcid):
        url = 'http://login.sina.com.cn/cgi/pin.php?r={randint}&s=0&p={pcid}'.format(
            randint=int(random.random() * 1e8), pcid=pcid)
        filename = 'pin.png'
        if os.path.isfile(filename):
            os.remove(filename)
 
        urllib.urlretrieve(url, filename)
        if os.path.isfile(filename):  # get verify code successfully
            #  display the code and require to input
            from PIL import Image
            import subprocess
            proc = subprocess.Popen(['display', filename])
            code = raw_input('請輸入驗證碼:')
            os.remove(filename)
            proc.kill()
            return dict(pcid=pcid, door=code)
        else:
            return dict()
 
    def login(self):
        info = self.__prelogin()
 
        login_data = {
            'entry': 'weibo',
            'gateway': '1',
            'from': '',
            'savestate': '7',
            'useticket': '1',
            'pagerefer': '',
            'pcid': '',
            'door': '',
            'vsnf': '1',
            'su': '',
            'service': 'miniblog',
            'servertime': '',
            'nonce': '',
            'pwencode': 'rsa2',
            'rsakv': '',
            'sp': '',
            'sr': '',
            'encoding': 'UTF-8',
            'prelt': '115',
            'url': 'http://weibo.com/ajaxlogin.php?framelogin=1&callback=parent.sinaSSOController.feedBackUrlCallBack',
            'returntype': 'META'
        }
        if 'showpin' in info and info['showpin']:  # need to input verify code
            login_data.update(self.__process_verify_code(info['pcid']))
        login_data['servertime'] = info['servertime']
        login_data['nonce'] = info['nonce']
        login_data['rsakv'] = info['rsakv']
        login_data['su'] = self.username
        login_data['sp'] = self.__encode_password(self.password, info)
 
        return self.__do_login(login_data)
 
    def __do_login(self, data):
        url = 'http://login.sina.com.cn/sso/login.php?client=%s' % __client_js_ver__
        headers = {
            'User-Agent': 'Weibo Assist'
        }
        req = urllib2.Request(
            url=url, data=urllib.urlencode(data), headers=headers)
        resp = urllib2.urlopen(req).read()
 
        return self.__parse_real_login_and_do(resp)
 
    def __parse_real_login_and_do(self, resp):
        p = re.compile('replace\(["\'](.+)["\']\)')
        url = p.search(resp).group(1)
 
        # parse url to check whether login successfully
        query = urlparse.parse_qs(urlparse.urlparse(url).query)
        if int(query['retcode'][0]) == 0:  # successful
            self.opener.open(url)  # log in and get cookies
            print u'登入成功!'
            return True
        else:  # fail
            print u'錯誤程式碼:', query['retcode'][0]
            print u'錯誤提示:', query['reason'][0].decode('gbk')
            return False
 
    def urlopen(self, url):
        return self.opener.open(url)
 
 
if __name__ == '__main__':
    weibo = Weibo('[email protected]', 'password')
    if weibo.login():
        print weibo.urlopen('http://weibo.com').read()
        # with open('weibo.html', 'w') as f:
        # print >> f, weibo.urlopen('http://weibo.com/kaifulee').read()

#-----------------------------------------------------------

轉載於:http://blog.youcanlove.me/xin-lang-wei-bo-deng-lu-fen-xi/