今天
要來說說滑動驗證碼了
大家應該都很熟悉
點擊滑塊然后移動到圖片缺口進行驗證

現在越來越多的網站使用這樣的驗證方式
為的是增加驗證碼識別的難度

那么
對於這種驗證碼
應該怎么破呢
接下來就是

打開 b 站的登錄頁面
https://passport.bilibili.com/login

可以看到登錄的時候需要進行滑塊驗證
按下 F12
進入 Network
看下我們將滑塊移到缺口松開之后做了什么提交

可以看到是一個 GET 請求
但是
這請求鏈接也太特么長了吧
就是比小帥b短了一點點

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

哇靠
gt?
challenge?
w?
這些都是什么鬼參數
還加密了
完全下不了手啊

那么
本篇完
再見
peace
說
你是不是迷戀我??

好吧
你居然滑到這里來了
說明你還是有點愛小帥b的
小帥b是那種遇到一點困難就放棄的人嗎
顯然不是
那么接下來才是真的

既然以請求的方式不好弄
我們從它們的源代碼入手
看看有什么突破口

回到 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')bg_div = bs.find_all(class_='gt_cut_bg_slice')fullbg_div = bs.find_all(class_='gt_cut_fullbg_slice')bg_url = re.findall('background-image:\surl\("(.*?)"\)',bg_div[0].get('style'))fullbg_url = re.findall('background-image:\surl\("(.*?)"\)',fullbg_div[0].get('style'))
拿到了圖片地址之后
將圖片下載下來
bg_url = bg_url[0].replace('webp', 'jpg')fullbg_url = fullbg_url[0].replace('webp', 'jpg')bg_image = requests.get(bg_url).contentfullbg_image = requests.get(fullbg_url).contentprint('完成圖片下載')
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-116im = image.crop((abs(location['x']), 58, abs(location['x'])+10, 116))upper_half_list.append(im)if location['y'] == 0:# 間距為10,y:0-58im = 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 = 0for im in upper_half_list:(offset, 0))offset += 10# 粘貼好下半部分offset = 0for im in down_half_list:(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)% str(distance))
可以通過圖片的 RGB 來計算
我們設定一個閾值
如果 r、g、b 大於這個閾值
我們就返回距離
def get_distance(bg_Image, fullbg_Image):#閾值threshold = 200print(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(knob, distance, 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 = 0mid = distance * 4 / 5t = 0.2v = 0while current < (distance - 10):if current < mid:a = 2else:a = -3v0 = vv = v0 + a * ts = v0 * t + 0.5 * a * t * tcurrent += sresult.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

相關文章
點個在看啊~~(破音)



