由於京東的滑塊驗證碼只提供缺口圖片,所以我通過不斷刷新驗證碼發現其圖片庫總共只有10張,然后我提前將不同的缺口圖片進行合成已獲得完整的參照圖片並保存在指定的文件夾中以備用。之后的具體步驟為:
首先用selenium打開京東登錄頁面並點擊賬號密碼登錄方式,自動填充帳號密碼點擊登錄出現驗證碼,獲取驗證碼缺口圖片,相關代碼為:
url = 'https://passport.jd.com/new/login.aspx?'
options = webdriver.ChromeOptions()
options.add_experimental_option('excludeSwitches', ['enable-automation'])
browser = webdriver.Chrome(options=options, executable_path='chromedriver.exe')
wait = WebDriverWait(browser, 10)
browser.maximize_window()
browser.get(url)
time.sleep(2)
browser.find_element_by_class_name('login-tab-r').click()
time.sleep(1)
browser.find_element_by_id('loginname').send_keys(username)
browser.find_element_by_id('nloginpwd').send_keys(password)
browser.find_element_by_class_name('login-btn').click()
time.sleep(1)
img_url = browser.find_element_by_class_name('JDJRV-bigimg').find_element_by_css_selector('img').get_attribute('src')
image1_bytes = base64.urlsafe_b64decode(img_url.split(',')[-1])
image1 = Image.open(io.BytesIO(image1_bytes)).convert("RGB")
將缺口圖片與完整圖片集對比取得對應的完整圖片,簡單判斷方法為定義一個值表示兩張圖片的像素點rgb數值之差的絕對值的總和,值越小相似性越高,如下:
for j in range(image.size[1]): for i in range(image.size[0]): delta = abs(pixel1[i, j][0] - pixel2[i, j][0]) + abs(pixel1[i, j][1] - pixel2[i, j][1]) + abs( pixel1[i, j][2] - pixel2[i, j][2]) diff += delta
之后比較兩張圖片計算出缺口的位置,並拖動滑塊到缺口處完成驗證,這里比較困難的主要是軌跡方程,我用先加速后減速的方法試了好幾個,但是都沒什么效果,成功率超級低
完整代碼如下:
from selenium import webdriver from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.common.action_chains import ActionChains from PIL import Image import time import base64 import os import io import random class Jingdong(): def __init__(self, username, password): self.username = username self.password = password self.url = 'https://passport.jd.com/new/login.aspx?' self.options = webdriver.ChromeOptions() self.options.add_experimental_option('excludeSwitches', ['enable-automation']) self.browser = webdriver.Chrome(options=self.options, executable_path='D:\Program Files (x86)\chromedriver.exe') self.wait = WebDriverWait(self.browser, 10) self.browser.maximize_window() def open_browser(self): self.browser.get(self.url) time.sleep(2) self.browser.find_element_by_class_name('login-tab-r').click() time.sleep(1) self.browser.find_element_by_id('loginname').send_keys(self.username) self.browser.find_element_by_id('nloginpwd').send_keys(self.password) self.browser.find_element_by_class_name('login-btn').click() time.sleep(1) def get_img_url(self): # 獲取驗證碼缺口圖片的地址 img_url = self.browser.find_element_by_class_name('JDJRV-bigimg').find_element_by_css_selector( 'img').get_attribute('src') return img_url @staticmethod def get_image(image1): # 將驗證碼的缺口圖片與已有文件夾中的完整圖片進行比較以取得其對應的完整圖片 difference = 0 image_ = image1 size = image1.size images = os.listdir('./img2') pixel1 = image1.load() for image in images: image = Image.open(f'./img2/{image}') pixel2 = image.load() diff = 0 for j in range(size[1]): for i in range(size[0]): delta = abs(pixel1[i, j][0] - pixel2[i, j][0]) + abs(pixel1[i, j][1] - pixel2[i, j][1]) + abs( pixel1[i, j][2] - pixel2[i, j][2]) diff += delta if difference == 0: difference = diff image_ = image elif difference > diff: difference = diff image_ = image return image_ @staticmethod def is_pixel_equal(image1, image2, x, y): # 判斷兩張圖片相同坐標的rgb數值是否近似 pixel1 = image1.load()[x, y] pixel2 = image2.load()[x, y] threshold = 5 if abs(pixel1[0] - pixel2[0]) < threshold and abs(pixel1[1] - pixel2[1]) < threshold and abs( pixel1[2] - pixel2[2]) < threshold: return True else: return False def get_gap(self): # 返回驗證碼缺口的距離 left = 60 img_url = self.get_img_url() image1_bytes = base64.urlsafe_b64decode(img_url.split(',')[-1]) image1 = Image.open(io.BytesIO(image1_bytes)).convert("RGB") image2 = self.get_image(image1) if not image2: self.browser.find_element_by_class_name('JDJRV-img-refresh').click() time.sleep(1) self.get_gap() else: for i in range(left, image1.size[0]): for j in range(image1.size[1]): if not self.is_pixel_equal(image1, image2, i, j): return i return left @staticmethod def get_track(distance): # 軌跡生成 track = [] track_ = [] delta_t = 0.1 for i in range(1, 21): delta_dis = 1 / 12 * distance * (delta_t * i) ** 3 - 1 / 12 * distance * (delta_t * (i - 1)) ** 3 track.append(round(delta_dis)) for i in range(1, 10): delta_dis = 1 / 3 * distance * (i * delta_t) ** 3 - 1 / 3 * distance * ((i - 1) * delta_t) ** 3 track_.append(round(delta_dis)) track.append(0) while len(track_) > 0: track.append(track_.pop()) track[20] = distance - sum(track) return track def verify(self): # 滑動滑塊進行驗證 distince = self.get_gap() if distince > 180: distince = distince - abs(180-distince)/180*40 - 40 else: distince = distince + abs(180-distince)/180*40 - 40 tracks = self.get_track(distince) slider = self.browser.find_element_by_class_name('JDJRV-slide-btn') ActionChains(self.browser).click_and_hold(slider).perform() for axis in tracks: yoffset = random.randint(-2, 2) ActionChains(self.browser).move_by_offset(xoffset=axis, yoffset=yoffset).perform() time.sleep(0.5) ActionChains(self.browser).release().perform() time.sleep(3) if self.browser.current_url != self.url: print("登錄成功") time.sleep(3) self.browser.close() else: self.verify() def main(self): self.open_browser() time.sleep(1) self.verify() if __name__ == '__main__': jingdong = Jingdong('username', 'password') jingdong.main()
