Python滑动验证码破解实战:基于Selenium与图像像素对比
本文详解利用Python和Selenium破解滑动验证码的完整流程。通过截取完整图与缺口图,对比像素差异计算滑动距离,并结合ActionChains模拟人类拖拽轨迹。文章提供国家企业公示网站的实战代码,涵盖驱动配置、重试机制及PhantomJS替代方案,帮助开发者掌握自动化突破图形验证的技术。
前言
在上一篇关于验证码分割的文章中,我们探讨了两种分割方法。此外还存在一种被称为“滴水算法”(Drop Fall Algorithm)的技术,但由于该算法逻辑较为晦涩,理解难度较大,因此本文暂不深入讨论。今天的重点转向滑动验证码的处理。尽管网络上已有大神声称能绕过Selenium直接破解此类验证码,但鉴于个人技术局限,本文将采用更为稳妥且通用的笨办法进行演示。
其核心原理十分直观:首先,点击验证码按钮后,系统会显示一张经过滑动处理后的完整背景图;接着,点击滑块会出现带有缺口的拼图。我们需要分别对这两张图进行截图,随后通过对比像素值的差异来精确计算滑块的移动距离,最后结合Selenium库模拟真实的拖拽动作即可完成验证。
关于Selenium的具体安装步骤此处略过。滑动验证码破解的关键难点在于模拟人类自然的拖拽行为。移动速度过快或过慢都极易触发风控机制,因此需要精心控制拖拽的加速度与轨迹。
以下以国家企业公示网站为例进行演示:
# -*- coding: utf-8 -*-
import time
import random
from io import BytesIO
from PIL import Image
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver import ActionChains
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
class 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"//a[@target='_blank']"):
print(o.get_attribute("href"))
self.driver.quit()
else:
print("识别失败")
self.driver.quit()
if __name__ == '__main__':
s = Slide('中国平安')
s.run()
代码中的注释已非常详尽,此处不再赘述。如果在运行过程中遇到提示错误,请检查驱动配置。
selenium.common.exceptions.WebDriverException: Message: 'chromedriver' executable needs to be in PATH. Please see https://sites.google.com/a/chromium.org/chromedriver/home
若出现此类报错,需前往 https://sites.google.com/a/chromium.org/chromedriver/home 下载对应版本的驱动,并将其解压至 /usr/local/bin 目录下。此外,若在使用服务器环境运行,可考虑使用 PhantomJS 替代 Chrome 浏览器。同时,建议在代码中加入失败自动重试的逻辑,以提升脚本的稳定性,感兴趣读者可自行完善相关功能。
总结
以上就是本文介绍的全部内容,希望这些内容能对各位的学习或工作提供有价值的参考。如有任何疑问,欢迎在评论区留言交流,感谢大家对本站的支持。
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













