python爬虫学习:验证码之滑动验证码

前面两个文章提到了普通图片的验证码识别,且尤其对于机器学习的识别方式精度相对会比较高。但是,现在开始流行滑动验证码,所以这里作者提及一点简单的滑动验证码识别技巧。

打开火狐浏览器,按下 F12 ,输入 url 为 http://www.gsxt.gov.cn/index.html ,可以打开 国家企业信用信息公示系统 ,输入关键词 中国联通 ,点击搜索会弹出一个滑动验证码出来,本文就是主要识别这个网址的滑动验证码。

识别这样的滑动验证码主要运用 selenium 库,判断图片中需要将按钮滑动到正确位置的距离。首先需要打开浏览器,设置最大的加载时间为 90 秒,如果超过 90 秒那么久直接调用 js 去停止加载,最后判断网页是否加载完毕,如果没有,则重新加载:

from selenium import webdriver
# 打开浏览器
'''
遇到python不懂的问题,可以加Python学习交流群:1004391443一起学习交流,群文件还有零基础入门的学习资料
'''
def openbrowser(url):
    global browser

    # 声明谷歌浏览器
    browser = webdriver.Chrome()
    # 限制加载时间不能超过90秒
    browser.set_page_load_timeout(90)

    try:
        # 输入网址
        browser.get(url)
        if "400 Bad request" in browser.page_source:
            browser.get(url)
    except:
        # 超过90秒强制停止加载
        browser.execute_script('window.stop()')
        if "400 Bad request" in browser.page_source:
            browser.get(url)
        else:
            pass
    time.sleep(5)

利用 F12 可以知道输入框的 id=keyword ,查询框的 id=btn_query :

# 输入关键字并查询
def inputKeyword(keyword):
    # 清空输入框
    browser.find_element_by_id("keyword").clear()
    # 输入查找的关键字
    browser.find_element_by_id("keyword").send_keys(keyword)
    time.sleep(5)
    # 点击查询按钮
    browser.find_element_by_id("btn_query").click()
    time.sleep(5)

而 selenium 可以控制鼠标操作,调用库即可。输入中国联通进行查询,出现滑动验证码识别,由此可以知道横拉滑块的 classname=gt_slider_knob ,设置一个函数,输入拖动的横距离和纵距离:

from selenium.webdriver.common.action_chains import ActionChains
# 拖放滑块
def drag_and_drop(x_offset, y_offset):
    # 找到滑块并滑动
    source = browser.find_element_by_class_name("gt_slider_knob")
    # 调用鼠标操作并且拖动
    ActionChains(browser).drag_and_drop_by_offset(source, x_offset, y_offset).perform()
    time.sleep(8)

下面这里是重点!识别滑动验证码的核心是,判断原图和验证码图片 RGB 值的变化,假设原来验证码的图片是:

然后用鼠标轻轻拉动下方的滑块,就会出现滑动验证码的图片:

一般我们都知道每张图片都是用很多像素点构成的:

每一个像素点都会有一个 RGB 值,这个值代表的是改像素点图片的颜色,而这里生成的验证码的图片很明显是有一个凹下去的区域,这个区域的颜色和原图不一样。所以我们主要是判断:

  1. 在什么地方原图和验证码图片的 RGB 值相差很大
  2. 相差很大的这个点的横纵坐标是多少,横坐标即为需要进行滑动的距离

在浏览器中找到验证码图片的位置,即为 classname=gt_box

获取这个图片的 left 、 top 、 right 、 bottom ,这里需要解释一下,top 和 right 相当于一张图片左上角的坐标,right 是距离左上角这个点向右的宽度,bottom 是距离左上角这个点向下的宽度:

定位到这张图片的位置,截取图片保存到内存中:

import io
'''
遇到python不懂的问题,可以加Python学习交流群:1004391443一起学习交流,群文件还有零基础入门的学习资料
'''
# 截取验证码图片
def screenShotImage():
    try:
        # 定位到验证码的框
        captchaElement = browser.find_element_by_class_name("gt_box")
    except:
        inputKeyword(keyword)
        # 定位到验证码的框
        captchaElement = browser.find_element_by_class_name("gt_box")
    locations = captchaElement.location
    sizes = captchaElement.size

    left = int(locations["x"])
    top = int(locations["y"])
    right = left + int(sizes["width"])
    bottom = top + int(sizes['height'])

    # 截图
    screenshot = browser.get_screenshot_as_png()
    screenshot = Image.open(io.BytesIO(screenshot))
    png = screenshot.crop((left, top, right, bottom))
    png.save(str(int(time.time())) + ".png")
    time.sleep(1)
    return png

