相关文章推荐
逃课的登山鞋  ·  hive sql 数组拆分-掘金·  2 年前    · 
谦逊的毛豆  ·  错误代码 1045 Access ...·  2 年前    · 
酷酷的金鱼  ·  python opencv ...·  2 年前    · 
寂寞的紫菜汤  ·  hooks中ant design ...·  2 年前    · 

上篇我们借助 tesserocr 库解决了图片码的识别验证,但在实际生活中,用得更多的是滑动验证,这篇就来解决滑动验证的问题

大部分网站都采用的是滑动验证,就是拖动滑块拼接图片,其中又大都采用 极验 http://www.geetest.com/ )所提供的技术,官方网页如下

本篇案例选用 哔哩哔哩动画 验证登录( https://passport.bilibili.com/login

chromedriver: 浏览器驱动,可以理解为一个没有界面的chrome浏览器

selenium: 用于模拟人对浏览器进行点击、输出、拖拽等操作,就相当于是个人在使用浏览器,也常常用来应付反爬虫措施,配合chromedriver使用,使用方法直接粘大神写的: https://cuiqingcai.com/5630.html

Image模块 :提供很多对图片进行处理的方法的库,用法请查看 https://www.cnblogs.com/kongzhagen/p/6295925.html

1. 获取验证图片

通过访问登录页面,分析源码找到完整图片和带滑块缺口的图片 ,通过 selenium 键入登录信息

2. 获取缺口位置

通过对比原始的图片和带滑块缺口的图片的像素,计算出滑块缺口的位置,得到所需要滑动的距离

3.模拟拖动

利用selenium进行对滑块的拖拽,注意模仿人的行为:先快后慢,有个对准过程

第一大步:获取验证图片

1.初始化一些需要用到的参数

from selenium import webdriver
from selenium.webdriver.support.wait import WebDriverWait
# 初始化
def init():
    # 定义为全局变量,方便其他模块使用
    global url, browser, username, password, wait
    # 登录界面的url
    url = 'https://passport.bilibili.com/login'
    # 实例化一个chrome浏览器
    browser = webdriver.Chrome()
    # 用户名
    username = '***********'
    password = '***********'
    # 设置等待超时
    wait = WebDriverWait(browser, 20)

2.通过 selenium 键入登录信息

(by the way:之前用 post 提交表单做过模拟登录,其实用selenium模拟人键入登录信息和点击提交也可以成功登录,但问题是selenium相率太低,所以一般能不用selenium就不用)

from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
def login():
    # 打开登录页面
    browser.get(url)
    # 获取用户名输入框
    user = wait.until(EC.presence_of_element_located((By.ID, 'login-username')))
    # 获取密码输入框
    passwd = wait.until(EC.presence_of_element_located((By.ID, 'login-passwd')))
    # 输入用户名
    user.send_keys(username)
    # 输入密码
    passwd.send_keys(password)

3.获取验证图片

通过分析源码找到原始完整图片和带滑块缺口的图片

from urllib.request import urlretrieve
from bs4 import BeautifulSoup
import re
from PIL import Image
# 获取图片信息
def get_image_info(img):
    :param img: (Str)想要获取的图片类型:带缺口、原始
    :return: 该图片(Image)、位置信息(List)
    # 将网页源码转化为能被解析的lxml格式
    soup = BeautifulSoup(browser.page_source, 'lxml')
    # 获取验证图片的所有组成片标签
    imgs = soup.find_all('div', {'class': 'gt_cut_'+img+'_slice'})
    # 用正则提取缺口的小图片的url,并替换后缀
    img_url = re.findall('url\(\"(.*)\"\);', imgs[0].get('style'))[0].replace('webp', 'jpg')
    # 使用urlretrieve()方法根据url下载缺口图片对象
    urlretrieve(url=img_url, filename=img+'.jpg')
    # 生成缺口图片对象
    image = Image.open(img+'.jpg')
    # 获取组成他们的小图片的位置信息
    position = get_position(imgs)
    # 返回图片对象及其位置信息
    return image, position

但是这里有一个问题,验证图片是由两行许多的小图片组成的,而这些小图片不是按顺序排列的,这样就无法计算滑块缺口的位置了

因此我们上面的代码在有一个获取每个小图片正确位置信息的方法 get_position()

其代码如下:

# 获取小图片位置
def get_position(img):
    :param img: (List)存放多个小图片的标签
    :return: (List)每个小图片的位置信息
    img_position = []
    for small_img in img:
        position = {}
        # 获取每个小图片的横坐标
        position['x'] = int(re.findall('background-position: (.*)px (.*)px;', small_img.get('style'))[0][0])
        # 获取每个小图片的纵坐标
        position['y'] = int(re.findall('background-position: (.*)px (.*)px;', small_img.get('style'))[0][1])
        img_position.append(position)
    return img_position

4.把这些小图片裁剪下来以方便重新拼成顺序正确的图片

from PIL import Image
# 裁剪图片
def Corp(image, position):
    :param image:(Image)被裁剪的图片
    :param position: (List)该图片的位置信息
    :return: (List)存放裁剪后的每个图片信息
    # 第一行图片信息
    first_line_img = []
    # 第二行图片信息
    second_line_img = []
    for pos in position:
        if pos['y'] == -58:
            first_line_img.append(image.crop((abs(pos['x']), 58, abs(pos['x']) + 10, 116)))
        if pos['y'] == 0:
            second_line_img.append(image.crop((abs(pos['x']), 0, abs(pos['x']) + 10, 58)))
    return first_line_img, second_line_img
 

5.拼接处正确图片

按两行逐次拼接,使用 paste() 方法按照位置信息得到正确图片

# 拼接大图
def put_imgs_together(first_line_img, second_line_img, img_name):
    :param first_line_img: (List)第一行图片位置信息
    :param second_line_img: (List)第二行图片信息
    :return: (Image)拼接后的正确顺序的图片
    # 新建一个图片,new()第一个参数是颜色模式,第二个是图片尺寸
    image = Image.new('RGB', (260,116))
    # 初始化偏移量为0
    offset = 0
    # 拼接第一行
    for img in first_line_img:
        # past()方法进行粘贴,第一个参数是被粘对象,第二个是粘贴位置
        image.paste(img, (offset, 0))
        # 偏移量对应增加移动到下一个图片位置,size[0]表示图片宽度
        offset += img.size[0]
    # 偏移量重置为0
    x_offset = 0
    # 拼接第二行
    for img in second_line_img:
        # past()方法进行粘贴,第一个参数是被粘对象,第二个是粘贴位置
        image.paste(img, (x_offset, 58))
        # 偏移量对应增加移动到下一个图片位置,size[0]表示图片宽度
        x_offset += img.size[0]
    # 保存图片
    image.save(img_name)
    # 返回图片对象
    return image

此时,我们得到的就是位置正确的图片了

以上,第一大步 获取验证图片 就算是完成了

第二大步:获取缺口位置

1.计算缺口位置(即滑块需要滑动的距离)

# 计算滑块移动距离
def get_distance(bg_image, fullbg_image):
    :param bg_image: (Image)缺口图片
    :param fullbg_image: (Image)完整图片
    :return: (Int)缺口离滑块的距离
    # 滑块的初始位置
    distance = 57
    # 遍历像素点横坐标
    for i in range(distance, fullbg_image.size[0]):
        # 遍历像素点纵坐标
        for j in range(fullbg_image.size[1]):
            # 如果不是相同像素
            if not is_pixel_equal(fullbg_image, bg_image, i, j):
                # 返回此时横轴坐标就是滑块需要移动的距离
                return i

这其中有个is_pixel_equal()方法用于判断是否为相同像素,从而判断是不是缺口位置

其代码如下:

# 判断像素是否相同
def is_pixel_equal(bg_image, fullbg_image, x, y):
    :param bg_image: (Image)缺口图片
    :param fullbg_image: (Image)完整图片
    :param x: (Int)位置x
    :param y: (Int)位置y
    :return: (Boolean)像素是否相同
    # 获取缺口图片的像素点(按照RGB格式)
    bg_pixel = bg_image.load()[x, y]
    # 获取完整图片的像素点(按照RGB格式)
    fullbg_pixel = fullbg_image.load()[x, y]
    # 设置一个判定值,像素值之差超过判定值则认为该像素不相同
    threshold = 60
    # 判断像素的各个颜色之差,abs()用于取绝对值
    if (abs(bg_pixel[0] - fullbg_pixel[0] < threshold) and abs(bg_pixel[1] - fullbg_pixel[1] < threshold) and abs(bg_pixel[2] - fullbg_pixel[2] < threshold)):
        # 如果差值在判断值之内,返回是相同像素
        return True
    else:
        # 如果差值在判断值之外,返回不是相同像素
        return False

第三大步:模拟拖动

1.构造模拟人类的滑块移动轨迹:先快后慢,有对准时间

# 构造滑动轨迹
def get_trace(distance):
    :param distance: (Int)缺口离滑块的距离
    :return: (List)移动轨迹
    # 创建存放轨迹信息的列表
    trace = []
    # 设置加速的距离
    faster_distance = distance*(4/5)
    # 设置初始位置、初始速度、时间间隔
    start, v0, t = 0, 0, 0.2
    # 当尚未移动到终点时
    while start < distance:
        # 如果处于加速阶段
        if start < faster_distance:
            # 设置加速度为2
            a = 1.5
        # 如果处于减速阶段
        else:
            # 设置加速度为-3
            a = -3
        # 移动的距离公式
        move = v0 * t + 1 / 2 * a * t * t
        # 此刻速度
        v = v0 + a * t
        # 重置初速度
        v0 = v
        # 重置起点
        start += move
        # 将移动的距离加入轨迹列表
        trace.append(round(move))
    # 返回轨迹信息
    return trace

2.利用selenium拖拽滑块

# 模拟拖动
def move_to_gap(trace):
    # 得到滑块标签
    slider = wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'gt_slider_knob')))
    # 使用click_and_hold()方法悬停在滑块上,perform()方法用于执行
    ActionChains(browser).click_and_hold(slider).perform()
    for x in trace:
        # 使用move_by_offset()方法拖动滑块,perform()方法用于执行
        ActionChains(browser).move_by_offset(xoffset=x, yoffset=0).perform()
    # 模拟人类对准时间
    sleep(0.5)
    # 释放滑块
    ActionChains(browser).release().perform()

