python爬蟲21 | 對於b站這樣的滑動驗證碼,不好意思,照樣自動識別


今天

 

要來說說滑動驗證碼了

 

大家應該都很熟悉

 

點擊滑塊然后移動到圖片缺口進行驗證

 

 

現在越來越多的網站使用這樣的驗證方式

 

為的是增加驗證碼識別的難度

 

 

那么

 

對於這種驗證碼

 

應該怎么破呢

 

接下來就是

 

學習 python 的正確姿勢

 

 

打開 b 站的登錄頁面

 

https://passport.bilibili.com/login

 

 

可以看到登錄的時候需要進行滑塊驗證

 

按下 F12

 

進入 Network

 

看下我們將滑塊移到缺口松開之后做了什么提交

 

 

可以看到是一個 GET 請求

 

但是

 

這請求鏈接也太特么長了吧

 

就是比小帥b短了一點點

 

 

我們來看看請求的參數是怎么樣的

 

 

哇靠

 

gt?

 

challenge?

 

w?

 

這些都是什么鬼參數

 

還加密了

 

完全下不了手啊

 

 

那么

 

本篇完

 

再見

 

peace

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

你是不是迷戀我??

 

 

好吧

 

你居然滑到這里來了

 

說明你還是有點小帥b的

 

小帥b是那種遇到一點困難就放棄的人嗎

 

顯然不是

 

那么接下來才是真的

 

學習 python 的正確姿勢

 

 

既然以請求的方式不好弄

 

我們從它們的源代碼入手

 

看看有什么突破口

 

 

回到 b 站的登錄頁

 

按下 F12

 

進入 Element

 

然后點擊滑塊出現了圖片

 

定位一下

 

 

發現有兩個 a 標簽

 

一個 class 是 gt_bg gt_show

 

一個 class 是 gt_fullbg gt_show

 

和小帥b想的一樣

 

這個驗證碼應該是有兩張圖片

 

一張是完全的背景圖片

 

一張是缺口的圖片

 

那把這兩張圖片下載下來對比一下不就行了

 

打開 a 標簽一看

 

 

哇靠

 

一張圖片被切割成很多小塊

 

原來這張圖片是拼出來的

 

我們看看原始圖片是怎么樣的

 

 

什么亂七八糟的

 

再仔細看下源代碼

 

原來是在同一張圖片通過偏移量合成了一張完整的圖片

 

background-position: -277px -58px;

 

厲害厲害

 

小帥b看了一下缺口的圖片也是如此

 

 

 

到這里

 

我們的第一個思路就是

 

下載這兩張原始圖片

 

然后通過偏移量合成兩張真正的圖片

 

背景圖

 

 

↓變身

 

 

 

缺口圖

 

 

↓變身

 

 

那么怎么做呢?

 

因為我們還要模擬滑動滑塊

 

所以呢

 

我們要用到 selenium

 

打開b站的登錄頁

 

然后等到那個滑塊顯示出來

 

 # 獲取滑塊按鈕 driver.get(url) slider = WAIT.until(EC.element_to_be_clickable( (By.CSS_SELECTOR, "#gc-box > div > div.gt_slider > div.gt_slider_knob.gt_show")))

 

接下來我們就獲取頁面的源碼

 

driver.page_source

 

然后使用 bs 獲取兩張原始背景圖片的 url 

 

 bs = BeautifulSoup(driver.page_source,'lxml') # 找到背景圖片和缺口圖片的div bg_div = bs.find_all(class_='gt_cut_bg_slice') fullbg_div = bs.find_all(class_='gt_cut_fullbg_slice')
# 獲取缺口背景圖片url bg_url = re.findall('background-image:\surl\("(.*?)"\)',bg_div[0].get('style'))    # 獲取背景圖片url fullbg_url = re.findall('background-image:\surl\("(.*?)"\)',fullbg_div[0].get('style'))

 

拿到了圖片地址之后

 

將圖片下載下來

 

 # 將圖片格式存為 jpg 格式 bg_url = bg_url[0].replace('webp', 'jpg') fullbg_url = fullbg_url[0].replace('webp', 'jpg') # print(bg_url) # print(fullbg_url)
