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