前言
上篇文章記錄了2種分割驗證碼的方法,此外還有一種叫做”滴水算法”(Drop Fall Algorithm)的方法,但本人智商原因看這個算法看的云里霧里的,所以今天記錄滑動驗證碼的處理吧。網上據說有大神已經破解了滑動驗證碼的算法,可以不使用selenium來破解,但本人能力不足還是使用笨方法吧。
基礎原理很簡單,首先點擊驗證碼按鈕后的圖片是滑動后的完整結果,點擊一下滑塊后會出現拼圖,對這2個分別截圖后比較像素值來找出滑動距離,并結合selenium來實現拖拽效果。
至于selenium怎么安裝就不說了,滑動驗證碼的一個難點就是要模擬人的拖拽行為,移動快了不行,慢了也不行。
這里以國家企業公示網站為例:
# -*- coding: utf-8 -*-import timeimport randomfrom io import BytesIOfrom PIL import Imagefrom selenium import webdriverfrom selenium.webdriver.common.by import Byfrom selenium.webdriver import ActionChainsfrom selenium.webdriver.support.ui import WebDriverWaitfrom selenium.webdriver.support import expected_conditions as ECclass Slide(object): """滑動驗證碼破解""" def __init__(self, target): self.target = target # 要搜索的公司名稱 self.driver = webdriver.Chrome() self.wait = WebDriverWait(self.driver, 10) def crop(self, left, top, right, bottom, pic_name): """截屏并裁剪""" ss = Image.open(BytesIO(self.driver.get_screenshot_as_png())) cp = ss.crop((left, top, right, bottom)) # 注意這里順序 cp.save(pic_name) return cp def calc_move(self, pic1, pic2): """根據閾值計算移動距離""" pix1 = pic1.load() pix2 = pic2.load() threshold = 200 move = 0 # 因為滑塊都從左向右滑動,而碎片本身寬度為60所以從60開始遍歷 for i in range(60, pic1.size[0]): flag = False for j in range(pic1.size[1]): r = abs(pix1[i, j][0] - pix2[i, j][0]) g = abs(pix1[i, j][1] - pix2[i, j][1]) b = abs(pix1[i, j][2] - pix2[i, j][2]) # if r > threshold and g > threshold and b > threshold: # 方法1:分別判斷rgb大于閾值 # flag = True # break if r + g + b > threshold: # 方法2:判斷rgb總和跟閾值比較,效果比1好 為什么呢?? flag = True break if flag: move = i break return move def path1(self, distance): """繪制移動路徑方法1,構造一個等比數列""" q = 0.4 # 測試后發現0.4效果最佳 n = 10 # 最多移動幾次 a1 = ((1 - q) * distance) / (1 - q**n) result = [] for o in range(1, n + 1): an = a1 * q**(o - 1) if an < 0.1: # 小于移動閾值的就不要了 break t = random.uniform(0, 0.5) # 測試后0.5秒的間隔成功率最高 result.append([an, 0, t]) return result def path2(self, distance): """繪制移動路徑方法2,模擬物理加速、減速運動,效果比1好""" result = [] current = 0 # 減速閾值 mid = distance * 4 / 5 # 計算間隔 t = 0.2 # 初速度 v = 0 while current < (distance - 10): if current < mid: # 加速度為正2 a = 2 else: # 加速度為負3 a = -3 # 初速度v0 v0 = v # 當前速度v = v0 + at v = v0 + a * t # 移動距離x = v0t + 1/2 * a * t^2 move = v0 * t + 0.5 * a * t * t # 當前位移 current += move # 加入軌跡 result.append([round(move), 0, random.uniform(0, 0.5)]) return result def run(self): self.driver.get("http://www.gsxt.gov.cn/index") input_box = self.driver.find_element_by_id('keyword') input_box.send_keys(self.target) search_btn = self.driver.find_element_by_id('btn_query') time.sleep(3) # 注意這里等一下再點,否則會出現卡死現象 search_btn.click() # 等待驗證碼彈出 bg_pic = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, "gt_cut_fullbg"))) # html中坐標原點是左上角,右為x軸正方向,下為y軸正方向 # 輸出的x為正就是此元素距離屏幕左側距離 # 輸出的y為正就是此元素距離屏幕上側距離 # 所以我們需要截圖的四個距離如下: top, bottom, left, right = ( bg_pic.location['y'], bg_pic.location['y'] + bg_pic.size['height'], bg_pic.location['x'], bg_pic.location['x'] + bg_pic.size['width']) time.sleep(1) cp1 = self.crop(left, top, right, bottom, '1.png') # 獲取滑塊按鈕并點擊一下 slide = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, "gt_slider_knob"))) slide.click() time.sleep(3) # 等3秒報錯信息消失 TODO 這里應該可以改進 cp2 = self.crop(left, top, right, bottom, '2.png') move = self.calc_move(cp1, cp2) result = self.path1(move) # result = self.path2(move) # 拖動滑塊 ActionChains(self.driver).click_and_hold(slide).perform() for x in result: ActionChains(self.driver).move_by_offset(xoffset=x[0],yoffset=x[1]).perform() # ActionChains(driver).move_to_element_with_offset(to_element=slide,xoffset=x[0],yoffset=x[1]).perform() time.sleep(x[-1]) # 如果使用方法1則需要sleep time.sleep(0.5) ActionChains(self.driver).release(slide).perform() # 釋放按鈕 time.sleep(0.8) element = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, "gt_info_text"))) ans = element.text if u"通過" in ans: # 這里也需要等一下才能獲取到具體的鏈接 element = self.wait.until(EC.presence_of_all_elements_located((By.CLASS_NAME, "search_list_item"))) for o in self.driver.find_elements_by_xpath(u"http://a[@target='_blank']"): print(o.get_attribute("href")) self.driver.quit() else: print("識別失敗") self.driver.quit()if __name__ == '__main__': s = Slide('中國平安') s.run()
新聞熱點
疑難解答