# 下載圖片 bg_image = requests.get(bg_url).content fullbg_image = requests.get(fullbg_url).content print('完成圖片下載')

 

ok 

 

 



 

我們已經把兩張原始圖片下載下來了

 

 

那么接下來就是要合成圖片了

 

我們要根據圖片的位置來合成

 

也就是源碼中的 background-position

 

 

獲取每一個小圖片的位置

 

我們可以通過字典的形式來表示這些位置

 

然后將數據放到列表中

 

 # 存放每個合成缺口背景圖片的位置 bg_location_list = []    # 存放每個合成背景圖片的位置 fullbg_location_list = []
for bg in bg_div: location = {} location['x'] = int(re.findall('background-position:\s(.*?)px\s(.*?)px;', bg.get('style'))[0][0]) location['y'] = int(re.findall('background-position:\s(.*?)px\s(.*?)px;', bg.get('style'))[0][1]) bg_location_list.append(location)
for fullbg in fullbg_div: location = {} location['x'] = int(re.findall('background-position:\s(.*?)px\s(.*?)px;', fullbg.get('style'))[0][0]) location['y'] = int(re.findall('background-position:\s(.*?)px\s(.*?)px;', fullbg.get('style'))[0][1]) fullbg_location_list.append(location)

 

那么

 

現在我們已經有了原始圖片

 

還知道了每個位置應該顯示原始圖片的什么部分

 

接下來我們就寫一個方法

 

用來合成圖片

 

 # 寫入圖片 bg_image_file = BytesIO(bg_image) fullbg_image_file = BytesIO(fullbg_image)
# 合成圖片 bg_Image = mergy_Image(bg_image_file, bg_location_list) fullbg_Image = mergy_Image(fullbg_image_file, fullbg_location_list)

 

那么問題又來了

 

怎么合成啊

 

我們再看看一開始分析的圖片

 

 

這里圖片被分割成的每一個小圖片的尺寸是

 

10 * 58

 

所以我們也要將我們剛剛下載的原始圖片切割成相應的尺寸大小

 

而且

 

這張圖片是由上半部分的小圖片和下半部分的小圖片合成的

 

所以我們定義兩個 list 來裝這些小圖片

 

 # 存放上下部分的各個小塊 upper_half_list = [] down_half_list = []

 

然后將原始的圖片切割好放進去

 

image = Image.open(image_file)
# 通過 y 的位置來判斷是上半部分還是下半部分,然后切割 for location in location_list: if location['y'] == -58: # 間距為10,y:58-116 im = image.crop((abs(location['x']), 58, abs(location['x'])+10, 116)) upper_half_list.append(im) if location['y'] == 0: # 間距為10,y:0-58 im = image.crop((abs(location['x']), 0, abs(location['x']) + 10, 58)) down_half_list.append(im)

 

至此

 

我們這兩個 list 就分別放好了各個切割的圖片了

 

那么接下來就創建一張空白的圖片

 

然后將小圖片一張一張(間距為10)的粘貼到空白圖片里

 

這樣我們就可以得到一張合成好的圖片了

 

 

我真是個天才

 

 

 # 創建一張大小一樣的圖片 new_image = Image.new('RGB', (260, 116))
# 粘貼好上半部分 y坐標是從上到下(0-116) offset = 0 for im in upper_half_list: new_image.paste(im, (offset, 0)) offset += 10
# 粘貼好下半部分 offset = 0 for im in down_half_list: new_image.paste(im, (offset, 58)) offset += 10

 

那么到現在

 

我們可以得到網頁上顯示的那兩張圖片了

 

一張完全的圖片

 

 

一張帶缺口的圖片

 

 

接下來我們就要通過對比這兩張圖

 

看看我們要滑動的距離是多遠

 

 # 合成圖片 bg_Image = mergy_Image(bg_image_file, bg_location_list) fullbg_Image = mergy_Image(fullbg_image_file, fullbg_location_list) # bg_Image.show() # fullbg_Image.show()
# 計算缺口偏移距離 distance = get_distance(bg_Image, fullbg_Image) print('得到距離:%s' % str(distance))

 

可以通過圖片的 RGB 來計算

 

我們設定一個閾值

 

如果 r、g、b 大於這個閾值

 