结果展示:

程序结构图

from selenium import webdriver
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver import ActionChains
from urllib.request import urlretrieve
from bs4 import BeautifulSoup
import re
from PIL import Image
from time import sleep
# 初始化
def init():
    # 定义为全局变量,方便其他模块使用
    global url, browser, username, password, wait
    # 登录界面的url
    url = 'https://passport.bilibili.com/login'
    # 实例化一个chrome浏览器
    browser = webdriver.Chrome()
    # 用户名
    username = '***********'
    password = '***********'
    # 设置等待超时
    wait = WebDriverWait(browser, 20)
def login():
    # 打开登录页面
    browser.get(url)
    # 获取用户名输入框
    user = wait.until(EC.presence_of_element_located((By.ID, 'login-username')))
    # 获取密码输入框
    passwd = wait.until(EC.presence_of_element_located((By.ID, 'login-passwd')))
    # 输入用户名
    user.send_keys(username)
    # 输入密码
    passwd.send_keys(password)
# 获取图片信息
def get_image_info(img):
    :param img: (Str)想要获取的图片类型:带缺口、原始
    :return: 该图片(Image)、位置信息(List)
    # 将网页源码转化为能被解析的lxml格式
    soup = BeautifulSoup(browser.page_source, 'lxml')
    # 获取验证图片的所有组成片标签
    imgs = soup.find_all('div', {'class': 'gt_cut_'+img+'_slice'})
    # 用正则提取缺口的小图片的url,并替换后缀
    img_url = re.findall('url\(\"(.*)\"\);', imgs[0].get('style'))[0].replace('webp', 'jpg')
    # 使用urlretrieve()方法根据url下载缺口图片对象
    urlretrieve(url=img_url, filename=img+'.jpg')
    # 生成缺口图片对象
    image = Image.open(img+'.jpg')
    # 获取组成他们的小图片的位置信息
    position = get_position(imgs)
    # 返回图片对象及其位置信息
    return image, position
# 获取小图片位置
def get_position(img):
    :param img: (List)存放多个小图片的标签
    :return: (List)每个小图片的位置信息
    img_position = []
    for small_img in img:
        position = {}
        # 获取每个小图片的横坐标
        position['x'] = int(re.findall('background-position: (.*)px (.*)px;', small_img.get('style'))[0][0])
        # 获取每个小图片的纵坐标
        position['y'] = int(re.findall('background-position: (.*)px (.*)px;', small_img.get('style'))[0][1])
        img_position.append(position)
    return img_position
# 裁剪图片
def Corp(image, position):
    :param image:(Image)被裁剪的图片
    :param position: (List)该图片的位置信息
    :return: (List)存放裁剪后的每个图片信息
    # 第一行图片信息
    first_line_img = []
    # 第二行图片信息
    second_line_img = []
    for pos in position:
        if pos['y'] == -58:
            first_line_img.append(image.crop((abs(pos['x']), 58, abs(pos['x']) + 10, 116)))
        if pos['y'] == 0:
            second_line_img.append(image.crop((abs(pos['x']), 0, abs(pos['x']) + 10, 58)))
    return first_line_img, second_line_img
# 拼接大图
def put_imgs_together(first_line_img, second_line_img, img_name):
    :param first_line_img: (List)第一行图片位置信息
    :param second_line_img: (List)第二行图片信息
    :return: (Image)拼接后的正确顺序的图片
    # 新建一个图片,new()第一个参数是颜色模式,第二个是图片尺寸
    image = Image.new('RGB', (260,116))
    # 初始化偏移量为0
    offset = 0
    # 拼接第一行
    for img in first_line_img:
        # past()方法进行粘贴,第一个参数是被粘对象,第二个是粘贴位置
        image.paste(img, (offset, 0))
        # 偏移量对应增加移动到下一个图片位置,size[0]表示图片宽度
        offset += img.size[0]
    # 偏移量重置为0
    x_offset = 0
    # 拼接第二行
    for img in second_line_img:
        # past()方法进行粘贴,第一个参数是被粘对象,第二个是粘贴位置
        image.paste(img, (x_offset, 58))
        # 偏移量对应增加移动到下一个图片位置,size[0]表示图片宽度
        x_offset += img.size[0]
    # 保存图片
    image.save(img_name)
    # 返回图片对象
    return image
# 判断像素是否相同
def is_pixel_equal(bg_image, fullbg_image, x, y):
    :param bg_image: (Image)缺口图片
    :param fullbg_image: (Image)完整图片
    :param x: (Int)位置x
    :param y: (Int)位置y
    :return: (Boolean)像素是否相同
    # 获取缺口图片的像素点(按照RGB格式)
    bg_pixel = bg_image.load()[x, y]
    # 获取完整图片的像素点(按照RGB格式)
    fullbg_pixel = fullbg_image.load()[x, y]
    # 设置一个判定值,像素值之差超过判定值则认为该像素不相同
    threshold = 60
    # 判断像素的各个颜色之差,abs()用于取绝对值
    if (abs(bg_pixel[0] - fullbg_pixel[0] < threshold) and abs(bg_pixel[1] - fullbg_pixel[1] < threshold) and abs(bg_pixel[2] - fullbg_pixel[2] < threshold)):
        # 如果差值在判断值之内,返回是相同像素
        return True
    else:
        # 如果差值在判断值之外,返回不是相同像素
        return False
# 计算滑块移动距离
def get_distance(bg_image, fullbg_image):
    :param bg_image: (Image)缺口图片
    :param fullbg_image: (Image)完整图片
    :return: (Int)缺口离滑块的距离
    # 滑块的初始位置
    distance = 57
    # 遍历像素点横坐标
    for i in range(distance, fullbg_image.size[0]):
        # 遍历像素点纵坐标
        for j in range(fullbg_image.size[1]):
            # 如果不是相同像素
            if not is_pixel_equal(fullbg_image, bg_image, i, j):
                # 返回此时横轴坐标就是滑块需要移动的距离
                return i
