新版知乎模擬登陸

1.Cookie和Session的區別

如果你登錄知乎,填寫過用戶名、密碼下次進來的時候不想再填寫了,那麼你在第一次登錄後,服務器就會發送給你的瀏覽器一個Cookie,Cookie中包含了你的用戶名、密碼,下次再次發送請求給知乎的時候,瀏覽器會自動給請求加上Cookie,這樣服務器就能知道你是誰。這就是Cookie的機制。

但是這種機制是不安全的,當你本地Cookie被別人獲取後,就能直接使用你的賬號了。於是出現了session機制:當你第一次以用戶名、密碼登錄知乎時,知乎服務器會自己生成一條Session Id和sesson Value保存在服務器端,同時將Session Id作爲Cookie中的一個鍵值對返回給瀏覽器,當你第二次請求知乎服務器的時候,請求會自動加上Session Id,那麼服務器端收到Session Id後,會在服務器上查詢是否有此Session Id,如果查詢到,那麼就匹配到相應的Session Value,也就是包含用戶名密碼的部分,這時候服務器就能識別出來你是那個用戶了。

明確一點:Session Value中包含的是加密的用戶名、密碼等用戶的Profile,是存放在服務器端,同事每一個Session Id都是有一個有效期的,這兩點保證了安全性。

2.Http狀態碼

code 說明
200 請求被成功處理
301/302 永久重定向/臨時重定向
403 沒有訪問權限
404 滅有對應的資源
500 服務器錯誤
503 服務器停機或正在維護

3.分析知乎登錄

3.1 找到登錄請求URL

通過嘗試手機號碼和郵箱號,分析得出知乎統一登錄網址爲:https://www.zhihu.com/api/v3/oauth/sign_in

3.2 找到登錄時的header部分

:authority: www.zhihu.com
:method: POST
origin: https://www.zhihu.com
referer: https://www.zhihu.com/signup?next=%2F
user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36
x-xsrftoken: 405b7e07-d4f5-4b35-8b70-3e129d97a4d8

主要的是找到x-xsrftoken,以前的教程寫的都是_xsrf在返回的html中標籤中,改版後變化了,我們可以在返回的cookie中找到,所以
首先設置一個header

agent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36"

header = {
    "HOST": "www.zhihu.com",
    "Referer": "https://www.zhihu.com/",
    "User-Agent": agent,
}

取得_xsrf

def get_xsrf():
    response = session.post("https://www.zhihu.com/signup?next=%2F", headers=header)
return response.cookies['_xsrf']

隨後更新header

    header.update({
        "X-Xsrftoken": xsrf
    })

3.3分析請求登錄頁面的data數據

3.3.1得到請求的data數據格式爲下

------WebKitFormBoundary4YUIdOtklYTeomJn
Content-Disposition: form-data; name="client_id"

c3cef7c66a1843f8b3a9e6a1e3160e20
------WebKitFormBoundary4YUIdOtklYTeomJn
Content-Disposition: form-data; name="grant_type"

password
------WebKitFormBoundary4YUIdOtklYTeomJn
Content-Disposition: form-data; name="timestamp"

1527727860453
------WebKitFormBoundary4YUIdOtklYTeomJn
Content-Disposition: form-data; name="source"

com.zhihu.web
------WebKitFormBoundary4YUIdOtklYTeomJn
Content-Disposition: form-data; name="signature"

8417b29b51739a7dba377934e7678c35625464f0
------WebKitFormBoundary4YUIdOtklYTeomJn
Content-Disposition: form-data; name="username"

+8615639151994
------WebKitFormBoundary4YUIdOtklYTeomJn
Content-Disposition: form-data; name="password"

admin123
------WebKitFormBoundary4YUIdOtklYTeomJn
Content-Disposition: form-data; name="captcha"


------WebKitFormBoundary4YUIdOtklYTeomJn
Content-Disposition: form-data; name="lang"

en
------WebKitFormBoundary4YUIdOtklYTeomJn
Content-Disposition: form-data; name="ref_source"

