├── README.md └── jiyan_code.py /README.md: -------------------------------------------------------------------------------- 1 | # verification_code 2 | 验证码的破解 3 | 包括普通图片验证、极验、点验等 4 | -------------------------------------------------------------------------------- /jiyan_code.py: -------------------------------------------------------------------------------- 1 | 2 | import requests 3 | from selenium import webdriver 4 | from selenium.webdriver.support.wait import WebDriverWait 5 | from selenium.webdriver.support import expected_conditions as EC 6 | from selenium.webdriver.common.by import By 7 | from selenium.webdriver import ActionChains 8 | import time 9 | from PIL import Image 10 | from io import BytesIO 11 | 12 | 13 | BORDER = 6 14 | INIT_LEFT = 60 15 | 16 | 17 | class JiyanCode(): 18 | def __init__(self): 19 | self.url = 'http://account.geetest.com/login' 20 | self.bro = webdriver.Chrome() 21 | self.wait = WebDriverWait(self.bro, 20) 22 | self.email = '815490913@qq.com' 23 | self.password = '123456' 24 | 25 | def __del__(self): 26 | self.bro.close() 27 | 28 | def get_url(self): 29 | """ 30 | 打开url,填写密码 31 | :return: 32 | """ 33 | self.bro.get(self.url) 34 | email = self.wait.until(EC.presence_of_element_located((By.ID, 'email'))) 35 | password = self.wait.until(EC.presence_of_element_located((By.ID, 'password'))) 36 | email.send_keys(self.email) 37 | password.send_keys(self.password) 38 | 39 | def get_button(self): 40 | """ 41 | 模拟点击初始验证 42 | :return: 43 | """ 44 | button = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'geetest_radar_tip'))) 45 | button.click() 46 | 47 | def get_position(self): 48 | """ 49 | 获取验证码位置 50 | :return:验证码位置元组 51 | """ 52 | # image = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'geetest_canvas_img geetest_absolute'))) 53 | img = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'geetest_canvas_img'))) 54 | time.sleep(2) 55 | location = img.location 56 | size = img.size 57 | top, bottom, left, right = location['y'], location['y'] + size['height'], location['x'], location['x'] + size[ 58 | 'width'] 59 | return (top, bottom, left, right) 60 | 61 | def get_screen(self): 62 | """ 63 | 获取页面截图 64 | :return: 65 | """ 66 | screen = self.bro.get_screenshot_as_png() 67 | screen = Image.open(BytesIO(screen)) 68 | return screen 69 | 70 | def get_image(self): 71 | """ 72 | 获取验证码图片 73 | :return: 74 | """ 75 | top, bottom, left, right = self.get_position() 76 | screen = self.get_screen() 77 | cap = screen.crop((left, top, right, bottom)) 78 | return cap 79 | 80 | def get_slider(self): 81 | """ 82 | 获取滑块对象 83 | :return: 84 | """ 85 | slider = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'geetest_slider_button'))) 86 | return slider 87 | 88 | def is_pixel_equal(self, image1, image2, x, y): 89 | """ 90 | 判断两个像素是否相同 91 | :param image1:不带缺口的图片(初始图片) 92 | :param image2:带缺口的图片(按了滑块的图片 93 | :param x:位置x 94 | :param y:位置y 95 | :return:像素是否相同 96 | """ 97 | pixel1 = image1.load()[x, y] 98 | pixel2 = image2.load()[x, y] 99 | num = 60 100 | if abs(pixel1[0] - pixel2[0]) < num and abs(pixel1[1] - pixel2[1]) < num and abs( 101 | pixel1[2] - pixel2[2]) < num: 102 | return 1 103 | else: 104 | return 0 105 | 106 | def get_gap(self, image1, image2): 107 | """ 108 | 获取缺口偏移量 109 | :param image1:不带缺口的图片(初始图片) 110 | :param image2:带缺口的图片(按了滑块的图片) 111 | :return:缺口偏移量 112 | """ 113 | left = 60 114 | for i in range(left, image1.size[0]): 115 | for j in range(image1.size[1]): 116 | if not self.is_pixel_equal(image1, image2, i, j): 117 | left = i 118 | return left 119 | return left 120 | 121 | def get_track(self, distance): 122 | """ 123 | 根据偏移量获取移动轨迹 124 | :param distance:偏移量 125 | :return:移动轨迹 126 | """ 127 | # 移动轨迹 128 | track = [] 129 | # 当前位移 130 | current = 0 131 | # 减速阈值 132 | mid = distance * 4 / 5 133 | # 计算间隔 134 | t = 0.2 135 | # 初速度 136 | v = 0 137 | 138 | while current < distance: 139 | if current < mid: 140 | # 加速度为正2 141 | a = 2 142 | else: 143 | # 加速度为负3 144 | a = -3 145 | # 初速度v0 146 | v0 = v 147 | # 当前速度v = v0 + at 148 | v = v0 + a * t 149 | # 移动距离x = v0t + 1/2 * a * t^2 150 | move = v0 * t + 1 / 2 * a * t * t 151 | # 当前位移 152 | current += move 153 | # 加入轨迹 154 | track.append(round(move)) 155 | return track 156 | 157 | def move_to_gap(self, slider, track): 158 | """ 159 | 拖动滑块到缺口处 160 | :param slider:滑块 161 | :param track:轨迹 162 | :return: 163 | """ 164 | ActionChains(self.bro).click_and_hold(slider).perform() 165 | for x in track: 166 | ActionChains(self.bro).move_by_offset(xoffset=x, yoffset=0).perform() 167 | time.sleep(0.5) 168 | ActionChains(self.bro).release().perform() 169 | 170 | def login(self): 171 | """ 172 | 登录 173 | :return: 174 | """ 175 | submit = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'login-btn'))) 176 | submit.click() 177 | time.sleep(3) 178 | print('登录成功') 179 | 180 | def crack(self): 181 | """ 182 | 登录的接口 183 | :return: 184 | """ 185 | self.get_url() # 打开webdriver,输入用户密码 186 | self.get_button() 187 | image1 = self.get_image() 188 | slider = self.get_slider() 189 | slider.click() 190 | image2 = self.get_image() 191 | gap = self.get_gap(image1, image2) 192 | gap -= BORDER 193 | track = self.get_track(gap) 194 | self.move_to_gap(slider, track) 195 | 196 | success = self.wait.until( 197 | EC.text_to_be_present_in_element((By.CLASS_NAME, 'geetest_success_radar_tip_content'), '验证成功')) 198 | return success 199 | 200 | def main(self): 201 | """ 202 | 程序入口 203 | :return: 204 | """ 205 | success = self.crack() 206 | if success: 207 | self.login() 208 | else: 209 | for i in range(5): 210 | success = self.crack() 211 | if success: 212 | break 213 | 214 | 215 | if __name__ == '__main__': 216 | jiyan_code = JiyanCode() 217 | jisu_code.main() 218 | --------------------------------------------------------------------------------