在对原图和验证码图片进行对比的时候,要排除掉拼图的干扰,拼图也就是这张图片,它和原图也是不一样的:

如何排除这张图片的干扰呢?本文首先找到这张图片的宽度:

# 获取滑块的图片,是为了得到滑块的宽度
def getWidth():
    # 找到滑块并滑动
    slider = browser.find_element_by_class_name("gt_slice")
    sizes = slider.size
    width = sizes["width"]
    return width

由于该滑块总是出现在左边,所以在对比 RBG 值的时候跳过这个宽度。

将原图的每个像素点和验证码的每个像素点做差,如果差值都小于60则认为这个像素点出现 明显差异 ,即认定为验证码凹陷位置的起点。由于观察验证码图片的时候发现,滑块左边其实是有5个位移是空白的,所以最后的位移值要减掉5。而如果没有找到正确的偏移值,则返回一个假的偏移值55,防止程序中断:

# 获取滑动偏移量
def getOffset():
    img1 = screenShotImage()
    drag_and_drop(x_offset=5, y_offset=0)
    img2 = screenShotImage()
    w1, h1 = img1.size
    w2, h2 = img2.size
    print("wh")
    print(w1, h1, w2, h2)

    if w1 != w2 or h1 != h2:
        return False

    slider = getWidth()
    print("开始循环", w3, h3)
    for x in range(slider, w3):
        print(x)
        for y in range(0, h3):
            pix1 = img1.getpixel((x, y))
            pix2 = img2.getpixel((x, y))
            # 如果相差超过70则就认为找到了缺口的位置
            if abs(pix1[0]-pix2[0]) < 60 and abs(pix1[1]-pix2[1]) < 60 and abs(pix1[2]-pix2[2]) < 60:
				# 滑块左边其实是有5个位移是空白的,所以要减掉
                return x-5
	# 如果没有找到正确的偏移值,则返回一个假的偏移值,防止程序中断
    return 55

但是不管临界值选取60还是其他值效果都非常差,所以可以利用图片库比较两张图片有什么不同:

# 判断两张图片不同的地方
img3 = ImageChops.difference(img1, img2)
img3.save(str(int(time.time())) + ".png")

可以判断这张图片如果底色不是黑色,即 RBG(0,0,0) ,且像素颜色鲜艳,颜色鲜艳即 RBG 的某个值大于70,那么就认为找到了凹陷地方的边界,更改代码增加挑选条件:

# 获取滑动偏移量
def getOffset():
    img1 = screenShotImage()
    drag_and_drop(x_offset=5, y_offset=0)
    img2 = screenShotImage()
    # 判断两张图片不同的地方
    img3 = ImageChops.difference(img1, img2)
    img3.save(str(int(time.time())) + ".png")
    w1, h1 = img1.size
    w2, h2 = img2.size
    w3, h3 = img3.size
    print("wh")
    print(w1, h1, w2, h2)

    if w1 != w2 or h1 != h2:
        return False

    slider = getWidth()
    print("开始循环", w3, h3)
    for x in range(slider, w3):
        print(x)
        for y in range(0, h3):
            pix1 = img1.getpixel((x, y))
            pix2 = img2.getpixel((x, y))
            pix3 = img3.getpixel((x, y))
            print(pix3[0],pix3[1],pix3[2])
            # 如果相差超过70则就认为找到了缺口的位置
            if pix3[0] > 70 or pix3[1] > 70 or pix3[2] > 70 and abs(pix1[0]-pix2[0]) < 60 and abs(pix1[1]-pix2[1]) < 60 and abs(pix1[2]-pix2[2]) < 60:
                # 滑块左边其实是有5个位移是空白的,所以要减掉
                return x-5
    # 如果没有找到正确的偏移值,则返回一个假的偏移值,防止程序中断
    return 55

最后代入运行能准确的找到滑块验证码的位置:

猜你喜欢

转载自blog.csdn.net/qq_40925239/article/details/90698098