python-selenium模拟登录bilibili之挑战极验滑动

目标网站:https://passport.bilibili.com/login

研究了三天模拟登录挑战极验滑动。

绝大多数网上已有的模拟登陆bilibili的代码很相似(如下),主要部分的滑动都是先加速后减速,但是呢效果甚微,几乎十死无生,偶尔就突然那么一次给你登陆成功。接下来,目光往下往下再往下..........................

from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver import ActionChains
from selenium.common.exceptions import TimeoutException
import time
import base64
from PIL import Image
from io import BytesIO


USERNAME = '填你的账号'
PASSWARD = '填你的密码'
BORDER = 12
INIT_LEFT = 60

class loginBili():
    def __init__(self):
        self.url = 'https://passport.bilibili.com/login'  # B站登录界面
        # 创建chrome参数对象
        chrome_options = webdriver.ChromeOptions()
        # 设置为开发者模式
        chrome_options.add_experimental_option('excludeSwitches', ['enable-automation'])
        self.browser = webdriver.Chrome(options=chrome_options)
        # 定义显示等待
        self.wait = WebDriverWait(self.browser, 20)
        self.username = USERNAME
        self.password = PASSWARD

    # def __del__(self):
    #     # 关闭浏览器
    #     self.browser.close()

    def login_successfully(self):
        """
        判断是否登陆成功
        :return:
        """
        try:
            # 登录成功后 界面上会有一个消息按钮
            return bool(
                WebDriverWait(self.browser, 5).until(EC.presence_of_element_located((By.XPATH, '//span[text()="消息"]')))
            )
        except TimeoutException:
            print('超时了!!!')
            return False

    def move_to_gap(self,track):
        """
        拖动滑块到缺口处
        :param track: 轨迹
        :return:
        """
        # 按住滑块
        slider = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.geetest_slider_button')))
        ActionChains(self.browser).click_and_hold(slider).perform()
        for x in track:
            # 只有水平方向有运动 按轨迹移动
            ActionChains(self.browser).move_by_offset(xoffset=x, yoffset=0).perform()
        time.sleep(0.5)
        ActionChains(self.browser).release().perform()  # 松开鼠标

    def get_track(self, gap):
        """
        根据偏移量 获取移动轨迹
        :param gap: 偏移量
        :return: 移动轨迹
        """
        # 移动轨迹
        track = []
        # 当前位移
        current = 0
        # 减速阈值
        mid = gap * 4 / 5  # 前4/5段加速 后1/5段减速
        # 计算间隔
        t = 0.2
        # 初速度
        v = 0
        while current < gap:
            if current < mid:
                a = 5  # 加速度为+3
            else:
                a = -5  # 加速度为-3

            # 初速度v0
            v0 = v
            # 当前速度
            v = v0 + a * t
            # 移动距离
            move = v0 * t + 0.5 * a * (t ** 2)
            # 当前位移
            current += move
            # 加入轨迹
            track.append(round(move))

        return track

    def is_pixel_equal(self, image1, image2, x, y):
        """
        判断两张图片 各个位置的像素是否相同
        :param image1:不带缺口的图片
        :param image2: 带缺口的图片
        :param x: 位置x
        :param y: 位置y
        :return: (x,y)位置的像素是否相同
        """
        # 获取两张图片指定位置的像素点
        pixel1 = image1.load()[x, y]
        pixel2 = image2.load()[x, y]
        # 设置一个阈值 允许有误差
        threshold = 10
        # 彩色图 每个位置的像素点有三个通道
        if abs(pixel1[0] - pixel2[0]) < threshold and abs(pixel1[1] - pixel2[1]) < threshold and abs(
                pixel1[2] - pixel2[2]) < threshold:
            return True
        return False

    def get_gap(self, image1, image2):
        """
        获取缺口偏移量
        :param image1:不带缺口的图片
        :param image2: 带缺口的图片
        :return:
        """
        left = INIT_LEFT  # 定义一个左边的起点 缺口一般离图片左侧有一定的距离 有一个滑块
        for i in range(INIT_LEFT, image1.size[0]):  # 从左到右 x方向
            for j in range(image1.size[1]):  # 从上到下 y方向
                if not self.is_pixel_equal(image1, image2, i, j):
                    left = i  # 找到缺口的左侧边界 在x方向上的位置
                    return left

        return left

    def get_geetest_image(self):
        """
        获取验证码图片
        :return: 图片对象
        """
        # # 带滑块和阴影的图片
        # im = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.geetest_canvas_bg')))
        # im.screenshot('image.png')

        time.sleep(1)
        # 执行 JS 代码并拿到完整图 base64 数据
        JS = 'return document.getElementsByClassName("geetest_canvas_fullbg")[0].toDataURL("image/png");'
        im_info = self.browser.execute_script(JS)
        # print(im_info)
        # 拿到base64编码的图片信息
        im_base64 = im_info.split(',')[1]
        # 转为bytes类型
        image1 = base64.b64decode(im_base64)
        # 加载图片
        image1 = Image.open(BytesIO(image1))
        # 保存图片
        image1.save('image1.png')

        # 执行 JS 代码并拿到只带阴影图 base64 数据
        JS = 'return document.getElementsByClassName("geetest_canvas_bg")[0].toDataURL("image/png");'
        im_info = self.browser.execute_script(JS)
        # print(im_info)
        # 拿到base64编码的图片信息
        im_base64 = im_info.split(',')[1]
        # 转为bytes类型
        image2 = base64.b64decode(im_base64)
        # 加载图片
        image2 = Image.open(BytesIO(image2))
        # 保存图片
        image2.save('image2.png')

        return image1, image2

    def get_login_btn(self):
        """
        登陆
        :return: None
        """
        # 打开网址
        self.browser.get(self.url)
        # 找到用户名输入框,在浏览器中定位它的HTML代码后 根据id属性来找
        username = self.wait.until(EC.presence_of_element_located((By.ID, 'login-username')))
        # 找到密码输入框
        password = self.wait.until(EC.presence_of_element_located((By.ID, 'login-passwd')))
        # 输入用户名和密码
        username.send_keys(self.username)
        time.sleep(2)
        password.send_keys(self.password)
        time.sleep(2)
        button = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'btn-login')))
        return button

    def run(self):
        # 输入用户名和密码并点击登录按钮
        self.get_login_btn().click()
        # 获取验证码图片
        image1, image2 = self.get_geetest_image()

        # 找到缺口的左侧边界 在x方向上的位置
        gap = self.get_gap(image1, image2)
        print('缺口位置:', gap)
        # 减去滑块左侧距离图片左侧在x方向上的距离 即为滑块实际要移动的距离
        gap -= BORDER
        # 获取移动轨迹
        track = self.get_track(gap)
        print('滑动轨迹:', track)
        # 按轨迹拖动滑块
        self.move_to_gap(track)

        if self.login_successfully():
            print("登录成功")
        else:
            print('正在重试.....')
            self.run()

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

 