homepage
------WebKitFormBoundary4YUIdOtklYTeomJn
Content-Disposition: form-data; name="utm_source"


------WebKitFormBoundary4YUIdOtklYTeomJn--

3.3.2 通過多次請求發現如下會改變的信息只有

username:
password:
timestamp:
captcha:
signature:

其中用戶名、密碼是自己控制的

3.3.2.1 timestamp

比較簡單就是距離1970年過去的秒數的整數部分。

timestamp = str(int(time.time()))

3.2.2.2 captcha

驗證碼處理:現在知乎更新了,有的使用字母驗證碼,有的使用點擊倒立的數字驗證碼。但是我發現登錄時只考慮圖形字母驗證碼也可以登錄,但不知道* 爲什麼 *

對字母驗證碼處理的方法就是下載圖片,然後打開,進行手動輸入

分析對驗證碼圖片的請求url和header

請求url爲https://www.zhihu.com/api/v3/oauth/captcha?lang=cn

header如下

    :authority: www.zhihu.com
    :method: GET
    :path: /api/v3/oauth/captcha?lang=cn
    :scheme: https
    accept: application/json, text/plain, */*
    accept-encoding: gzip, deflate, br
    accept-language: zh-CN,zh;q=0.9,en;q=0.8
    authorization: oauth c3cef7c66a1843f8b3a9e6a1e3160e20
    cookie: d_c0="AIDk5lsxrA2PTn6UE7w8ZXwIcLwr6s4V8TM=|1527673963"; q_c1=29e9198d965c42b4b4d13820bc7023db|1527673963000|1527673963000; _zap=a0bc8c13-50e7-484b-abde-db97010a065b; l_cap_id="YzIxYmFmNzg0YjViNGZmODljNTIwMjUwMTQ5NWY0NTY=|1527688563|f3bfccc25e61ab0e6c92295650f257e2f9cd779b"; r_cap_id="YTE3M2JlMWQ4NWQzNDA2NzllYThmMWYxNjMxZmRhMTY=|1527688563|be9b0cd7843bf090e5e0194ceb29a99fc60a1cce"; cap_id="ZWFjYTg3ODljMzI0NDVmOTgyYmE0NjRiMGQyZGRmYmU=|1527688563|4636e8decd51156afe879407f16e6c7f57e222ce"; tgw_l7_route=5bcc9ffea0388b69e77c21c0b42555fe; _xsrf=405b7e07-d4f5-4b35-8b70-3e129d97a4d8; capsion_ticket="2|1:0|10:1527727674|14:capsion_ticket|44:Y2M3YTcwYWQ3OGFiNGIxMjk3MWUwY2I5ZWQ0OWM0ZjQ=|ed0700eadd8466ffd6f4c61dfbce11a1f4d11483f32f1ae8262501a7fd859558"
    if-none-match: "fa4cf03c0ac47ca1c52ed2df2b71dfda86db6655"
    referer: https://www.zhihu.com/signup?next=%2F
    user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36
    x-udid: AIDk5lsxrA2PTn6UE7w8ZXwIcLwr6s4V8TM=

發現了有一個authorization字段,我在此踩坑,沒加這個字段的header不能成功請求圖片驗證碼
所以:

def get_captcha():
    captcha_url = 'https://www.zhihu.com/api/v3/oauth/captcha?lang=cn'
    header.update({
        "authorization": "oauth c3cef7c66a1843f8b3a9e6a1e3160e20"
    })
    response = session.get(captcha_url, headers=header)
    r = re.findall('"show_captcha":(\w+)', response.text)
    if r[0] == 'false':
        return ''
    else:
        print("需要輸入驗證碼!")
        response = session.put('https://www.zhihu.com/api/v3/oauth/captcha?lang=cn', headers=header)
        show_captcha = json.loads(response.text)['img_base64']
        with open('captcha.jpg', 'wb') as f:
            f.write(base64.b64decode(show_captcha))
        try:
            im = Image.open('captcha.jpg')
            im.show()
            im.close()
        except:
            print("打開文件失敗!")

        captcha = input('輸入驗證碼:')

        return captcha

3.2.2.3 signature

這邊是參照大神的分析

多次請求,其他參數都是固定的,但是signature參數是個什麼東西…知道意思是簽名,但是我們要從哪裏獲取這個呢.網頁源碼裏沒有,那肯定是js生成的,去js裏搜索在
https://static.zhihu.com/heifetz/main.app.19b9c7c4c4502d8ef477.js 中總算是找到了.爲了好看下載到編譯器裏,實在太大,編譯器直接卡死了,太尷尬了….漫長的等待後拿到這麼一段js

function (e, t, n) {
    "use strict";
    function r(e, t) {
        var n = Date.now(), r = new a.a("SHA-1", "TEXT");
        return r.setHMACKey("d1b964811afb40118a12068ff74a12f4", "TEXT"), r.update(e), r.update(i.a), r.update("com.zhihu.web"), r.update(String(n)), c({
            clientId: i.a,
            grantType: e,
            timestamp: n,
            source: "com.zhihu.web",
            signature: r.getHMAC("HEX")
        }, t)
    }

可以看出是使用sha-1 key=’d1b964811afb40118a12068ff74a12f4’和其他的一些字段生成的HMAX

def get_signature(time_str):
    h = hmac.new(key='d1b964811afb40118a12068ff74a12f4'.encode('utf-8'), digestmod=sha1)
    grant_type = 'password'
    client_id = 'c3cef7c66a1843f8b3a9e6a1e3160e20'
    source = 'com.zhihu.web'
    now = time_str
    h.update((grant_type + client_id + source + now).encode('utf-8'))
    return h.hexdigest()

3.4 封裝data和header進行登錄

def zhihu_login(account, password):
    # 知乎登錄
    time_str = str(int(time.time()))
    xsrf = get_xsrf()
    header.update({
        "X-Xsrftoken": xsrf
    })
    post_url = "https://www.zhihu.com/api/v3/oauth/sign_in"
    post_data = {
        "client_id": "c3cef7c66a1843f8b3a9e6a1e3160e20",
        "grant_type": "password",
        "timestamp": time_str,
        "source": "com.zhihu.web",
        "signature": get_signature(time_str),
        "username": account,
        "password": password,
        "captcha": get_captcha(),
        "lang": "cn",
        "ref_source": "homepage",
        "utm_source": ""
    }

    response = session.post(post_url, data=post_data, headers=header, cookies=session.cookies)
    if response.status_code == 201:
        # 保存cookie,下次直接讀取保存的cookie,不用再次登錄
        print("登錄成功")

        response = session.post("https://www.zhihu.com", headers=header, cookies=session.cookies)
       #print(response.text)
        session.cookies.save()
    else:
        print("登錄失敗")

如果登錄成功我們要保存cookie,下次登錄就直接實用cookie登錄即可,所以我們首先要加載cookie

session = requests.session()

session.cookies = cookielib.LWPCookieJar(filename="cookies.txt")  # cookie存儲文件,


try:
    session.cookies.load(ignore_discard=True)  # 從文件中讀取cookie
except:
    print("cookie 未能加載")

3.5 判斷是否已經登錄

因爲我們加載了cookie,如果cookie有效且正確,這不用再次登錄

這裏採用點擊用戶個人中心的鏈接,且不允許跳轉,從而獲取返回的狀態碼,如果狀態碼爲200則證明登錄了,否則要進行登錄

def is_login():
    # 通過個人中心頁面返回狀態碼來判斷是否登錄
    # 通過allow_redirects 設置爲不獲取重定向後的頁面

    response = session.get("https://www.zhihu.com/inbox", headers=header, allow_redirects=False)

    print(response.cookies)
    print(response.status_code)
    if response.status_code != 200:
        print("尚未登錄")
        zhihu_login("+***", "***")
    else:
        print("你已經登陸了")

至此,就可以成功登錄啦!

具體實現完整代碼,請移駕github

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章