爬虫入门经典(十九) | 难度提升,破解极验验证码

2020-11-24 15:08:34 浏览数 (1)

  大家好,我是不温卜火,是一名计算机学院大数据专业大三的学生,昵称来源于成语—不温不火,本意是希望自己性情温和。作为一名互联网行业的小白,博主写博客一方面是为了记录自己的学习过程,另一方面是总结自己所犯的错误希望能够帮助到很多和自己一样处于起步阶段的萌新。但由于水平有限,博客中难免会有一些错误出现,有纰漏之处恳请各位大佬不吝赐教!暂时只在csdn这一个平台进行更新,博客主页:https://buwenbuhuo.blog.csdn.net/。

PS:由于现在越来越多的人未经本人同意直接爬取博主本人文章,博主在此特别声明:未经本人允许,禁止转载!!!



推荐

  ♥各位如果想要交流的话,可以加下QQ交流群:974178910,里面有各种你想要的学习资料。♥

  ♥欢迎大家关注公众号【不温卜火】,关注公众号即可以提前阅读又可以获取各种干货哦,同时公众号每满1024及1024倍数则会抽奖赠送机械键盘一份 IT书籍1份哟~♥

上两篇文章中学长分别破解了文字验证码和简单的滑动验证码,接下来咱们升级破解,破解难度更大的滑动验证码,目前很多企业的用的第三方开发好的验证码,比如极验。

在此,博主给出极验的滑动验证网站进行测试。网址如下: https://captcha1.scrape.center/

我们打开这个网址点击登录,可以发现验证码要比豆瓣的验证码复杂多了。

一、验证码分析

滑动验证码可以通过分析js,这个难度过大。我们这里还是使用selenium模拟滑动解决。

需要解决两个问题: 第1是滑动的距离。 第2是模拟滑动。

模拟滑动已经可以解决,现在就需要解决滑动距离。

首先,我们先来获取完整图片,先找到验证码的位置,然后取消选取display和opacity即可得到完整的图片

或者这none改成block也可以显示完整图片

下面我们来对不一下这两张图片

其实除了这种方案之外,往上是有其他方案的,比如通过opencv找到缺口位置。

?既然这样,那么接下来思路就很清晰了

  • (1)访问网页,保存全屏为图片。
  • (2)截图验证码
  • (3)通过js修改css样式,显示完整图
  • (4)截图验证码
  • (5)对比找到缺口位置来判断出位移
  • (6)移动

注意:截图验证码的时候需要获取位置,这个时候需要本地屏幕设置100%和浏览器设置100%不缩放。

二、代码的逐步实现

实现的集体过程

  • (1)访问网页,保存全屏为图片。
  • (2)截图验证码
  • (3)通过js修改css样式,显示完整图
  • (4)截图验证码
  • (5)对比找到缺口位置来判断出位移
  • (6)移动

2.1 访问网页,使其能够出现验证码

  • 1. 代码如下
代码语言:javascript复制
from selenium import webdriver
from PIL import Image
import time

url = "https://captcha1.scrape.center/"
driver = webdriver.Chrome("./chromedriver/chromedriver.exe")
driver.maximize_window()
driver.get(url)

driver.find_elements_by_class_name("el-input__inner")[0].send_keys("老王")
driver.find_elements_by_class_name("el-input__inner")[1].send_keys("123456")
driver.find_element_by_class_name("el-button").click()

time.sleep(5)
driver.quit()
  • 2. 效果如下

2.2 截取验证码

1. 分析

  • 1. 确定图片位置
  • 2. 截取验证码

我们的截图,其实就是确定左上和右下的位置

2. 代码实现

  • 1. 先截取整个屏幕
代码语言:javascript复制
#保存屏幕
driver.save_screenshot("./screen1.png")
time.sleep(2)
  • 2. 截取验证码
代码语言:javascript复制
#获取验证码的定位
img = driver.find_element_by_class_name("geetest_canvas_img")
#print(img1.location,img1.size)#{'x': 590, 'y': 239} {'height': 160, 'width': 260}
#坐标(左上角,右下角)
rectangle = (img.location["x"],img.location["y"],img.location["x"] img.size["width"],img.location["y"] img.size["height"])
print(rectangle)
#打开图片
img_obj = Image.open("./screen1.png")
#截图
img_new = img_obj.crop(rectangle)
#保存
img_new.save("./img1.png")

driver.quit()

3. 最终结果(截图)

2.3 截取完整图片

1. 执行js,显示完整图片

代码如下

代码语言:javascript复制
document.getElementsByClassName("geetest_canvas_fullbg")[0].style = "display:block"

效果图如下

2. 代码实现

代码语言:javascript复制
#执行js,显示完整图片
driver.execute_script('document.getElementsByClassName("geetest_canvas_fullbg")[0].style = "display:block"')
time.sleep(2)

#保存屏幕
driver.save_screenshot("./screen2.png")
time.sleep(2)

#截图
img_obj = Image.open("./screen2.png")
img_new = img_obj.crop(rectangle)
img_new.save("./img2.png")