# 构造滑动轨迹
def get_trace(distance):
    :param distance: (Int)缺口离滑块的距离
    :return: (List)移动轨迹
    # 创建存放轨迹信息的列表
    trace = []
    # 设置加速的距离
    faster_distance = distance*(4/5)
    # 设置初始位置、初始速度、时间间隔
    start, v0, t = 0, 0, 0.2
    # 当尚未移动到终点时
    while start < distance:
        # 如果处于加速阶段
        if start < faster_distance:
            # 设置加速度为2
            a = 1.5
        # 如果处于减速阶段
        else:
            # 设置加速度为-3
            a = -3
        # 移动的距离公式
        move = v0 * t + 1 / 2 * a * t * t
        # 此刻速度
        v = v0 + a * t
        # 重置初速度
        v0 = v
        # 重置起点
        start += move
        # 将移动的距离加入轨迹列表
        trace.append(round(move))
    # 返回轨迹信息
    return trace
# 模拟拖动
def move_to_gap(trace):
    # 得到滑块标签
    slider = wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'gt_slider_knob')))
    # 使用click_and_hold()方法悬停在滑块上,perform()方法用于执行
    ActionChains(browser).click_and_hold(slider).perform()
    for x in trace:
        # 使用move_by_offset()方法拖动滑块,perform()方法用于执行
        ActionChains(browser).move_by_offset(xoffset=x, yoffset=0).perform()
    # 模拟人类对准时间
    sleep(0.5)
    # 释放滑块
    ActionChains(browser).release().perform()
# 主程序
def main():
    # 初始化
    init()
    login()
    # 获取缺口图片及其位置信息
    bg, bg_position = get_image_info('bg')
    # 获取完整图片及其位置信息
    fullbg, fullbg_position = get_image_info('fullbg')
    # 将混乱的缺口图片裁剪成小图,获取两行的位置信息
    bg_first_line_img, bg_second_line_img = Corp(bg, bg_position)
    # 将混乱的完整图片裁剪成小图,获取两行的位置信息
    fullbg_first_line_img, fullbg_second_line_img = Corp(fullbg, fullbg_position)
    # 根据两行图片信息拼接出缺口图片正确排列的图片
    bg_image = put_imgs_together(bg_first_line_img, bg_second_line_img, 'bg.jpg')
    # 根据两行图片信息拼接出完整图片正确排列的图片
    fullbg_image = put_imgs_together(fullbg_first_line_img, fullbg_second_line_img, 'fullbg.jpg')
    # 计算滑块移动距离
    distance = get_distance(bg_image, fullbg_image)
    # 计算移动轨迹
    trace = get_trace(distance-10)
    # 移动滑块
    move_to_gap(trace)
    sleep(5)
# 程序入口
if __name__ == '__main__':
    main()

github: https://github.com/JeesonZhang/pythonspider/blob/master/bilibili_crack

