今天
要來說說滑動驗證碼了
大家應該都很熟悉
點擊滑塊然后移動到圖片缺口進行驗證
現在越來越多的網站使用這樣的驗證方式
為的是增加驗證碼識別的難度
那么
對於這種驗證碼
應該怎么破呢
接下來就是
打開 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).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:
(offset, 0))
offset += 10
# 粘貼好下半部分
offset = 0
for 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 = 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(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 = 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
相關文章
點個在看啊~~(破音)