学习了别人破解滑动验证码的过程,自己做了个逻辑图分享给大家,代码也在下面
逻辑导图
代码
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.common.action_chains import ActionChains
from selenium.common.exceptions import TimeoutException
from PIL import Image
from io import BytesIO
from time import sleep
import base64
class LoginBiliBili:
def __init__(self, username, password):
"""
初始化数据
:param username: bilibili账号
:param password: 密码
"""
options = webdriver.ChromeOptions()
# 设置为开发者模式,避免被识别
options.add_experimental_option('excludeSwitches',
['enable-automation'])
self.username = username
self.password = password
# 定义浏览器
self.browser = webdriver.Chrome(options=options)
# 定义显示等待
self.wait = WebDriverWait(self.browser, 50)
# bilibili登录url
self.url = 'https://passport.bilibili.com/login'
def open(self):
"""
打开浏览器, 进入登陆界面
输入用户名, 密码
点击登陆
:return: None
"""
self.browser.get(self.url)
username = self.wait.until(EC.element_to_be_clickable((By.ID, 'login-username')))
password = self.wait.until(EC.element_to_be_clickable((By.ID, 'login-passwd')))
sleep(1)
username.send_keys(self.username)
sleep(1)
password.send_keys(self.password)
login_button = self.wait.until(
EC.presence_of_element_located((By.XPATH, '//*[@id="geetest-wrap"]/div/div[5]/a[1]')))
# 点击登录
login_button.click()
# 休眠, 让验证码图片加载出来
sleep(2)
def get_geetest_image(self):
"""
获取极验验证码图片
:return: c_image(王者验证图) ic_image(有缺失的验证图)
"""
"""
完整的验证图
页面源码:
"""
# 执行js 拿到canvas画布里面的图片数据
js = 'return document.getElementsByClassName("geetest_canvas_fullbg")[0].toDataURL("image/png");'
# 图片数据
complete_img_data = self.browser.execute_script(js)
# base64 编码的图片信息
complete_img_base64 = complete_img_data.split(',')[1]
# 转成bytes类型
complete_img = base64.b64decode(complete_img_base64)
# 加载图片 return 回去对比
c_image = Image.open(BytesIO(complete_img))
# c_image.show()
# 保存图片 (可不必保存)
c_image.save('./bilibili_captcha/c_image.png')
"""
有缺失的验证码图
页面源码:
"""
# 执行js 拿到canvas画布里的图片数据
js = 'return document.getElementsByClassName("geetest_canvas_bg")[0].toDataURL("image/png");'
# 图片数据
incomplete_img_data = self.browser.execute_script(js)
# base64 编码的图片信息
incomplete_img_base64 = incomplete_img_data.split(',')[1]
# 转为bytes类型
incomplete_img = base64.b64decode(incomplete_img_base64)
# 直接加载图片 return 回去对比
ic_image = Image.open(BytesIO(incomplete_img))
# ic_image.show()
# 保存图片(可不必保存)
ic_image.save('./bilibili_captcha/ic_image.png')
return c_image, ic_image
def is_pixel_similar(self, c_image, ic_image, x, y):
"""
比较两张图片的像素点
注意: 像素点比较是有偏差的, 需要允许一定范围的误差,
我们可以设置一个阈值
:param ic_image:
:param c_image:
:param x:
:param y:
:return: 当像素点不相同时, 返回 False
"""
# 获取两张图片执行位置的像素点
c_pixel = c_image.load()[x, y]
ic_pixel = ic_image.load()[x, y]
# 阈值 允许误差
threshold = 10
# 对比
if abs(c_pixel[0] - ic_pixel[0]) < threshold and \
abs(c_pixel[1] - ic_pixel[1]) < threshold and \
abs(c_pixel[2] - ic_pixel[2]) < threshold:
return True
return False
def get_slice_gap(self, c_image, ic_image):
"""
获取缺口的偏移量
通过比较两张图片的所有像素点, 获取两张图片是从哪里开始不同
从而得到 移动块 要在 x 方向移动的距离
:param c_image: 完整的图片
:param ic_image: 有缺失的图片
:return: 缺口的偏移量
"""
# ic_image.size:['width', 'height']
for x in range(c_image.size[0]):
for y in range(c_image.size[1]):
if not self.is_pixel_similar(c_image, ic_image, x, y):
# 移动块只在水平方向移动 只需返回 x
return x
def drag_slider(self, gap):
"""
拖动滑块
:param gap: 需要拖动的距离
:return: None
"""
slider = self.wait.until(EC.presence_of_element_located((By.XPATH, '/html/body/div[2]/div[2]/div[6]/div/div[1]/div[2]/div[2]')))
# 抓住滑块
ActionChains(self.browser).click_and_hold(on_element=slider).perform()
# 移动 只在水平方向上移动
ActionChains(self.browser).move_by_offset(xoffset=gap // 2, yoffset=0).perform()
sleep(1)
ActionChains(self.browser).move_by_offset(xoffset=gap // 2, yoffset=0).perform()
# 释放滑块
ActionChains(self.browser).release().perform()
"""
判断是否登陆成功
:return: 成功返回 True 失败返回False
"""
def login_success(self):
"""
判断是否登陆成功
:return: 成功返回 True 失败返回False
"""
try:
# 登录成功后 界面上会有一个消息按钮
return bool(
self.wait.until(EC.text_to_be_present_in_element((By.CLASS_NAME, 'mini-upload'), '投稿'))
)
except TimeoutException:
return False
def login(self):
"""
开始
:return: None
"""
# 打开浏览器, 输入账号 密码, 点击登陆
self.open()
# 获取验证图 ic_image(有缺失的验证图) c_image(完整的验证图)
c_image, ic_image = self.get_geetest_image()
# 获取缺口的偏移量
gap = self.get_slice_gap(c_image, ic_image)
print(f'缺口的偏移量为:{gap}')
# 拖动滑块
self.drag_slider(gap - 6)
sleep(8)
if self.login_success():
print('登陆成功')
else:
self.login()
if __name__ == '__main__':
login = LoginBiliBili('***', '***') # 输入账号和密码
login.login()