我們就返回距離

 

def get_distance(bg_Image, fullbg_Image):
#閾值 threshold = 200
print(bg_Image.size[0]) print(bg_Image.size[1])

for i in range(60, bg_Image.size[0]): for j in range(bg_Image.size[1]): bg_pix = bg_Image.getpixel((i, j)) fullbg_pix = fullbg_Image.getpixel((i, j)) r = abs(bg_pix[0] - fullbg_pix[0]) g = abs(bg_pix[1] - fullbg_pix[1]) b = abs(bg_pix[2] - fullbg_pix[2])
if r + g + b > threshold: return i

 

現在

 

我們知道了關鍵的滑動距離

 

激動人心的時刻到了

 

我們使用 selenium

 

拿到滑塊的元素

 

然后根據這個距離拖動到缺口位置不就好了么

 

馬上打開 selenium 的文檔

 

看到了這個函數

 

 

它可以使用左鍵點擊元素

 

然后拖動到指定距離

 

最后釋放鼠標左鍵

 

挖槽

 

正合我意

 

趕緊試一下

 

knob = WAIT.until(EC.presence_of_element_located((By.CSS_SELECTOR, "#gc-box > div > div.gt_slider > div.gt_slider_knob.gt_show")))ActionChains(driver).drag_and_drop_by_offset(knobdistance, 0).perform()

 

運行一下試試看吧

 

 

哇哦你妹哦~

 

妖怪吃了拼圖了

 

 

看來直接拖拽是不行的

 

容易遇到妖怪

 

畢竟這太快了

 

就算加藤鷹也沒那么快吧

 

小帥b試着拖完滑塊讓它睡一下再釋放

 

 ActionChains(driver).click_and_hold(knob).perform()    ActionChains(driver).move_by_offset(xoffset=distance, yoffset=0.1).perform()    time.sleep(0.5)    ActionChains(driver).release(knob).perform()

 

發現拼圖還是特么的被妖怪吃了

 

 

后來小帥b發現原來別人也遇到了這樣的問題

 

然后又發現了

 

有個叫勻速直線運動的東西

 

什么 加速度

 

什么 v = v0 + at

 

什么 s = ½at²

 

 

這不是高中的知識點么

 

瞬間想起小帥b高中的時候在最角落的課桌

 

此刻往右上方抬起頭

 

 45 度角

 

讓我的眼淚划出一條美麗的弧線

 

 

什么鬼

 

回到正題

 

我們可以使用它來構造一個運動路徑

 

該加速時加速

 

該減速的時候減速

 

這樣的話就更像人類在滑動滑塊了

 


def get_path(distance): result = [] current = 0 mid = distance * 4 / 5 t = 0.2 v = 0 while current < (distance - 10): if current < mid: a = 2 else: a = -3 v0 = v v = v0 + a * t s = v0 * t + 0.5 * a * t * t current += s result.append(round(s)) return result

 

這次

 

我們使用這個軌跡來滑動

 

 knob = WAIT.until(EC.presence_of_element_located((By.CSS_SELECTOR, "#gc-box > div > div.gt_slider > div.gt_slider_knob.gt_show"))) result = get_path(distance) ActionChains(driver).click_and_hold(knob).perform()
for x in result: ActionChains(driver).move_by_offset(xoffset=x, yoffset=0).perform()
time.sleep(0.5) ActionChains(driver).release(knob).perform()

 

好了好了

 

我們再來運行一下吧

 

 

哈哈哈

 

cool

 

成功識別了哇

 

我不管

 

 

當然了

 

成功率不是 100%

 

可以多調戲它幾次

 

ok

 

以上就是識別滑動驗證碼的具體過程了

 

對於其它大部分的滑動驗證碼

 

也是可以使用這招搞定的

 

由於篇幅有限

 

源代碼我放在了這個公眾號后台了

 

你發送〔滑動〕兩個字

 

就可以獲取啦

 

 

這次本篇就真的完啦

 

聽說你想約我?

 

peace

 

 

相關文章

 

(小帥b教你三招搞定模擬登錄)

 

(小帥b教你輕松識別圖片驗證碼)

 

 

 

      點個在看啊~~(破音)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM