Python爬虫实战

在本篇博客中,我们将使用selenium模拟登录bilibili网站,破解其登陆时的滑动验证码。

首先回顾一下,滑动验证码相关知识:

简介
滑动图形验证码,主要由两个图片组成:抠块和带有抠块阴影的原图。

这里有两个重要特性保证被暴力破解的难度:

(1)抠块的形状随机

(2)抠块所在原图的位置随机

生成滑动验证码
1)后端随机生成抠图和带有抠图阴影的背景图片,后台保存随机抠图位置坐标;

2)前端实现滑动交互,将抠图拼在抠图阴影之上,获取到用户滑动距离值;

3)前端将用户滑动距离值传入后端,后端校验误差是否在容许范围内。

利用selenium解决滑动验证码
1)获取没有缺口的图片

2)获取带缺口的图片

3)对比两张图片的所有RBG像素点,得到不一样像素点的x值(即要移动的距离)

4)模拟人的行为习惯(先匀加速拖动后匀减速拖动),把需要拖动的总距离分成一段一段小的轨迹

5)按照轨迹拖动,完成验证

程序主体框架:

#登陆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

#bilibili用户名、密码
#全局变量

USERNAME = ‘’
PASSWARD = ‘’
BORDER = 6
INIT_LEFT = 60

class loginBili():

def __init__(self):
    self.url = 'https://passport.bilibili.com/login' #B站登录界面
    self.browser = webdriver.Chrome()
    #定义显示等待
    self.wait = WebDriverWait(self.browser,20)
    self.username = USERNAME
    self.password = PASSWARD

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

def move_to_gap(self,slider,track):

def get_slider(self):
    pass

def get_track(self,gap):
    pass

def get_gap(self,image1,image2):
    pass

def get_geetest_image(self):
    pass

def get_login_btn(self):
    pass

def open(self):
    pass

def login(self):
    #输入用户名和密码
    self.open()
    #点击登录按钮
    button = self.get_login_btn() #找到登录按钮
    button.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)
    
    #点按滑块
    slider = self.get_slider()
    
    #按轨迹拖动滑块
    self.move_to_gap(slider,track)
    
    if self.login_successfully():
        print("登录成功")
    else: #可能不成功 再试一次
        time.sleep(5)
        self.login()

if name == ‘main’:
login = loginBili()
login.login()

打开登陆界面,输入用户名和密码:

def open(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)
    password.send_keys(self.password)

找到登录按钮:

def get_login_btn(self):
“”"
登陆
:return: None
“”"
‘’’
登录
‘’’
button = self.wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,’.btn btn-login’)))
return button
获取验证码图片:

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

    # 执行 JS 代码并拿到图片 base64 数据
    JS = 'return document.getElementsByClassName("geetest_canvas_fullbg")[0].toDataURL("image/png");'  # 不带阴影的完整图片
    im_info = self.browser.execute_script(JS)  # 执行js文件得到带图片信息的图片数据
    print(im_info)
    # 拿到base64编码的图片信息
    im_base64 = im_info.split(',')[1]
    # 转为bytes类型
    captcha1 = base64.b64decode(im_base64)
    # 将图片保存在本地
    with open('captcha1.png', 'wb') as f:
        f.write(captcha1)

        # 执行 JS 代码并拿到图片 base64 数据
        JS = 'return document.getElementsByClassName("geetest_canvas_bg")[0].toDataURL("image/png");'  # 带阴影的图片
        im_info = self.browser.execute_script(JS)  # 执行js文件得到带图片信息的图片数据
        print(im_info)
        # 拿到base64编码的图片信息
        im_base64 = im_info.split(',')[1]
        # 转为bytes类型
        captcha2 = base64.b64decode(im_base64)
        # 将图片保存在本地
        with open('captcha2.png', 'wb') as f:
            f.write(captcha2)

    captcha1 = Image.open('captcha1.png')
    captcha2 = Image.open('captcha2.png')
    return captcha1, captcha2

找到缺口的左侧边界 在x方向上的位置:

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 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 = 60
# 彩色图 每个位置的像素点有三个通道
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_track(self,gap):
    """
    根据偏移量 获取移动轨迹
    :param distance: 偏移量
    :return: 移动轨迹
    """

    # 移动轨迹
    track = []
    # 当前位移
    current = 0
    # 减速阈值
    mid = gap * 4 / 5  # 前4/5段加速 后1/5段减速
    # 计算间隔
    t = 0.2
    # 初速度
    v = 0

    while current < gap:
        if current < mid:
            a = 2  # 加速度为+2
        else:
            a = -3  # 加速度为-3

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

