前言
研究爬蟲的各位小伙伴都知道,需要登錄才能獲取信息的網站,是比較難爬的,原因就是在於,現在各大網站為了反爬,都加入了圖片驗證碼,滑動驗證碼之類的干擾
本篇就針對嗶哩嗶哩的滑動驗證碼進行講解和破解
關於破解滑動驗證究竟是自己使用機器學習還是第三方服務討論
先說一下個人觀點:本人作為一個爬蟲老鳥,如果只是為了使用,非常建議使用第三方服務,為什么呢,來聽我細細分析,
現在是2020年了,混IT的都知道,現在大紅大紫的熱門行業是哪個,肯定都說機器學習,都想入門機器學習,但是很多人還沒入門就掛了,這是為什么呢,因為入門機器學習,是需要有高數的底子的,可不是以前學一個語言,會常用邏輯就可以入門的了,這也是為什么到現在為止,依然還有非常大的機器學習人才缺口,再說一下為什么自己做爬蟲不建議使用機器學習,三個字,玩不起,
首先,你需要有大量的數據,然后再有一個不錯的主機用於訓練,再然后,就是需要你有高數的底子,如果這三個都有,並且學習了機器學習,你才可以勉強破解滑動驗證碼,並且不敢保證自己訓練的准確度,
這就是我推薦使用第三方接口的原因,因為第三方接口就是專門做這類機器學習的,它們有強大的人力物力專門做破解各種驗證碼,並且識別率非常高,現在一般都是90%以上,價格還香,何樂不為了,自己做是頭發掉的少還是加不夠多
當然,並不是說我不讓學習機器學習,畢竟現在是一個人工智能時代,如果已有不錯的數學基礎,並且有很強大興趣,在工作之余,可以入坑機器學習的,畢竟趨勢如此,
本人的觀點是,如果是爬蟲遇到了滑動驗證碼,直接使用第三方平台,如果你很有興趣,繼續需坑機器學習,
本文使用的第三方服務:https://2captcha.com/
根據本人測試,是目前識別率最高的平台,價格還行,3美元幾百次吧
所需工具
En.... 我們這里不需要 selenium,2captcha打碼平台很神奇,我們只需要 requests 模塊就可以啦,
2captcha打碼平台參數分析
既然我們選擇了第三方平台,我們務必要看一下人家的文檔,下面我們就2captcha平台的極驗破解,看一下人家的操作
首先打開人家官網
嗯...純英文,我也看不懂..怎么辦呢,別着急,我帶你們一步一步分析主要功能
登錄賬號
登錄完成后,會自動跳到主頁
紅色圈起來的地方表示剩余多少錢,沒有錢的話記得要氪金,否則是不能用滴,氪金過程這里就不多做解釋了哈,問題不大
藍色圈起來的地方表示這是你的唯一key,每次請求要帶上這個key的,所以要保管好
進入主題,研究文檔
點擊紅色圈的地方,API,一般API都是文檔,let's go
En....什么玩意..完全看不懂,別慌,往下滑
滑動到Rates,我們能看到一個列表,我們要解決的就是極驗(GeeTest),所以我們只看GeeTest就好了
點擊GeeTest
Go
好了,已經懵逼了,但是,怕什么,我們有翻譯!!!
這里大概整理一下它的意思
首先,找到目標網站的gt,challenge和api_server三個值,然后,加上其他一些參數發送到 https://2captcha.com/in.php,會返回一個任務ID
然后等個15秒左右以后,再向 https://2captcha.com/res.php 請求,帶上任務ID加上一些其他參數,會返回三個值,返回的三個值+用戶名密碼等的向目標網站請求,就可以通過驗證了
開始行動
在目標網站上,我們尋找一下gt,challenge,api_server三個東西,我們切換到嗶哩嗶哩找一下
我們點擊network,刷新網頁,重新加載所有請求,crtl+f,搜索challenge,竟然發現
combine 這個接口返回的是這個
竟然在 https://passport.bilibili.com/web/captcha/combine?plat=11 請求中 找到了,但是到底之不是這個呢,人家 2captcha文檔 說了,通常可以在initGeetest發現他,我們嘗試下
點擊Elements,按ctrl+shift+f全局搜索一下,搜索 initGeetest
還真有一個,我們點進去看看
還真有這個,我們打上斷點,再次刷新,匹配一下是否和network里的一樣
上面是斷點的值,下面是network的值,至少看着一樣的,我們至少可以確定,有很大關系
至少我們確定了兩個值,gt和challenge,還差一個api_server
我們隨便輸入賬號密碼點擊登錄一下,觸發一下極驗,在elements中,搜索api_server
紅色圈起來的地方就表示是api_server,基本參數都找齊了
剛才我們也說了,參數都找齊了,那我們就該請求打碼平台了
那我們,就干吶,前面說到,在network中,請求 https://passport.bilibili.com/web/captcha/combine?plat=11 就可以獲得gt,challenge,外加一個key
Ok,我們來請求一下
這樣,我們就拿到了gt,challenge
我們請求一下打碼平台的接口,帶上自己參數
打碼平台需要請求兩次,第一次返回的是任務ID,第二次才是滑動模塊的成功值
注:challenge是動態的,其他的是靜態的
API_KEY是打碼平台的key
兩個函數,我們就成功的拿到了打碼平台返回的值
紅色圈起來的,就是破解極驗的第一個關鍵參數,這個參數拿到之后呢,就已經跟人家打碼平台沒關系啦,我們只需要帶着相關參數,登錄嗶哩嗶哩就好了,但是這個參數要往哪發呢,在network經過一番查找后,似乎發現一個和登錄有關的接口
https://passport.bilibili.com/web/login/v2
我們可以看到,紅色框圈起來的部分,正式 2captcha平台 返回給我們的數據,key,正是 https://passport.bilibili.com/web/captcha/combine?plat=11 返回的key,但是password,進行了加密,他是如何加密的呢
經過不斷的斷點,不斷地斷點.....終於確定了,密碼會經過這個函數進行加密,它本質是 RSA非對稱加密 聽着就嚇人,不慌,盤它,
這個函數邏輯是先請求//passport.bilbilli.com/login?act=get&r="" ,帶上一個隨機數,然后會返回一個隨機hash,和一個公鑰key
公鑰key是固定的,然后將隨機hash和密碼進行加密,發送后他后,后台進行解密
破解代碼
通過上述兩個函數,就模擬出了密碼,最后,最后,我們只需要拼接所有參數,請求一下就ok了
示例效果
如果賬號密碼錯誤
如果賬號密碼正確
第一個表示跳轉的url,第二個是返回的cookie,以后我們想干什么,只需要帶着這個cookie就好了
完整代碼
from pprint import pprint import time import random import requests import base64 from Crypto.PublicKey import RSA from Crypto.Cipher import PKCS1_v1_5 as Cipher_pkcs1_v1_5 API_KEY = "be308827049bfeb0c4c222b76e8b1c92" method = "geetest" gt = "b6cc0fc51ec7995d8fd3c637af690de3" # challenge = "0fb2ae2da43962c1f7aec1dd3f9a58fe" pageurl = "https://passport.bilibili.com/login" api_server = "api.geetest.com" def getChallengeAndKey(): commbine_header = { "Accept": "application/json, text/plain, */*", "Accept-Encoding": "gzip, deflate, br", "Accept-Language": "zh-CN,zh;q=0.9", "Connection": "keep-alive", # "Cookie": "sid=9qe9dmi7", "Host": "passport.bilibili.com", "Referer": "https://passport.bilibili.com/login", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36" } commbine_url = "https://passport.bilibili.com/web/captcha/combine?plat=11" response_commbine = requests.get(url=commbine_url, headers=commbine_header) # print(response_commbine.text) # pprint(response_commbine.json()) key = response_commbine.json().get("data").get("result").get("key") challenge = response_commbine.json().get("data").get("result").get("challenge") return key, challenge def get2CaptchaChallengeAndValidateSeccode(challenge): captcha_url = f"https://2captcha.com/in.php?key={API_KEY}&method={method}>={gt}&challenge={challenge}&pageurl={pageurl}&api_server={api_server}&json=1" r = requests.get(captcha_url) print(r.json()) rid = r.json().get("request") # print(rid, type(rid)) time.sleep(15) while True: re_cpatcha_url = f"https://2captcha.com/res.php?key={API_KEY}&action=get&id={int(rid)}&json=1" # print(re_cpatcha_url) r2 = requests.get(re_cpatcha_url) print(r2.json()) if r2.json().get("status") == 1: geetest_challenge = r2.json().get("request").get("geetest_challenge") geetest_validate = r2.json().get("request").get("geetest_validate") geetest_seccode = r2.json().get("request").get("geetest_seccode") return geetest_challenge, geetest_validate, geetest_seccode time.sleep(5) # 密碼加密 def crack_pwd(hash: str, pwd: str): key = """-----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDjb4V7EidX/ym28t2ybo0U6t0n 6p4ej8VjqKHg100va6jkNbNTrLQqMCQCAYtXMXXp2Fwkk6WR+12N9zknLjf+C9sx /+l48mjUU8RqahiFD1XT/u2e0m2EN029OhCgkHx3Fc/KlFSIbak93EH/XlYis0w+ Xl69GV6klzgxW6d2xQIDAQAB -----END PUBLIC KEY----- """ # 注意上述key的格式 rsakey = RSA.importKey(key) cipher = Cipher_pkcs1_v1_5.new(rsakey) # 生成對象 new_pwd = hash + pwd cipher_text = base64.b64encode( cipher.encrypt(new_pwd.encode("utf-8")) ) # 對傳遞進來的用戶名或密碼字符串加密 value = cipher_text.decode('utf8') # 將加密獲取到的bytes類型密文解碼成str類型 return value # 獲取key def get_act(): act_header = { "Accept": "*/*", "Accept-Encoding": "gzip, deflate, br", "Accept-Language": "zh-CN,zh;q=0.9", "Connection": "keep-alive", "Cookie": "sid=9qe9dmi7; _uuid=A8F38E21-6734-4291-C4EC-404AEA0294C750293infoc; buvid3=8548F035-99E8-41F8-BDA1-C63065B96FD5155813infoc", "Host": "passport.bilibili.com", "Referer": "https://passport.bilibili.com/login", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36", "X-Requested-With": "XMLHttpRequest" } r1 = random.random() c_url = f"https://passport.bilibili.com/login?act=getkey&r={r1}" print("url:", c_url) response = requests.get(c_url, headers=act_header) # print(response.json()) hash = response.json().get("hash") key = response.json().get("key") # print(hash) # print(key) return hash, key def login_v2(): login_v2_header = { "Accept": "application/json, text/plain, */*", "Accept-Encoding": "gzip, deflate, br", "Accept-Language": "zh-CN,zh;q=0.9", "Connection": "keep-alive", # "Cookie": "sid=9qe9dmi7", "Host": "passport.bilibili.com", "Referer": "https://passport.bilibili.com/login", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36" } login_v2_url = "https://passport.bilibili.com/web/login/v2" r1 = requests.post(login_v2_url, headers=login_v2_header, data=login_v2_dict) pprint(r1.json()) print(r1.cookies.get_dict()) if __name__ == '__main__': username = "1234" password = "1234" login_v2_dict = { "captchaType": 11, # ok "username": username, # ok # 需要構建 js 獲取密碼 "password": "", "keep": True, # 通過 commbine 獲取 "key": "", "goUrl": "", # 通過 2captcha 獲取 "challenge": "", "validate": "", "seccode": "" } v2_key, challenge = getChallengeAndKey() # print(v2_key, challenge) geetest_challenge, geetest_validate, geetest_seccode = get2CaptchaChallengeAndValidateSeccode(challenge) # print(geetest_challenge) # print(geetest_validate) # print(geetest_seccode) hash, key_public_key = get_act() n = crack_pwd(hash, password) login_v2_dict["key"] = v2_key login_v2_dict["challenge"] = geetest_challenge login_v2_dict["validate"] = geetest_validate login_v2_dict["seccode"] = geetest_seccode login_v2_dict["password"] = n print(n) print(login_v2_dict) login_v2()