前几天看到有位大大写的破解极验证码,也就是二十滑块验证滑块验证。
本偏文章主要借鉴了其中的图片像素对比的方法,在原基础上尽心的修改创作。让本demo更适用于多个网站和一二代的滑块验证。
还有一个原因是原作者的demo我跑不通,逻辑出现了bug。
原作者地址:https://mp.weixin.qq.com/s/_SKphxxGg7Plgv9iG_LOkw
代码中我会详细的解释每一步的作用,欢迎借鉴。
里面的可借用的东西还是很多的。
更新: 2018-9-28
1.优化了移动轨迹
下面是代码demo:
from selenium import webdriver
from selenium.webdriver.support.wait import WebDriverWait #
from selenium.webdriver.support import expected_conditions as EC
import time
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
from bs4 import BeautifulSoup
import re
from PIL import Image, ImageChops
from io import BytesIO
import random
from bs4 import BeautifulSoup
class HuXiu(object):
def __init__(self):
#--------设置谷歌浏览器属性----------
chrome_option = webdriver.ChromeOptions()
self.driver = webdriver.Chrome(
executable_path=r"C:/Users/Administrator/AppData/Local/Google/Chrome/Application/chromedriver.exe",
chrome_options=chrome_option)
self.driver.set_window_size(1440, 900)
#--------设置谷歌浏览器属性----------
def visit_index(self):
#使用浏览器打开指定链接
self.driver.get("https://www.huxiu.com/")
#WebDriverWiat(
#driver:查看的页面
#timeOut:最大等待时间
#poll_frequency:调用until或until_not的频率,默认是0.5)
#EC就是expected_conditions,判断通过xpath能否获取到指定path下的按钮有没
#有出现,如果出现了,那么WebDriverWait就能接收到until传回的True,程序则会继续运行
#这个地方是判断页面有没有加载出注册按钮
WebDriverWait(self.driver, 10, 0.5).until(EC.element_to_be_clickable((By.XPATH, '//*[@class="js-register"]')))
#获取注册按钮的element
reg_element = self.driver.find_element_by_xpath('//*[@class="js-register"]')
#触发点击事件,弹出注册框
reg_element.click()
#这个与上面同理,等待注册框加载完成
WebDriverWait(self.driver, 10, 0.5).until(
EC.element_to_be_clickable((By.XPATH, '//div[@class="gt_slider_knob gt_show"]')))
# 进入模拟拖动流程
self.analog_drag()
def analog_drag(self):
# 获取拖动按钮的element
element = self.driver.find_element_by_xpath('//div[@class="gt_slider_knob gt_show"]')
#print('移动到滑块,获取完整图片')
#ActionChains()是selenium中关于底层自动交互的一个方法,可以根据提供的方法实现自动的点击、拖拽、移动等事件
ActionChains(self.driver).move_to_element(element).perform()
#防止网速出现异常的等待时间,让图片加载出来
time.sleep(3)
#----刷新验证图片-----
#这一步是在第一次验证失败后,会直接出现只有缺口的图片,无法进行完整图片的截图,因此进行了一次刷新动作
element_refresh = self.driver.find_element_by_xpath('//a[@class="gt_refresh_button"]')
element_refresh.click()
time.sleep(1)
#----刷新验证图片------
#完整图保存地址,最好设置成png格式,jpg设置会报错,原因下面会说明
fullImg_Path = 'I:/study_tz/venv/Slider_validation/full.png'
#缺口图片保存地址
cutImg_Path = 'I:/study_tz/venv/Slider_validation/cut.png'
#不相同,也就是说缺口位置保存地址
diff_path = 'I:/study_tz/venv/Slider_validation/dif.png'
#获取完整图片,调用get_img(自写)的方法
full = self.get_img(self.driver, 'class', 'gt_box', fullImg_Path)
#('点住不放,获取缺口图片')
ActionChains(self.driver).click_and_hold(element).perform()
#获取带缺口图片
cut = self.get_img(self.driver, 'class', 'gt_box', cutImg_Path)
# 根据两个图片计算距离,需要传入二进制数据
distance = self.get_offset_distance(cut, full)
#执行滑块移动
self.start_move(element, distance)
# 判断是否验证成功
#每个网站的判定方式不同,需要自行查找每次验证后的提示成功与否的地方
#在虎X中,有个div会根据结果显示success或者fail,通过BeautifulSoup来获取该元素,如果为None,说明能获取到元素,即成功
#该判断结构可以根据实际情况进行修改,模式不限。
soup = BeautifulSoup(self.driver.page_source,'lxml')
success = soup.find('div',{'class':'gt_ajax_tip gt_success'})
if success is None:
#这个部分是原作者的判断,他考虑到或许因为网速等原因,返回值的时候会有延迟,因此使用的事webDriverWait来获取的
try:
WebDriverWait(self.driver, 10, 0.5).until(
EC.element_to_be_clickable((By.XPATH, '//div[@class="gt_ajax_tip gt_success"]')))
except TimeoutException:
print("again times")
time.sleep(5)
# 失败后递归执行拖动
self.analog_drag()
else:
# 成功后输入手机号,发送验证码
self.register()
else:
time.sleep(3)
#失败后递归执行拖动
self.analog_drag()
# self.driver.close()
# 开始移动
def start_move(self, element, distance):
#这里加上52,理论上应该是+60,因为截图的时候把x+60了嘛
#但现在变成+52,是因为在实际中,滑块和缺口块的位置并不相同,存在一定的位置,所以需要使用的时候,调整下
distance += 52
# 按下鼠标左键
ActionChains(self.driver).click_and_hold(element).perform()
time.sleep(0.5)
track_list=get_track(distances+3)
time.sleep(2)
ActionChains(driver).click_and_hold(element).perform()
time.sleep(0.2)
# 根据轨迹拖拽圆球
for track in track_list:
ActionChains(self.driver).move_by_offset(xoffset=track,yoffset=0).perform()
# 模拟人工滑动超过缺口位置返回至缺口的情况,数据来源于人工滑动轨迹,同时还加入了随机数,都是为了更贴近人工滑动轨迹
imitate=ActionChains(driver).move_by_offset(xoffset=-1, yoffset=0)
time.sleep(0.015)
imitate.perform()
time.sleep(random.randint(6,10)/10)
imitate.perform()
time.sleep(0.04)
imitate.perform()
time.sleep(0.012)
imitate.perform()
time.sleep(0.019)
imitate.perform()
time.sleep(0.033)
ActionChains(self.driver).move_by_offset(xoffset=1, yoffset=0).perform()
# 放开圆球
ActionChains(self.driver).pause(random.randint(6,14)/10).release(element).perform()
# 判断颜色是否相近
def is_similar_color(self, x_pixel, y_pixel):
for i, pixel in enumerate(x_pixel):
#当返回True时,说明两点是相同的,返回False,说明找到了不同点,也就是找到了缺口元素所在的地方 50可根据情况做调整,主要是图片中会出现略微带点阴影的缺口值
if abs(y_pixel[i] - pixel) > 50:
return False
return True
#滑块移动轨迹
def get_track(distance):
track=[]
current=0
mid=distance*3/4
t=random.randint(2,3)/10
v=0
while current<distance:
if current<mid:
a=2
else:
a=-3
v0=v
v=v0+a*t
move=v0*t+1/2*a*t*t
current+=move
track.append(round(move))
return track
#计算缺口坐标,获取x,也就是说第一个符合阈值的点的横坐标,也就是距离边框的距离
#做两个for循环,分别获取两张图对应坐标的像素
def get_offset_distance(self, cut_image, full_image):
for x in range(cut_image.width):
for y in range(cut_image.height):
cpx = cut_image.getpixel((x, y))
fpx = full_image.getpixel((x, y))
#调用is_similar_color方法作对比
if not self.is_similar_color(cpx, fpx):
#这个截图只是为了看下是不是截取位置正确,可以舍弃的
img = cut_image.crop((x, y, x + 50, y + 40))
img.save("I:/study_tz/venv/Slider_validation/dif.png")
return x
#这一步就是通过定位元素的位置,然后获取该元素的大小,来计算截图位置,
def get_img(self, driver, element_type, element_path, img_path):
#获取想要截取截图的元素
element = driver.find_element_by_xpath('//div[@%s="%s"]' % (element_type, element_path))
#将整个页面转成二进制数据,临时存储在内存里
#这里要说下,在写这个方法的时候,selenium提供了一个screenshot的方法可以直接将某个元素变成截图,可是在使用Charm作为打开的浏览器,就会报错‘未知方法’。
#然后寄出然了百度,无果,都以一个问题。后在查阅外网的用户使用经验,有外国大佬总结了,这个方法好像仅仅支持火狐的浏览器,也就是说谷歌不行,因此就只能用笨办法,自己写了。
png = driver.get_screenshot_as_png()
im = Image.open(BytesIO(png))
#获取元素的大小,返回的是字典格式{‘width’:值,‘height’:值}
size = element.size
#获取元素的坐标,返回的是字典格式{‘w’:值,‘y’:值}
location = element.location
#这个地方+60,是因为截取缺口图的时候,缺口的那个滑块会被截取进去(那个滑块有阴影的,与缺口处阴影值相同),影响像素对比的结果,因此将这个部分去掉,可以自行调整
left = location['x'] + 60
top = location['y']
right = location['x'] + size['width']
bottom = location['y'] + size['height']
# print(left, top, right, bottom)
#使用crop方法,传入四个值,截图
im = im.crop((left, top, right, bottom))
im.save(img_path)
return im
#这一步是输入手机号注册,获取验证码的,很简单,就不详细叙述了。
def register(self):
element = self.driver.find_element_by_xpath('//input[@id="sms_username"]')
element.clear()
element.send_keys("你的手机号")
ele_captcha = self.driver.find_element_by_xpath('//span[@class="js-btn-captcha btn-captcha"]')
ele_captcha.click()
if __name__ == "__main__":
hx = HuXiu()
hx.visit_index()
下面是一些截图:
full_img:
cut_img:
dif_img:
验证图:
验证码:
我这个demo在该网站上,从写好后,正确率在99%。。没错过,很尴尬,可能在一些异常抛出错误上还有漏洞。如果你借鉴使用后出现这方面你的问题了。欢迎留言。
希望这篇文章对你有所帮助,咱们下次见。
ps:喜欢的点个赞呦