找到滑块对象(拖动滑块的按钮):

def get_slider(self):
“”"
获取滑块
:return: 滑块对象
“”"
‘’’


‘’’
slider = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,’.geetest_slider_button’)))

    return slider

按照运动轨迹把滑块拖到缺口处:

def move_to_gap(self,slider,track):
    """
    拖动滑块到缺口处
    :param slider:滑块
    :param track: 轨迹
    :return:
    """
    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 login_successfully(self):
    """
    判断是否登陆成功
    :return:
    """
    try:
        '''
       
3
消息
''' #登录成功后 界面上会有一个消息按钮 return bool( WebDriverWait(self.browser,5).until(EC.presence_of_element_located((By.XPATH,'//a[@title="消息"]'))) ) except TimeoutException: return False 完整代码:

#登陆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

#bilibili用户名、密码
#全局变量

USERNAME = ‘’
PASSWARD = ‘’
BORDER = 6
INIT_LEFT = 60

class loginBili():

def __init__(self):
    self.url = 'https://passport.bilibili.com/login' #B站登录界面
    self.browser = webdriver.Chrome()
    #定义显示等待
    self.wait = WebDriverWait(self.browser,20)
    self.username = USERNAME
    self.password = PASSWARD

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

def login_successfully(self):
    """
    判断是否登陆成功
    :return:
    """
    try:
        '''
       
3
消息
''' #登录成功后 界面上会有一个消息按钮 return bool( WebDriverWait(self.browser,5).until(EC.presence_of_element_located((By.XPATH,'//a[@title="消息"]'))) ) except TimeoutException: return False
def move_to_gap(self,slider,track):
    """
    拖动滑块到缺口处
    :param slider:滑块
    :param track: 轨迹
    :return:
    """
    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_slider(self):
    """
    获取滑块
    :return: 滑块对象
    """
    '''
    
''' slider = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,'.geetest_slider_button'))) return slider 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 = 3 # 加速度为+3 else: a = -3 # 加速度为-3 # 初速度v0 v0 = v # 当前速度 v = v0 + a * t # 移动距离 move = v0 * t + 1 / 2 * a * t * t # 当前位移 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 = 60 # 彩色图 每个位置的像素点有三个通道 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 = 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'))) time.sleep(2) im.screenshot('captcha.png') # 执行 JS 代码并拿到图片 base64 数据 JS = 'return document.getElementsByClassName("geetest_canvas_fullbg")[0].toDataURL("image/png");' # 不带阴影的完整图片 im_info = self.browser.execute_script(JS) # 执行js文件得到带图片信息的图片数据 print(im_info) # 拿到base64编码的图片信息 im_base64 = im_info.split(',')[1] # 转为bytes类型 captcha1 = base64.b64decode(im_base64) # 将图片保存在本地 with open('captcha1.png', 'wb') as f: f.write(captcha1) # 执行 JS 代码并拿到图片 base64 数据 JS = 'return document.getElementsByClassName("geetest_canvas_bg")[0].toDataURL("image/png");' # 带阴影的图片 im_info = self.browser.execute_script(JS) # 执行js文件得到带图片信息的图片数据 print(im_info) # 拿到base64编码的图片信息 im_base64 = im_info.split(',')[1] # 转为bytes类型 captcha2 = base64.b64decode(im_base64) # 将图片保存在本地 with open('captcha2.png', 'wb') as f: f.write(captcha2) captcha1 = Image.open('captcha1.png') captcha2 = Image.open('captcha2.png') return captcha1, captcha2 def get_login_btn(self): """ 登陆 :return: None """ '''
值有空格的 查找时写一半就好 要么前半段要么后半段 ''' #button = self.wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,'.btn-login'))) button = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'btn'))) return button def open(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) password.send_keys(self.password) def login(self): #输入用户名和密码 self.open() #点击登录按钮 button = self.get_login_btn() #找到登录按钮 button.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) #点按滑块 slider = self.get_slider() #按轨迹拖动滑块 self.move_to_gap(slider,track) if self.login_successfully(): print("登录成功") else: #可能不成功 再试一次 time.sleep(5) self.login()

if name == ‘main’:
login = loginBili()
login.login()

作者:CoreJT
来源:CSDN
原文:https://blog.csdn.net/sdu_hao/article/details/96714304
版权声明:本文为博主原创文章,转载请附上博文链接!

你可能感兴趣的:(Python爬虫实战)