2.4 验证码缺口和完整截图

此部分,为上述源码的整合

代码语言:javascript复制
from selenium import webdriver
from PIL import Image
import time

url = "https://captcha1.scrape.center/"
driver = webdriver.Chrome("./chromedriver/chromedriver.exe")
driver.maximize_window()
driver.get(url)

driver.find_elements_by_class_name("el-input__inner")[0].send_keys("老王")
driver.find_elements_by_class_name("el-input__inner")[1].send_keys("123456")
driver.find_element_by_class_name("el-button").click()

time.sleep(5)

#保存屏幕
driver.save_screenshot("./screen1.png")

#获取验证码的定位
img = driver.find_element_by_class_name("geetest_canvas_img")
#print(img1.location,img1.size)#{'x': 590, 'y': 239} {'height': 160, 'width': 260}
#坐标(左上角,右下角)
rectangle = (img.location["x"],img.location["y"],img.location["x"] img.size["width"],img.location["y"] img.size["height"])
print(rectangle)
#打开图片
img_obj = Image.open("./screen1.png")
#截图
img_new = img_obj.crop(rectangle)
#保存
img_new.save("./img1.png")


#执行js,显示完整图片
driver.execute_script('document.getElementsByClassName("geetest_canvas_fullbg")[0].style = "display:block"')
time.sleep(2)

#保存屏幕
driver.save_screenshot("./screen2.png")
time.sleep(2)

#截图
img_obj = Image.open("./screen2.png")
img_new = img_obj.crop(rectangle)
img_new.save("./img2.png")

driver.quit()

2.5 对比缺口验证

接下来,对比两张图的不同,从而找到缺口的位置,就是我们要移动的位置。

这里发现滑块的定位是固定的,距左面大概55左右。这两个图在截图的时候应该分别向右移动55像素,比对找到缺口后再加上原来的55就是要移动的位移。

分析完毕,下面我们只需修改下列一行代码

代码语言:javascript复制
#坐标(左上角,右下角)
rectangle = (img.location["x"] 55,img.location["y"],img.location["x"] img.size["width"],img.location["y"] img.size["height"])

接着要对比像素,找到二者不同的像素,这里需要注意,极验图上还加入了阴影,增加了对比难度。

经过多张图的对比,发现差值临界点30左右可以既去掉灰色阴影的干扰块又能识别出缺口位置 通过代码,对比像素的不同

代码语言:javascript复制
#对比像素的不同

from PIL import Image
img1 = Image.open("./img1.png")
img2 = Image.open("./img2.png")

for i in range(img1.width):
    for j in range(img1.height):
        if img1.getpixel((i,j))!=img2.getpixel((i,j)):
            print(i,j,img1.getpixel((i,j)),img2.getpixel((i,j)))

以上面的图为例,x就是31,加上原来的55,就是86,所以确定了滑块的位移就是86左右。

2.6 滑动滑块(此部分以后有时间会修复,在此只给出代码)

此部分直接使用上一篇博文的匀加速匀减速代码

代码语言:javascript复制
def get_tracks(distance, rate=0.5, t=0.2, v=0):
    """
    将distance分割成小段的距离
    :param distance: 总距离
    :param rate: 加速减速的临界比例
    :param a1: 加速度
    :param a2: 减速度
    :param t: 单位时间
    :param t: 初始速度
    :return: 小段的距离集合
    """
    tracks = []
    # 加速减速的临界值
    mid = rate * distance
    # 当前位移
    s = 0
    # 循环
    while s < distance:
        # 初始速度
        v0 = v
        if s < mid:
            a = 3
        else:
            a = -2
        # 计算当前t时间段走的距离
        s0 = v0 * t   0.5 * a * t * t
        # 计算当前速度
        v = v0   a * t
        # 四舍五入距离,因为像素没有小数
        tracks.append(round(s0))
        # 计算当前距离
        s  = s0

    return tracks

接下来就完成滑动,发现如下图所示,每次滑动都会超过大概5个像素左右,圆形的滑块与上面方形的滑块有像素偏差,需要在滑动距离上减去这个5个像素就可以解决。

三、完整代码

代码语言:javascript复制
# encoding: utf-8
'''
  @author 李华鑫
  @create 2020-10-11 16:03
  Mycsdn:https://buwenbuhuo.blog.csdn.net/
  @contact: 459804692@qq.com
  @software: Pycharm
  @file: 验证码升级2.py
  @Version:1.0
  
'''
"""
大概的思路就出来了:
(1)访问网页,保存全屏为图片。
(2)截图验证码
(3)通过js修改css样式,显示完整图
(4)截图验证码
(5)对比找到缺口位置来判断出位移
(6)移动
"""


from selenium import webdriver
from selenium.webdriver.common.action_chains import ActionChains
from PIL import Image
import time



#网址
url = "https://captcha1.scrape.center/"
#截屏的图片路径
screen1 = "./screen1.png"
screen2 = "./screen2.png"
#滑动验证的图片路径
img1 = "./img1.png"
img2 = "./img2.png"
#截图的偏移量
distance_offset = 55
distance_offset2 = 5
#位移
distance = 0