好,停停停停停停停停停停停停停停停停停停停停停停停停停停停停停停停

在这里首先感谢下这么兄台的这篇https://blog.csdn.net/l_am_ydk/article/details/105577293,借鉴了滑动方法。

以下代码是我参考上述文章中的代码加以补全和修改后的完整代码,我测试了成功率还是很高的,基本一次成功,偶尔两三四五......................次

from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver import ActionChains
from selenium.common.exceptions import TimeoutException
import time
import base64
from PIL import Image
from io import BytesIO
import random


class Binance(object):
    def __init__(self):
        options = webdriver.ChromeOptions()
        # 设置为开发者模式
        options.add_experimental_option('excludeSwitches', ['enable-automation'])
        self.driver = webdriver.Chrome(options=options)
        self.USERNAME = '输入你的账号'
        self.PASSWARD = '输入你的密码'

    def get_login_btn(self):
        # 打开网址
        self.driver.get("https://passport.bilibili.com/login")
        username = WebDriverWait(self.driver, 10, 0.5).until(EC.presence_of_element_located((By.ID, 'login-username')))
        username.clear()
        username.send_keys(self.USERNAME)
        time.sleep(2)
        pwd = WebDriverWait(self.driver, 10, 0.5).until(EC.presence_of_element_located((By.ID, 'login-passwd')))
        pwd.clear()
        pwd.send_keys(self.PASSWARD)
        time.sleep(2)

        #点击登录,弹出滑块验证码
        login_btn = WebDriverWait(self.driver, 10, 0.5).until(EC.element_to_be_clickable((By.XPATH, '//*[@class="btn-box"]//a[@class="btn btn-login"]')))
        login_btn.click()
        WebDriverWait(self.driver, 10, 0.5).until(EC.presence_of_element_located((By.CLASS_NAME, 'geetest_slider_button')))


    def get_geetest_image(self):
        """
        获取验证码图片
        :return: 图片对象
        """
        # 执行 JS 代码并拿到完整图 base64 数据
        JS = 'return document.getElementsByClassName("geetest_canvas_fullbg")[0].toDataURL("image/png");'
        im_info = self.driver.execute_script(JS)
        # print(im_info)
        # 拿到base64编码的图片信息
        im_base64 = im_info.split(',')[1]
        # 转为bytes类型
        image1 = base64.b64decode(im_base64)
        # 加载图片
        image1 = Image.open(BytesIO(image1))
        # 保存图片
        image1.save('image1.png')

        # 执行 JS 代码并拿到只带阴影图 base64 数据
        JS = 'return document.getElementsByClassName("geetest_canvas_bg")[0].toDataURL("image/png");'
        im_info = self.driver.execute_script(JS)
        # print(im_info)
        # 拿到base64编码的图片信息
        im_base64 = im_info.split(',')[1]
        # 转为bytes类型
        image2 = base64.b64decode(im_base64)
        # 加载图片
        image2 = Image.open(BytesIO(image2))
        # 保存图片
        image2.save('image2.png')

        return image1, image2

    def is_pixel_equal(self, image1, image2, x, y):
        """
        判断两张图片 各个位置的像素是否相同
        :param image1:不带缺口的图片
        :param image2: 带缺口的图片
        :param x: 位置x
        :param y: 位置y
        :return: (x,y)位置的像素是否相同
        """
        # 获取两张图片指定位置的像素点
        pixel1 = image1.load()[x, y]
        pixel2 = image2.load()[x, y]
        # 设置一个阈值 允许有误差
        threshold = 10
        # 彩色图 每个位置的像素点有三个通道
        if abs(pixel1[0] - pixel2[0]) < threshold and abs(pixel1[1] - pixel2[1]) < threshold and abs(
                pixel1[2] - pixel2[2]) < threshold:
            return True
        return False

    def get_gap(self, image1, image2):
        """
        获取缺口偏移量
        :param image1:不带缺口的图片
        :param image2: 带缺口的图片
        :return:
        """
        left = 60  # 定义一个左边的起点 缺口一般离图片左侧有一定的距离 有一个滑块
        for i in range(60, image1.size[0]):  # 从左到右 x方向
            for j in range(image1.size[1]):  # 从上到下 y方向
                if not self.is_pixel_equal(image1, image2, i, j):
                    left = i  # 找到缺口的左侧边界 在x方向上的位置
                    return left

        return left

    def login_successfully(self):
        """
        判断是否登陆成功
        :return:
        """
        try:
            # 登录成功后 界面上会有一个消息按钮
            return bool(
                WebDriverWait(self.driver, 10).until(EC.presence_of_element_located((By.XPATH, '//span[text()="消息"]')))
            )
        except TimeoutException:
            print('超时了!!!')
            return False

    def analog_drag(self):
        # 刷新一下验证图片
        element = self.driver.find_element_by_xpath('//a[@class="geetest_refresh_1"]')
        element.click()
        time.sleep(1)

        # 保存两张图片
        full_image,cut_image = self.get_geetest_image()
        # 根据两个图片计算距离
        distance = self.get_gap(cut_image, full_image)

        # 开始移动
        self.start_move(distance)

        # 判断是否验证成功
        if self.login_successfully():
            print("登录成功")
        else:
            print('正在重试.....')
            self.run()

    def start_move(self, distance):
        element = self.driver.find_element_by_xpath('//div[@class="geetest_slider_button"]')

        # 这里就是根据移动进行调试,计算出来的位置不是百分百正确的,加上一点偏移
        distance -= element.size.get('width') / 2
        distance += 25
        print('缺口位置:' + str(distance))
        # 按下鼠标左键
        ActionChains(self.driver).click_and_hold(element).perform()
        time.sleep(0.5)
        while distance > 0:
            if distance > 10:
                # 如果距离大于10,就让他移动快一点
                span = random.randint(5, 8)
            else:
                # 快到缺口了,就移动慢一点
                span = random.randint(2, 3)
            ActionChains(self.driver).move_by_offset(span, 0).perform()
            distance -= span
            print(f'本次滑动距离:{str(span)}  还剩:{str(distance)}')
            time.sleep(random.randint(10, 50) / 100)

        # 往回滑点
        ActionChains(self.driver).move_by_offset(distance, 1).perform()
        ActionChains(self.driver).release(on_element=element).perform()

    def run(self):
        # 登陆弹出验证码
        self.get_login_btn()
        # 点击滑块并滑动
        self.analog_drag()

if __name__ == '__main__':
    ok = Binance()
    ok.run()

另外在网上还发现了同样是模拟登陆bilibili,但是对于图像处理是不同的,有兴趣可以试试。

https://www.jb51.net/article/185340.htm

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值