極驗驗證碼破解之selenium
這一篇寫完很久了,因為識別率一直很低,沒辦法拿出來見大家,所以一直隱藏著,今天終於可以拿出來見見陽光了。
哈嘍,大家好,我是星星在線,我又來了,今天給大家帶來的是極驗驗證碼的selenium破解之法,是不是有點小激動呢,小夥伴們等不了了,讓我們趕緊直入主題吧。
虎嗅網註冊
這次我們是拿虎嗅開刀,註冊賬號的時候需要滑動圖片到缺口位置,這種驗證碼我們現在也經常遇到,這個就不用詳細介紹了吧
針對這種驗證碼我們首先確定了使用selenium模擬滑動破解方式,selenium鼠標移動點擊拖動都比較簡單,那麽問題就在於拖動多少距離,眼睛看起來很直觀,但是程序怎麽獲取呢?利用圖像識別......,額,這個只能想想了吧。不如看看網頁源碼或者請求信息,看看有沒有有效的信息。
查看網頁信息
鼠標右鍵點擊到圖片上,查看元素
這一瞬間的圖片,還好我二十幾年的麒麟臂沒白練,我們看看元素查看到的都是什麽東西
這看起來有點奇怪哦,有個圖片鏈接,還有位置信息,而且還那麽多,先把圖片鏈接拷貝到瀏覽器裏訪問下看看
WF,這是什麽鬼?註意到那個像豬尾巴一樣的6了嗎?還有那個小箭頭,跟上面完整圖片對比一下,發現把箭頭挪動到小6旁邊,豬尾巴就成功了。當然你仔細觀察的話,還有其他的比如文字也是類似。那麽我們可以確認這張圖片應該是被打亂的,如果我們可以把它拼起來,是不是就離計算缺口位置比較近了。現在我們應該要註意到元素查看裏後面的位置信息了,那麽多,看起來應該跟這個打亂順序有點關系吧。我們來確認一下。我的想法是這樣子的,既然這個位置和拼圖有關,而且再看我們上面麒麟臂截的圖,我再標記一下
我們點擊查看元素的時候,瀏覽器會幫我們突出顯示一下,本來我是在圖片上點擊查看的,按照我的想法,它不是應該整張圖片突出顯示一下嗎?看起來好像不是這麽回事,只有那麽一小部分,而且上面還有元素信息,寬高類名,再回去看看圖3,位置坐標裏,前面應該是x軸,後面是y軸,y軸只有58和0,再根據圖2一看,圖片分為上下兩部分,再數一下div的數量,26塊,每一塊寬10x高58。按照這個來算的話,那麽整個圖片的寬就是260,高116,用截圖工具去拉一下圖片的寬高,基本吻合
接下來就是確定怎麽拼了。這裏很抱歉的告訴大家,豬沒了,等我寫到這裏再去查看網頁的時候,圖片已經刷新了。所以接下來的截圖可能不一樣,在這裏提前跟大家說明一下
這個差不多算中間位置了吧,查那麽一點點無所謂了
我去,這......跟我想的不太一樣呀,再找兩張看看,代表性及其強烈的
為了防止有人說我水字數,另外兩個角就不截圖了。到這一步可能有人納悶了,為啥?你剛才說圖片寬度260,為什麽坐標裏出現了289這樣的坐標,這不就是超標了嗎?一開始我也有這樣的疑惑,可能我們看到圖片比實際的小,也許人家在圖片外面還留了邊框呢,我一開始是這麽想的。但是這個坐標是前面url裏面的圖片坐標,然後我就去看了一下圖4
這個圖片盡然比較大,坐標問題有答案了,但是這個跟260有什麽關系呢?打亂的圖片比較大,拼好的小,那它是怎麽拼的呢?幸好我們看到了一個比較有用信息
看到這個-1px了嗎?它成功引起了我的註意,因為按照我的想法,如果是從拼圖裏拿出一部分拼成一個完成圖片的話,那麽最左邊拿出來的圖片,應該是從(0,0),(0,58),但是我們看到的是(1,0),(1,58),y值還是比較符合我們的預期的,第一部分從0開始,高58,第二部分從58開始。但是x值有點問題,按照1作為起點,那第二個應該是11,因為寬度是10,這是確定的,我們找找看
是13,難道每一小塊前面都多余了1個像素?按照這種的話也應該是12呀,按照這種方式我們繼續找一找剩下的,通過分析我們發現每個小塊+12作為下一個小塊的起點。這樣的話左右各去掉一個像素,寬度不就是10了嗎?而且每個小塊是12,26個是312,跟我們看到的拼圖大小差不多,說明我們分析的是正確的。按照元素裏提供的坐標,取寬度為10的大小即可。接下來分析一下這些坐標的意義。
坐標分析
分析一下我們圖9到圖12的截圖,首先說圖9,我本來覺得它x、y應該是0,就算不是0,也應該是各位數字吧,結果的y是58,這個算到下半截圖片區域了,x是157,跑中場去了。圖11呢,你的x應該在300左右,y應該100以上吧,結果y是0,到上半段,x是205,在中場偏後,離守門員還遠呢。這是怎麽肥事?不過我們發現了,圖9在元素裏是第一個,圖11在元素裏是最後一個,再結合坐標前面的y值全是58,後面的y值全是0,符合我們上半段下半段顛倒的想法了,然後你再分別查看圖9右邊/圖11左邊的元素就會發現,和元素裏面div的順序一樣。到這裏就差不多了。
總結一下:最終的圖片就是把拼圖,即圖4,按照x=157、y=58、w=10、h=58截取出來,放在上半部分第一個位置,x=145、y=58、w=10、h=58截取出來放在上半部分第二個位置,緊挨著第一個,以此類推,拼成一張整圖。
這個就是我拼出來的,恩,很好,很不錯嘛小夥子。不過好像哪裏不對,缺口嘞。仔細看看網頁元素
原來一個是fullbg,一個是cutbg,這個名字就很有寓意嘛,那就好了,再把cutbg拼一下看看
這回就對上了。現在的問題就變成怎麽計算缺口位置了
缺口位置
我覺得可能會有計算兩張圖片不同位置的方式吧,度娘來一發,然後獲取了python實戰===用python對比兩張圖片的不同,然後發現了ImageChops.difference這個接口,結果你們知道的,不準確,為啥捏?仔細看拼好的兩張圖,除了缺口還有其他地方不一樣呀。看到圖16缺口後面那個陰影沒,讓我的心裏蒙上了一層陰影,再觀察其他的圖片,基本都有類似的,這可怎麽辦?這在後面還好說,如果是在前面呢,那不就計算到陰影裏去了嘛。如果這個對比有一個容差就好了,我以前用按鍵精靈的時候好像就有這種嘛,這個好不智能呀。既然它是對比像素,我直接取像素對比一下不就得了,而且我還不給它用==,給它一個範圍,如果色差在這個範圍內就算一樣了,這樣不就有容差了嗎?這個缺口一般都非常明顯,而陰影跟背景又很模糊,應該是可行的。思路就是獲取圖片的寬高,然後一個像素一個像素的遍歷對比。
色差
這個色差怎麽確定?一種方式就是調試,這種是比較麻煩的,還有一種方式就是獲取多張圖片,全圖和缺陷圖,然後使用取色工具,取對應位置的顏色值,確定一個大概範圍。距離確定了,下面就是移動了
selenium模擬移動
selenium的模擬操作網上介紹很多,這裏我們只要確認需要哪些接口就行了。
ActionChains方法:
- move_to_element(to_element) - 鼠標移動到某個元素
- click_and_hold(on_element =None) - 點擊鼠標左鍵,不松開
- move_by_offset(xoffset,yoffset) - 鼠標從當前位置移動到某個坐標
- release(on_element = None) - 在某個元素位置松開鼠標左鍵
- perform() - 執行操作,記住這個很重要,調用上面的方法後,一定要執行perform才能真正執行
selenium的操作我就不詳細描述了,這裏用到的都是比較簡單的用法。
原理分析就完了,這一次必須要貼代碼了,否則可能很多人完成不了,也有利於大家的理解。
# -*- coding: utf-8 -*-
import random
import time, re
from selenium import webdriver
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.action_chains import ActionChains
from PIL import Image
import requests
from io import BytesIO
class HuXiu(object):
def __init__(self):
chrome_option = webdriver.ChromeOptions()
# chrome_option.set_headless()
self.driver = webdriver.Chrome(executable_path=r"/usr1/webdrivers/chromedriver", chrome_options=chrome_option)
self.driver.set_window_size(1440, 900)
def visit_index(self):
self.driver.get("https://www.huxiu.com/")
WebDriverWait(self.driver, 10, 0.5).until(EC.element_to_be_clickable((By.XPATH, ‘//*[@class="js-register"]‘)))
reg_element = self.driver.find_element_by_xpath(‘//*[@class="js-register"]‘)
reg_element.click()
WebDriverWait(self.driver, 10, 0.5).until(EC.element_to_be_clickable((By.XPATH, ‘//div[@class="gt_slider_knob gt_show"]‘)))
# 進入模擬拖動流程
self.analog_drag()
def analog_drag(self):
#鼠標移動到拖動按鈕,顯示出拖動圖片
element = self.driver.find_element_by_xpath(‘//div[@class="gt_slider_knob gt_show"]‘)
ActionChains(self.driver).move_to_element(element).perform()
time.sleep(3)
# 刷新一下極驗圖片
element = self.driver.find_element_by_xpath(‘//a[@class="gt_refresh_button"]‘)
element.click()
time.sleep(1)
# 獲取圖片地址和位置坐標列表
cut_image_url, cut_location = self.get_image_url(‘//div[@class="gt_cut_bg_slice"]‘)
full_image_url, full_location = self.get_image_url(‘//div[@class="gt_cut_fullbg_slice"]‘)
# 根據坐標拼接圖片
cut_image = self.mosaic_image(cut_image_url, cut_location)
full_image = self.mosaic_image(full_image_url, full_location)
# 保存圖片方便查看
cut_image.save("cut.jpg")
full_image.save("full.jpg")
# 根據兩個圖片計算距離
distance = self.get_offset_distance(cut_image, full_image)
# 開始移動
self.start_move(distance)
# 如果出現error
try:
WebDriverWait(self.driver, 5, 0.5).until(EC.element_to_be_clickable((By.XPATH, ‘//div[@class="gt_ajax_tip gt_error"]‘)))
print("驗證失敗")
return
except TimeoutException as e:
pass
# 判斷是否驗證成功
try:
WebDriverWait(self.driver, 10, 0.5).until(EC.element_to_be_clickable((By.XPATH, ‘//div[@class="gt_ajax_tip gt_success"]‘)))
except TimeoutException:
print("again times")
time.sleep(5)
# 失敗後遞歸執行拖動
self.analog_drag()
else:
# 成功後輸入手機號,發送驗證碼
self.register()
# 獲取圖片和位置列表
def get_image_url(self, xpath):
link = re.compile(‘background-image: url\("(.*?)"\); background-position: (.*?)px (.*?)px;‘)
elements = self.driver.find_elements_by_xpath(xpath)
image_url = None
location = list()
for element in elements:
style = element.get_attribute("style")
groups = link.search(style)
url = groups[1]
x_pos = groups[2]
y_pos = groups[3]
location.append((int(x_pos), int(y_pos)))
image_url = url
return image_url, location
# 拼接圖片
def mosaic_image(self, image_url, location):
resq = requests.get(image_url)
file = BytesIO(resq.content)
img = Image.open(file)
image_upper_lst = []
image_down_lst = []
for pos in location:
if pos[1] == 0:
# y值==0的圖片屬於上半部分,高度58
image_upper_lst.append(img.crop((abs(pos[0]), 0, abs(pos[0]) + 10, 58)))
else:
# y值==58的圖片屬於下半部分
image_down_lst.append(img.crop((abs(pos[0]), 58, abs(pos[0]) + 10, img.height)))
x_offset = 0
# 創建一張畫布,x_offset主要為新畫布使用
new_img = Image.new("RGB", (260, img.height))
for img in image_upper_lst:
new_img.paste(img, (x_offset, 58))
x_offset += img.width
x_offset = 0
for img in image_down_lst:
new_img.paste(img, (x_offset, 0))
x_offset += img.width
return new_img
# 判斷顏色是否相近
def is_similar_color(self, x_pixel, y_pixel):
for i, pixel in enumerate(x_pixel):
if abs(y_pixel[i] - pixel) > 50:
return False
return True
# 計算距離
def get_offset_distance(self, cut_image, full_image):
for x in range(cut_image.width):
for y in range(cut_image.height):
cpx = cut_image.getpixel((x, y))
fpx = full_image.getpixel((x, y))
if not self.is_similar_color(cpx, fpx):
img = cut_image.crop((x, y, x + 50, y + 40))
# 保存一下計算出來位置圖片,看看是不是缺口部分
img.save("1.jpg")
return x
# 開始移動
def start_move(self, distance):
element = self.driver.find_element_by_xpath(‘//div[@class="gt_slider_knob gt_show"]‘)
# 這裏就是根據移動進行調試,計算出來的位置不是百分百正確的,加上一點偏移
distance -= element.size.get(‘width‘) / 2
distance += 15
# 按下鼠標左鍵
ActionChains(self.driver).click_and_hold(element).perform()
time.sleep(0.5)
while distance > 0:
if distance > 10:
# 如果距離大於10,就讓他移動快一點
span = random.randint(5, 8)
else:
# 快到缺口了,就移動慢一點
span = random.randint(2, 3)
ActionChains(self.driver).move_by_offset(span, 0).perform()
distance -= span
time.sleep(random.randint(10,50)/100)
ActionChains(self.driver).move_by_offset(distance, 1).perform()
ActionChains(self.driver).release(on_element=element).perform()
def register(self):
element = self.driver.find_element_by_xpath(‘//input[@id="sms_username"]‘)
element.clear()
element.send_keys("手機號")
ele_captcha = self.driver.find_element_by_xpath(‘//span[@class="js-btn-captcha btn-captcha"]‘)
ele_captcha.click()
if __name__ == "__main__":
h = HuXiu()
h.visit_index()
這個移動move_by_offset,我之前的y值也是隨機的[-5,5],我覺得這個模擬會更真實一點,總會上下抖動的嘛,結果就是因為這個考慮的太人性了,識別率非常低,改了好多範圍,更大的、更小的,結果最後不偏移,竟然識別率奇高。TMD考慮的太人性化了竟然識別不了,我也是醉了。最後再把執行效果發一下吧
如果你覺得我的文章還可以,可以關註我的微信公眾號:Python爬蟲實戰之路
也可以掃描下面二維碼,添加我的微信號
極驗驗證碼破解之selenium