selenium+python模拟登陆B站

selenium+python模拟登陆B站

https://www.jianshu.com/u/948da055a416

"""
    B站模拟登陆即极验.
    成功率一般, 具体的轨迹移动可自定义.
"""
from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
import time
import traceback
from PIL import Image
import re
import requests
from random import randint


def get_position(driver, klass):
    """
    获取网页上的验证码图片的偏移位置.
    :param driver: chrome driver对象.
    :param klass: 验证码的xpath特征点.
    :return: 图片的偏移位置列表.
    """
    eles = WebDriverWait(driver, 10).until(
        EC.presence_of_all_elements_located((By.XPATH, f"//div[@class='{klass}']/div"))
    )
    style_list = [ele.get_attribute('style') for ele in eles]

    return [re.findall(r'background-position: -(.*?)px -?(.*?)px;', sty)[0] for sty in style_list]


def action_displacement(driver, distance_list):
    """
    动作链执行, 移动滑块.
    :param driver: chrome driver对象.
    :param distance_list: 滑块轨迹移动列表.
    :return:
    """
    slide_button = WebDriverWait(driver, 10).until(
        EC.visibility_of_element_located((By.XPATH, "//div[@class='gt_slider_knob gt_show']")))
    ActionChains(driver).click_and_hold(slide_button).perform()
    for i in distance_list:
        ActionChains(driver).move_by_offset(xoffset=i, yoffset=0).perform()
    ActionChains(driver).release().perform()

    return


def acceleration(distance):
    """
    模拟人为滑块移动, 实为变加速运动, 这里也可以自定义修改为合适的人为移动.
    :param distance: 图片缺口距离.
    :return: 滑动轨迹列表.
    """
    t = 0.1
    speed = 0
    current = 0
    a_distance = distance * 4 / 5
    distance_list = []
    a_list = [1, 2, 3, 5, 4, 6]
    while current < distance:
        if current < a_distance:
            if distance < 50:
                a = a_list[randint(0, 2)]
            else:
                a = a_list[randint(3, 5)]
            a_move = speed * t + 0.5 * a * t * t
            speed += (a * t)
            distance_list.append(round(a_move))
            current += a_move

        else:
            distance_list.extend([3, 3, 2, 2, 1, 1])
            break

    offset = sum(distance_list) - distance
    if offset > 0:
        distance_list.extend([-1 for i in range(offset)])
    elif offset < 0:
        distance_list.extend([1 for i in range(abs(offset))])

    distance_list.extend([0, 0, 1, 1, 0, 0, -1, -1])

    return distance_list


def get_displacement(pixel):
    """
    获取图片上的缺口距离.
    :param pixel: 图片的pixel值.
    :return: 缺口距离.
    """
    offset = set()
    bg_pix, full_pix = pixel

    for x in range(0, 260):
        for y in range(0, 116):
            if abs(bg_pix[x, y] - full_pix[x, y]) > 50:
                offset.add(x)

    return min(offset)


def handle_pic(bg, bg_position):
    """
    将获得的打乱了的验证图片, 进行重组.
    :param bg: 图片的名字.
    :param bg_position: 图片的偏移位置.
    :return: 图片的pixel值.
    """
    old_im = Image.open(bg)
    new_im = Image.new('RGB', (312, 116), 125)

    offset = 0
    x = 0
    y = 0

    for posi in bg_position:
        if offset != 26:
            new_im.paste(old_im.crop((int(posi[0]) - 1, int(posi[1]), int(posi[0]) + 12, int(posi[1]) + 59)), (x, 0))
            offset += 1
            x += 10
        else:
            new_im.paste(old_im.crop((int(posi[0]) - 1, int(posi[1]), int(posi[0]) + 12, int(posi[1]) + 59)), (y, 58))
            y += 10

    new_im = new_im.crop((0, 0, 261, 117))
    new_im.save(f'new_{bg}')

    pixel_data = new_im.convert("L").load()
    new_im.load()

    new_im.close()
    old_im.close()

    return pixel_data


def get_picture(driver, klass):
    """
    获取验证码图片.
    :param driver: chrome driver对象.
    :param klass: 验证码图片的xpath特征点.
    :return: 格式化的验证码图片名.
    """
    ele = WebDriverWait(driver, 10).until(
        EC.presence_of_element_located((By.XPATH, f"//div[@class='{klass}']/div"))
    )
    url = re.findall(r'url\("(.*?)"\);', ele.get_attribute('style'))[0]
    with open(f'{klass.split()[0]}.jpg', 'wb') as f:
        f.write(requests.get(url).content)

    return f'{klass.split()[0]}.jpg'


def chr_opt():
    """
    chrome browser的设置.
    :return: 返回设置对象.
    """
    chrome_opt = Options()
    prefs = {
        'profile.default_content_setting_values': {
            'notifications': 2
        }
    }
    chrome_opt.add_experimental_option('prefs', prefs)
    chrome_opt.add_argument('--headless')
    chrome_opt.add_argument('--disable-gpu')
    chrome_opt.add_argument('--window-size=1366,768')

    return chrome_opt


def login_bili(driver):
    """
    输入登陆的账户和密码.
    :param driver: 传入webdriver对象即Chrome.
    :return:
    """
    user_element = driver.find_element_by_xpath("//li[@class='item username status-box']/input")
    pawd_element = driver.find_element_by_xpath("//li[@class='item password status-box']/input")

    user_element.clear()
    user_element.send_keys(user)

    pawd_element.send_keys(passwd)

    return


if __name__ == '__main__':
    '''
        逻辑主.
    '''
    # 配置用户名和密码.
    url = 'https://passport.bilibili.com/login'
    user = 'username'
    passwd = 'password'

    # 设置bg和full_bg的xpath特征点.
    bg_klass = 'gt_cut_bg gt_show'
    full_bg_klass = 'gt_cut_fullbg gt_show'

    driver = webdriver.Chrome(chrome_options=chr_opt())
    try:
        driver.get(url)
        # 将 获取的图片(get_picture)和获取的图片偏移位置(get_position)传入到handle_pic处理, 返回重组后的验证图片的pixel data.
        bg_pixel = handle_pic(get_picture(driver, bg_klass), get_position(driver, bg_klass))
        full_pixel = handle_pic(get_picture(driver, full_bg_klass), get_position(driver, full_bg_klass))

        # 将 获得的验证图片上缺口的距离位置(get_displacement) 传入到acceleration处理, 获得一个滑块轨迹的List.
        dis_list = acceleration(get_displacement((bg_pixel, full_pixel)) - 5)

        # 输入账户和密码.
        login_bili(driver)

        # 执行action chain.
        action_displacement(driver, dis_list)
        time.sleep(5)

        # 如果登陆成功, 网页会跳转即current_url会发生变化.
        if driver.current_url[-5:] != 'login':
            print("登陆成功")
        else:
            print("登陆失败")
        time.sleep(5)

    except:
        traceback.print_exc()

    finally:
        driver.quit()

你可能感兴趣的:(python)