某教育网站滑动验证码破解 - 识别率100% 项目Github地址: https://github.com/Henryhaohao/Slider_Captcha_Crack 某教育网站 - http://gkcf.jxedu.gov.cn/ 这是一篇详细介绍 Python 爬虫入门的教程,从实战出发,适合初学者。读者只需在阅读过程紧跟文章思路,理清相应的实现代码,30 分钟即可学会编写简单的 Python 爬虫。 这篇 Python 爬虫教程主要讲解以下 5 部分内容: 了解网页; 使用 requests 库抓取网站数据; 使用 Beautiful Soup 解析网页; 清洗和组织数据; 爬虫攻防战; 前程无忧爬虫–仅供学习使用 前程无忧职位链接:https://search.51job.com/list/090200,000000,0000,00,9,99,大数据,2,1.html?lang=c&postchannel=0000&workyear=99&cotype=99&degreefrom=99&jobterm=99&companysize=99&ord_field=0&dibiaoid=0&line=&welfare= 先右键检查分析网页,这里我们已经找到了详情页的链接 可以看到详情页的链接就在a标签 python处理验证滑块的思路如下:用selenium打开网页->定位模块->用selenium模拟鼠标拖动滑块完成验证。 1、首先打开开发者工具,用元素选择整个滑块模块,查看尺寸 这里在选择后整个滑块显示的尺寸为宽300px,高40px,接下来我们选择滑块按钮,显示的是40px,意味着我们需要将滑块按钮滑动300px-40px=260px 接下来我们引入selenium模块,利用xpath定位滑块位置,代码如下: 定位滑块后,我们利用selenium库提供的ActionChains模块下的click 滑动滑块的两个关键点为:(1)生成滑动轨迹(2)控制滑动按钮进行滑动 (1)根据要滑动的距离生成滑动轨迹,此处是模拟人为滑动:先加速滑动滑块,再减速滑动滑块。代码如下: 其中distance参数就是要滑动的距离,返回值tracks为生成的滑动轨迹,,后面要把tracks传给滑动滑块的函数。 对于 Python 爬虫中遇到的滑块验证,你可以考虑以下几种方法来解决: 手动解决滑块验证:在爬虫程序中手动解决滑块验证,比如通过手动模拟鼠标滑动操作来完成滑块验证。 使用浏览器插件解决滑块验证:你可以使用浏览器插件,比如 Chrome 浏览器中的「验证识别」插件,来自动识别并填写滑块验证。 使用 Selenium 来解决滑块验证:你可以使用 Selenium 这个自动化测试工具来模拟人的... 上篇我们借助 tesserocr 库解决了图片码的识别验证,但在实际生活中,用得更多的是滑动验证,这篇就来解决滑动验证的问题滑动验证大部分网站都采用的是滑动验证,就是拖动滑块拼接图片,其中又大都采用极验(http://www.geetest.com/)所提供的技术,官方网页如下本篇案例选用哔哩哔哩动画验证登录(https://passport.bilibili.com/login)所需工具chro... ## 2. 分析网站结构 在爬取网站信息之前,我们需要先了解一下网站的结构。通过分析淘宝网的页面源代码,我们可以发现,每个商品的信息都包含在一个<div>标签中,并且每个商品信息的标签结构都是相同的。因此,我们可以通过分析标签结构,编写相应的爬虫程序来抓取商品信息。 ## 3. 编写爬虫程序 根据分析结果,我们编写了一个Python爬虫程序。该程序使用了Requests库和BeautifulSoup库来获取网页源代码和解析HTML标签。具体的爬虫流程如下: 1. 使用Requests库发送HTTP请求,获取网页源代码。 2. 使用BeautifulSoup库解析HTML标签,获取商品信息。 3. 将商品信息存储到本地数据库中。 ## 4. 存储数据 我们选择了MySQL数据库作为存储数据的工 会议室预订系统一、目标及业务流程期望效果:业务流程:用户注册用户登录预订会议室退订会议室选择日期;今日以及以后日期二、表结构设计和生成1、models.py(用户继承AbstractUser)from django.db importmodelsfrom django.contrib.auth.models importAbstractUser#Create your models here.cl... min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res) # 寻找最优匹配。cv2.rectangle(bg_img, tl, br, (0, 0, 255), 2) # 绘制矩形。cv2.imwrite(mark_img, bg_img) # 保存在本地。bg_img = cv2.imread(bg_img) # 背景图片。tp_img = cv2.imread(tp_img) # 缺口图片。# print('原来:', tl[0]) 博客来源于: https://www.cnblogs.com/wyh0923/p/16528354.html # -*- coding: utf-8 -*- # https://gov.pkulaw.cn/ 1. import io 2. import json 3. from pathlib import Path 4. from PIL import Image 5. import bas...