破解滑块验证码(打码平台)

…衆ロ難τιáo~ 提交于 2019-12-03 02:38:22

此处以模拟登录B站为例,链接 >>> https://passport.bilibili.com/login
打码平台用的是 联众打码 >>> https://www.jsdati.com/

滑块验证码样式如下
在这里插入图片描述

步骤:

  1. 使用selenium键入账号密码,点击登录
  2. 待验证码弹出并加载完毕后,进行全屏截图
  3. 人工定位图片的位置,利用图片处理软件测量验证码距离全屏截图上下左右的距离
  4. 根据此距离利用图像处理库在全屏截图中截取验证码
  5. 重塑验证码的尺寸(和网页中css样式一致),并交给打码平台
  6. 处理打码平台的返回值,并计算出要滑动的距离
  7. 用selenium的动作链控制滑动

实现代码如下

# coding:utf-8

import json
import time

import requests
from PIL import Image
from selenium import webdriver
from selenium.webdriver import ActionChains


# 重写联众打码的api
def catch():
    '''
            main() 参数介绍
            api_username    (API账号)             --必须提供
            api_password    (API账号密码)         --必须提供
            file_name       (需要识别的图片路径)   --必须提供
            api_post_url    (API接口地址)         --必须提供
            yzm_min         (识别结果最小长度值)        --可空提供
            yzm_max         (识别结果最大长度值)        --可空提供
            yzm_type        (识别类型)          --可空提供
            tools_token     (工具或软件token)     --可空提供
    '''
    api_username = '',
    api_password = ''
    file_name = 'D:/shuati/bilibili_login/yan_zheng_ma.png'
    api_post_url = "http://v1-http-api.jsdama.com/api.php?mod=php&act=upload"
    yzm_min = ''
    yzm_max = ''
    yzm_type = '1310'
    tools_token = ''

    # proxies = {'http': 'http://127.0.0.1:8888'}
    headers = {
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
        'Accept-Language': 'zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3',
        'Accept-Encoding': 'gzip, deflate',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:53.0) Gecko/20100101 Firefox/53.0',
        # 'Content-Type': 'multipart/form-data; boundary=---------------------------227973204131376',
        'Connection': 'keep-alive',
        'Host': 'v1-http-api.jsdama.com',
        'Upgrade-Insecure-Requests': '1'
    }

    files = {
        'upload': (file_name, open(file_name, 'rb'), 'image/png')
    }

    data = {
        'user_name': api_username,
        'user_pw': api_password,
        'yzm_minlen': yzm_min,
        'yzm_maxlen': yzm_max,
        'yzmtype_mark': yzm_type,
        'zztool_token': tools_token
    }
    s = requests.session()
    # r = s.post(api_post_url, headers=headers, data=data, files=files, verify=False, proxies=proxies)
    r = s.post(api_post_url, headers=headers, data=data, files=files, verify=False)
    print(r.text)
    return r.text


class Login(object):
    def __init__(self):
        url = 'https://passport.bilibili.com/login'
        self.driver = webdriver.Chrome()
        self.driver.get(url)
        self.driver.maximize_window()

    def send(self):
        self.driver.implicitly_wait(6)
        username_input = self.driver.find_element_by_id('login-username')
        username_input.clear()
        username_input.send_keys('18024819731')

        password_input = self.driver.find_element_by_id('login-passwd')
        password_input.clear()
        password_input.send_keys('163')

        # 需要注意的东西是有多个class_name的话,只取其中一个就可以了
        # 如此处 class="btn btn-login" 只取出 btn-login 即可
        login_button = self.driver.find_element_by_class_name('btn-login')
        login_button.click()

    def scream_catch(self):
        self.driver.get_screenshot_as_file('whole_scream.png')
        element = self.driver.find_element_by_class_name('geetest_absolute')
        print(element.location)
        print(element.size)


        # 因为滑块每次出现的位置都是一样的,所以定死的取出图片的位置即可
        # 用Fireworks获取图片所在的坐标(x,y)
        left = 1108
        top = 321
        right = left + 314
        bottom = top + 195

        im = Image.open('whole_scream.png')
        # 在全屏的截图中取出验证码的所在位置
        im = im.crop((left, top, right, bottom))

        # 估计是因为屏幕的原因,从全屏截取出来的验证码的尺寸是等比例放大了的
        # 但是必须用和网页中验证码的尺寸相等的图片来提交给打码平台
        # 重塑图片的尺寸大小
        im = im.resize((253, 155), Image.ANTIALIAS)
        im.save('yan_zheng_ma.png')
        # 调用打码平台的api
        json_data = catch()
        json_data = json.loads(json_data)
        string = json_data['data']['val']
        if '|' in string:
            a, b = string.split('|')
            a, c = a.split(',')
            b, d = b.split(',')

            x_offset = int(a) - int(b)
            offset = int(c) - int(d)
            # 返回值的第一个数和第三个数之差的绝对值就是要滑动的距离
            x_offset = x_offset if x_offset > 0 else -x_offset
            offset = offset if offset > 0 else -offset
            print(offset)
            # 经过多次调试发现,如果两个坐标的y值之差大于7就会左偏 |c - d|的偏差
            if offset >= 7:
                x_offset += offset
            print(x_offset)
            return x_offset
        else:
            # 经过多次调试发现,打码平台有时候会返回没有'|'的字符串,而此时里面的第一个数就是距离
            x_offset = string.split(',')[0]
            return int(x_offset)

    def slip_button(self):
        x = self.scream_catch()
        # 滑动的动作链
        handler = self.driver.find_element_by_class_name('geetest_slider_button')
        ActionChains(self.driver).click_and_hold(handler).perform()
        time.sleep(1)
        ActionChains(self.driver).move_by_offset(xoffset=x, yoffset=0).perform()
        # 此处一定要睡,如果不睡的话,可能还没拉到那个位置就直接进行下一步的动作了
        time.sleep(1.5)
        ActionChains(self.driver).click().perform()

    def run(self):
        self.send()
        # 睡一下,等验证码完全加载出来,不然可能截出来是验证码没加载好的截图
        time.sleep(2)
        self.slip_button()


if __name__ == '__main__':
    login = Login()
    login.run()

经过多次测试,成功率大概在70%。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!