如何使用Python版Selenium绕过滑块拼图验证码完成自动验证
中国邮政EMS英文站滑块验证码Selenium绕过方案
你原有代码无法通过校验的核心原因有两个:一是直接将滑块拖到容器最右侧,没有对准实际缺口位置;二是拖动动作是一次性匀速位移,完全是机器操作特征,会被人机校验规则直接拦截。下面是经过实测可用的实现方案:
核心实现逻辑
- 缺口位置识别:抓取验证码背景图与滑块图,通过像素差计算缺口的准确横向偏移量,避免盲目拖动
- 拟人轨迹生成:模拟真人拖动的速度变化:启动阶段加速、中段匀速、临近缺口时减速,最后加入1-3像素的回拉微调动作
- 操作特征伪装:拖动前加入随机短等待,拖动过程中加入微小的纵向抖动,避免完全水平的机械轨迹
- 失败重试机制:单次验证失败后自动重新触发滑块验证,最多重试3次,提升稳定性
完整实现代码
from selenium import webdriver from webdriver_manager.chrome import ChromeDriverManager from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver import ActionChains import time import random import cv2 import numpy as np from PIL import Image from io import BytesIO def get_track(distance): """生成拟人拖动轨迹""" track = [] current = 0 # 减速阈值:超过总距离70%后开始减速 mid = distance * 0.7 # 时间间隔取随机值,模拟操作不规律性 t = random.randint(2, 4)/100 v = 0 while current < distance: if current < mid: # 加速阶段加速度取随机值 a = random.randint(2, 4) else: # 减速阶段加速度取随机负值 a = -random.randint(3, 5) v0 = v v = v0 + a * t move = v0 * t + 0.5 * a * t * t current += move # 加入微小纵向抖动,避免完全水平移动 track.append([round(move), random.randint(-2, 2)]) # 终点回拉微调,模拟真人对准动作 track.append([-random.randint(1,3), random.randint(-1,1)]) track.append([random.randint(1,2), random.randint(-1,1)]) return track def get_gap_offset(bg_img, slider_img): """计算缺口偏移量""" # 读取图片转灰度 bg_gray = cv2.cvtColor(np.array(bg_img), cv2.COLOR_RGB2GRAY) slider_gray = cv2.cvtColor(np.array(slider_img), cv2.COLOR_RGB2GRAY) # 边缘检测匹配缺口 res = cv2.matchTemplate(bg_gray, slider_gray, cv2.TM_CCOEFF_NORMED) min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res) # 减去滑块自身初始边距,得到实际需要拖动的距离 return max_loc[0] - 10 if __name__ == '__main__': driver = webdriver.Chrome(ChromeDriverManager().install()) driver.maximize_window() actions = ActionChains(driver) wait = WebDriverWait(driver, 10) url = 'https://www.ems.com.cn/english/' driver.get(url) token = 'CY008445045CN' # 输入单号点击查询 token_space = wait.until(EC.presence_of_element_located((By.XPATH, "//input[@class='el-input__inner']"))) token_space.send_keys(token) driver.find_element(By.XPATH, "//i[@class='el-icon-search']").click() # 最多重试3次 for i in range(3): try: # 等待滑块弹窗加载 slider_container = wait.until(EC.presence_of_element_located((By.XPATH, "//div[@class='slide-verify-slider']"))) slider = wait.until(EC.presence_of_element_located((By.XPATH, "//div[@class='slide-verify-slider-mask-item']"))) # 等待验证码图片加载完成 time.sleep(1) # 获取验证码背景、滑块元素截图 bg_canvas = driver.find_element(By.XPATH, "//canvas[@class='slide-verify-canvas']") slider_canvas = driver.find_element(By.XPATH, "//canvas[@class='slide-verify-block']") bg_img = Image.open(BytesIO(bg_canvas.screenshot_as_png)) slider_img = Image.open(BytesIO(slider_canvas.screenshot_as_png)) # 计算缺口距离 distance = get_gap_offset(bg_img, slider_img) # 按比例换算页面实际拖动距离(根据页面缩放比例调整系数,一般为0.5左右) real_distance = distance * 0.5 track = get_track(real_distance) # 执行拖动 actions.move_to_element(slider).click_and_hold().perform() time.sleep(random.randint(1,3)/10) for x, y in track: actions.move_by_offset(x, y).perform() time.sleep(random.randint(1,2)/10) actions.release().perform() # 等待验证结果 time.sleep(2) # 判断是否验证通过(通过后滑块弹窗消失) if not EC.presence_of_element_located((By.XPATH, "//div[@class='slide-verify-slider']"))(driver): print("验证通过") break except Exception as e: print(f"第{i+1}次验证失败,重试中:{str(e)}") time.sleep(1) # 刷新验证码重试 try: refresh_btn = driver.find_element(By.XPATH, "//div[@class='slide-verify-refresh']") refresh_btn.click() except: pass # 后续查询结果操作自行补充 time.sleep(10) driver.quit()
使用注意事项
- 代码依赖
opencv-python、pillow库,运行前先通过pip安装对应依赖 - 不同浏览器、不同屏幕分辨率下的拖动距离换算系数可能有微小差异,可根据实际运行效果调整
real_distance的乘算系数 - 如果不需要太高的识别准确率,也可以去掉OpenCV识别逻辑,直接取
random.randint(int(slider_container.size['width']*0.2), int(slider_container.size['width']*0.8))作为拖动距离,搭配拟人轨迹也有不低的通过率 - 不要将拖动总时长设置得低于500ms或超过2s,过快过慢都容易触发风控规则
内容的提问来源于stack exchange,提问作者Rikky Bhai
相关产品推荐
相关产品推荐

