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()