思路

  1. 输入用户名密码点击登陆
  2. 获取验证码的原始图片与有缺口的图片
  3. 找出两张图片的缺口起始处
  4. 拖动碎片

功能代码段

# 使用到的库
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.action_chains import ActionChains
from PIL import Image
import time
import base64
username = '用户名'
password = '密码'
# 放在外面的原因是如果再类的内部初始化,则程序结束后浏览器会自动退出
driver = webdriver.Chrome()

初始化相关参数

    # 初始化相关参数
    def __init__(self):
        self.url = 'https://passport.bilibili.com/login'
        self.browser = driver
        self.wait = WebDriverWait(self.browser, 20)
        self.name = username
        self.pw = password

获取按钮、输入框、碎片拖动按钮对象

    def get_login_button(self):
        """
        获取初始登录按钮
        :return: 按钮对象
        """
        button = self.wait.until(
            EC.presence_of_element_located((By.XPATH, "//a[contains(@class,'btn') and contains(@class, 'btn-login')]")))
        return button

    def get_slider_button(self):
        """
        获取拖动碎片的地方
        :return: 拖动对象
        """
        sliderbutton = self.wait.until(EC.presence_of_element_located((By.XPATH, "//div[@class='geetest_slider_button']")))
        return sliderbutton

    def get_login_input(self):
        """
        获取登陆输入框(用户名/密码)
        :return: 输入框对象
        """
        user_login = self.wait.until(EC.presence_of_element_located((By.XPATH, "//input[@id='login-username']")))
        pw_login = self.wait.until(EC.presence_of_element_located((By.XPATH, "//input[@id='login-passwd']")))
        return user_login, pw_login

获取带有碎片的图片和完整图片

    def save_pic(self, data, filename):
        """
        解码获取到的base64再写入到文件中,保存图片
        :return:
        """
        data = data.split(',')[1]
        data = base64.b64decode(data)
        with open(filename, 'wb') as f:
            f.write(data)

    def get_pic(self):
        """
        获取无缺口图片和有缺口图片
        :return: 图片对象
        """
        picName = ['full.png', 'slice.png']
        # 图片对象的class
        className = ['geetest_canvas_fullbg', 'geetest_canvas_bg']
        # canvas标签中的图片通过js代码获取base64编码,然后再通过解码,将其写入文件才能获取到
        for i in range(len(className)):
            js = "var change = document.getElementsByClassName('"+className[i]\
                 + "'); return change[0].toDataURL('image/png');"
            im_info = self.browser.execute_script(js)
            self.save_pic(im_info, picName[i])

判断像素点是否相同

    def is_pixel_equal(self, image1, image2, x, y):
        """
        判断两个像素点是否是相同
        :param image1: 不带缺口图片
        :param image2: 带缺口图片
        :param x: 像素点的x坐标
        :param y: 像素点的y坐标
        :return:
        """
        pixel1 = image1.load()[x, y]
        pixel2 = image2.load()[x, y]
        threshold = 40
        if abs(pixel1[0] - pixel2[0]) < threshold \
                and abs(pixel1[1] - pixel2[1]) < threshold \
                and abs(pixel1[2] - pixel2[2]) < threshold:
            return True
        else:
            return False

获取需要移动的距离

    def get_gap(self, image1, image2):
        """
        获取缺口偏移量
        :param image1: 不带缺口图片
        :param image2: 带缺口图片
        :return:
        """
        # 这个可以自行操作一下,如果发现碎片对不准,可以调整
        left = 10
        for i in range(left, image1.size[0]):
            for j in range(image1.size[1]):
                if not self.is_pixel_equal(image1, image2, i, j):
                    left = i
                    return left
        return left

变速运动拖动碎片,否则容易被看出来是机器执行

    def get_track(self, distance):
        """
        根据偏移量获取移动轨迹
        :param self:
        :param distance: 偏移量
        :return: 移动轨迹
        """
        # 移动轨迹
        track = []
        # 当前位移
        current = 0
        # 对的不一定很准确,所以自行调整一下distance
        distance = distance - 9
        # 减速阈值 -> 也就是加速到什么位置的时候开始减速
        mid = distance * 4 / 5
        # 计算间隔
        t = 0.2
        # 初速度
        v = 0

        while current < distance:
            if current < mid:
                # 加速度为正2
                a = 2
            else:
                # 加速度为负3
                a = -3
            v0 = v
            v = v0 + a * t
            move = v0 * t + 1 / 2 * a * t * t
            current += move
            track.append(round(move))
        return track

模拟拖动碎片

    def move_to_gap(self, slider, tracks, browser):
        """
        拖动滑块到缺口处
        :param self:
        :param slider: 滑块
        :param tracks: 轨迹
        :return:
        """
        # click_and_hold()点击鼠标左键,不松开
        ActionChains(self.browser).click_and_hold(slider).perform()
        for x in tracks:
            # move_by_offset()鼠标从当前位置移动到某个坐标
            ActionChains(self.browser).move_by_offset(xoffset=x, yoffset=0).perform()
        time.sleep(0.5)
        # release()在某个元素位置松开鼠标左键
        ActionChains(self.browser).release().perform()

配置执行

    def test(self):
        # 输入用户名和密码
        self.browser.get(self.url)
        user_login, pw_login = self.get_login_input()
        user_login.send_keys(self.name)
        pw_login.send_keys(self.pw)
        # 点击按钮对象
        button = self.get_login_button()
        button.click()
        # 这里设置等待是为了使得滑动验证码能出现,之后才能通过toDataURL获取
        time.sleep(3)
        self.get_pic()
        image1 = Image.open('full.png')
        image2 = Image.open('slice.png')
        left = self.get_gap(image1, image2)
        track = self.get_track(left)
        slider = self.get_slider_button()
        self.move_to_gap(slider, track, self.browser)

