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属性来找
    '''
    <input type="text" value="" placeholder="你的手机号/邮箱" id="login-username" maxlength="50" autocomplete="off" class="">
    '''
    username = self.wait.until(EC.presence_of_element_located((By.ID,'login-username')))
    #找到密码输入框
    '''
    <input type="password" placeholder="密码" id="login-passwd" class="">
    '''
    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: 图片对象
    """
    '''
    <canvas class="geetest_canvas_bg geetest_absolute" height="160" width="260"></canvas>
    '''
    # 带阴影的图片
    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:
        '''
       <a data-v-4d9bc88b="" href="//message.bilibili.com" target="_blank" title="消息" class="t"><div data-v-4d9bc88b="" class="num">3</div> <!---->
消息
''' #登录成功后 界面上会有一个消息按钮 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:
        '''
       <a data-v-4d9bc88b="" href="//message.bilibili.com" target="_blank" title="消息" class="t"><div data-v-4d9bc88b="" class="num">3</div> <!---->
消息
''' #登录成功后 界面上会有一个消息按钮 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: 滑块对象
    """
    '''
    <div class="geetest_slider_button" style="opacity: 1; transform: translate(0px, 0px);"></div>
    '''
    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: 图片对象
    """
    '''
    <canvas class="geetest_canvas_bg geetest_absolute" height="160" width="260"></canvas>
    '''
    # 带阴影的图片
    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
    """
    '''
    <a class="btn btn-login">登录</a>
    值有空格的 查找时写一半就好 要么前半段要么后半段
    '''
    #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属性来找
    '''
    <input type="text" value="" placeholder="你的手机号/邮箱" id="login-username" maxlength="50" autocomplete="off" class="">
    '''
    username = self.wait.until(EC.presence_of_element_located((By.ID,'login-username')))
    #找到密码输入框
    '''
    <input type="password" placeholder="密码" id="login-passwd" class="">
    '''
    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
来源:优快云
原文:https://blog.youkuaiyun.com/sdu_hao/article/details/96714304
版权声明:本文为博主原创文章,转载请附上博文链接!

内容概要:本文深入解析了扣子COZE AI编程及其详细应用代码案例,旨在帮助读者理解新一代低门槛智能体开发范式。文章从五个维度展开:关键概念、核心技巧、典型应用场景、详细代码案例分析以及未来发展趋势。首先介绍了扣子COZE的核心概念,如Bot、Workflow、Plugin、Memory和Knowledge。接着分享了意图识别、函数调用链、动态Prompt、渐进式发布及监控可观测等核心技巧。然后列举了企业内部智能客服、电商导购助手、教育领域AI助教和金融行业合规质检等应用场景。最后,通过构建“会议纪要智能助手”的详细代码案例,展示了从需求描述、技术方案、Workflow节点拆解到调试与上线的全过程,并展望了多智能体协作、本地私有部署、Agent2Agent协议、边缘计算插件和实时RAG等未来发展方向。; 适合人群:对AI编程感兴趣的开发者,尤其是希望快速落地AI产品的技术人员。; 使用场景及目标:①学习如何使用扣子COZE构建生产级智能体;②掌握智能体实例、自动化流程、扩展能力和知识库的使用方法;③通过实际案例理解如何实现会议纪要智能助手的功能,包括触发器设置、下载节点、LLM节点Prompt设计、Code节点处理和邮件节点配置。; 阅读建议:本文不仅提供了理论知识,还包含了详细的代码案例,建议读者结合实际业务需求进行实践,逐步掌握扣子COZE的各项功能,并关注其未来的发展趋势。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值