def get_tracks(distance, rate=0.5, t=0.2, v=0):
    """
    将distance分割成小段的距离
    :param distance: 总距离
    :param rate: 加速减速的临界比例
    :param a1: 加速度
    :param a2: 减速度
    :param t: 单位时间
    :param t: 初始速度
    :return: 小段的距离集合
    """
    tracks = []
    # 加速减速的临界值
    mid = rate * distance
    # 当前位移
    s = 0
    # 循环
    while s < distance:
        # 初始速度
        v0 = v
        if s < mid:
            a = 3
        else:
            a = -2
        # 计算当前t时间段走的距离
        s0 = v0 * t   0.5 * a * t * t
        # 计算当前速度
        v = v0   a * t
        # 四舍五入距离,因为像素没有小数
        tracks.append(round(s0))
        # 计算当前距离
        s  = s0

    return tracks

def save_img(driver):
    """保存图片"""

    time.sleep(2)
    #保存屏幕
    driver.save_screenshot(screen1)
    #获取验证码的定位
    img = driver.find_element_by_class_name("geetest_canvas_img")
    #截图的区域
    rectangle = (img.location["x"] distance_offset,img.location["y"],img.location["x"] img.size["width"],img.location["y"] img.size["height"])
    #打开图片
    img_obj = Image.open(screen1)
    #截图
    img_new = img_obj.crop(rectangle)
    #保存
    img_new.save(img1)


    #执行js,显示完整图片
    driver.execute_script('document.getElementsByClassName("geetest_canvas_fullbg")[0].style = "display:block"')
    time.sleep(2)
    #保存屏幕
    driver.save_screenshot(screen2)
    #执行js,隐藏完整图片
    driver.execute_script('document.getElementsByClassName("geetest_canvas_fullbg")[0].style = "display:none"')
    time.sleep(2)

    #截图
    img_obj = Image.open(screen2)
    img_new = img_obj.crop(rectangle)
    img_new.save(img2)

def discern_distance():
    """识别距离"""
    global distance

    #打开图像,得到图像对象
    img1_obj = Image.open(img1)
    img2_obj = Image.open(img2)
    #循环获取像素点
    for i in range(img1_obj.width):
        for j in range(img1_obj.height):
            #获取像素点
            p1 = img1_obj.getpixel((i, j))
            p2= img2_obj.getpixel((i, j))
            #比较
            if abs(p1[0]-p2[0])>30 and  abs(p1[1]-p2[1])>30 and abs(p1[2]-p2[2])>30:
                #计算位移
                distance = distance_offset i-distance_offset2
                return

def slide(driver):
    """滑动"""
    # 找到滑块
    block = driver.find_element_by_class_name('geetest_slider_button')
    # 循环
    while True:
        print("distance:",distance)
        # 摁下滑块
        ActionChains(driver).click_and_hold(block).perform()
        # 移动
        ActionChains(driver).move_by_offset(distance*0.8, 0).perform()
        # 获取位移
        tracks = get_tracks(distance*0.2)
        # 循环
        for track in tracks:
            # 移动
            ActionChains(driver).move_by_offset(track, 0).perform()
        # 释放
        ActionChains(driver).release().perform()
        # 停一下
        time.sleep(5)
        # 判断
        if driver.current_url == url:
            print("失败...再来一次...")
            # 单击刷新按钮刷新
            driver.execute_script('document.getElementsByClassName("geetest_refresh_1")[0].click()')
            # 停一下
            time.sleep(2)
            #截图
            save_img(driver)
            #识别
            discern_distance()
        else:
            print("成功")
            break


def main():
    """主程序"""

    #开始访问网址
    driver = webdriver.Chrome("./chromedriver/chromedriver.exe")
    driver.maximize_window()
    driver.get(url)

    #找到用户名、密码、单击登录   弹出极验
    driver.find_elements_by_class_name("el-input__inner")[0].send_keys("admin")
    driver.find_elements_by_class_name("el-input__inner")[1].send_keys("admin")
    driver.find_element_by_class_name("el-button").click()

    time.sleep(5)

    #保存图片
    save_img(driver)
    #识别位移
    discern_distance()
    #滑动
    slide(driver)
    #退出
    driver.quit()


if __name__ == '__main__':
    main()

四、最终运行结果

美好的日子总是短暂的,虽然还想继续与大家畅谈,但是本篇博文到此已经结束了,如果还嫌不够过瘾,不用担心,我们下篇见!


  好书不厌读百回,熟读课思子自知。而我想要成为全场最靓的仔,就必须坚持通过学习来获取更多知识,用知识改变命运,用博客见证成长,用行动证明我在努力。   如果我的博客对你有帮助、如果你喜欢我的博客内容,请“点赞” “评论”“收藏”一键三连哦!听说点赞的人运气不会太差,每一天都会元气满满呦!如果实在要白嫖的话,那祝你开心每一天,欢迎常来我博客看看。   码字不易,大家的支持就是我坚持下去的动力。点赞后不要忘了关注我哦!

0 人点赞