完整代码

TIP
如果出现碎片移动存在一定对不准的情况,可以自行调整一下left和distance的值。

from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.action_chains import ActionChains
from PIL import Image
import time
import base64
username = '用户名'
password = '密码'
driver = webdriver.Chrome()


class Start:
    def __init__(self):
        self.url = 'https://passport.bilibili.com/login'
        self.browser = driver
        self.wait = WebDriverWait(self.browser, 20)
        self.name = username
        self.pw = password

    def get_login_button(self):
        """
        获取初始登录按钮
        :return: 按钮对象
        """
        button = self.wait.until(
            EC.presence_of_element_located((By.XPATH, "//a[contains(@class,'btn') and contains(@class, 'btn-login')]")))
        return button

    def get_slider_button(self):
        """
        获取拖动碎片的地方
        :return: 拖动对象
        """
        sliderbutton = self.wait.until(EC.presence_of_element_located((By.XPATH, "//div[@class='geetest_slider_button']")))
        return sliderbutton

    def get_login_input(self):
        """
        获取登陆输入框(用户名/密码)
        :return: 输入框对象
        """
        user_login = self.wait.until(EC.presence_of_element_located((By.XPATH, "//input[@id='login-username']")))
        pw_login = self.wait.until(EC.presence_of_element_located((By.XPATH, "//input[@id='login-passwd']")))
        return user_login, pw_login

    def save_pic(self, data, filename):
        """
        解码获取到的base64再写入到文件中,保存图片
        :return:
        """
        data = data.split(',')[1]
        data = base64.b64decode(data)
        with open(filename, 'wb') as f:
            f.write(data)

    def get_pic(self):
        """
        获取无缺口图片和有缺口图片
        :return: 图片对象
        """
        # 图片对象的类名
        # 首先需要这个东西已经出现了,我们才能去执行相关的js代码
        picName = ['full.png', 'slice.png']
        className = ['geetest_canvas_fullbg', 'geetest_canvas_bg']
        # canvas标签中的图片通过js代码获取base64编码
        for i in range(len(className)):
            js = "var change = document.getElementsByClassName('"+className[i]\
                 + "'); return change[0].toDataURL('image/png');"
            im_info = self.browser.execute_script(js)
            self.save_pic(im_info, picName[i])

    def is_pixel_equal(self, image1, image2, x, y):
        """
        判断两个像素点是否是相同
        :param image1: 不带缺口图片
        :param image2: 带缺口图片
        :param x: 像素点的x坐标
        :param y: 像素点的y坐标
        :return:
        """
        pixel1 = image1.load()[x, y]
        pixel2 = image2.load()[x, y]
        threshold = 40
        if abs(pixel1[0] - pixel2[0]) < threshold \
                and abs(pixel1[1] - pixel2[1]) < threshold \
                and abs(pixel1[2] - pixel2[2]) < threshold:
            return True
        else:
            return False

    def get_gap(self, image1, image2):
        """
        获取缺口偏移量
        :param image1: 不带缺口图片
        :param image2: 带缺口图片
        :return:
        """
        # 这个可以自行操作一下,如果发现碎片对不准,可以调整
        left = 10
        for i in range(left, image1.size[0]):
            for j in range(image1.size[1]):
                if not self.is_pixel_equal(image1, image2, i, j):
                    left = i
                    return left
        return left

    def get_track(self, distance):
        """
        根据偏移量获取移动轨迹
        :param self:
        :param distance: 偏移量
        :return: 移动轨迹
        """
        # 移动轨迹
        track = []
        # 当前位移
        current = 0
        # 因为老对不的不准确,所以自行调整一下distance
        distance = distance - 9
        # 减速阈值 -> 也就是加速到什么位置的时候开始减速
        mid = distance * 4 / 5
        # 计算间隔
        t = 0.2
        # 初速度
        v = 0

        while current < distance:
            if current < mid:
                # 加速度为正2
                a = 2
            else:
                # 加速度为负3
                a = -3
            v0 = v
            v = v0 + a * t
            move = v0 * t + 1 / 2 * a * t * t
            current += move
            track.append(round(move))
        return track

    def test(self):
        # 输入用户名和密码
        self.browser.get(self.url)
        user_login, pw_login = self.get_login_input()
        user_login.send_keys(self.name)
        pw_login.send_keys(self.pw)
        # 点击按钮对象
        button = self.get_login_button()
        button.click()
        # 保存图片
        time.sleep(3)
        self.get_pic()
        image1 = Image.open('full.png')
        image2 = Image.open('slice.png')
        left = self.get_gap(image1, image2)
        track = self.get_track(left)
        slider = self.get_slider_button()
        self.move_to_gap(slider, track, self.browser)

    def move_to_gap(self, slider, tracks, browser):
        """
        拖动滑块到缺口处
        :param self:
        :param slider: 滑块
        :param tracks: 轨迹
        :return:
        """
        # click_and_hold()点击鼠标左键,不松开
        ActionChains(self.browser).click_and_hold(slider).perform()
        for x in tracks:
            # move_by_offset()鼠标从当前位置移动到某个坐标
            ActionChains(self.browser).move_by_offset(xoffset=x, yoffset=0).perform()
        time.sleep(0.5)
        # release()在某个元素位置松开鼠标左键
        ActionChains(self.browser).release().perform()


Start().test()
01-18 17:01