日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長提供免費收錄網(wǎng)站服務,提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

前言

本文的文字及圖片來源于網(wǎng)絡,僅供學習、交流使用,不具有任何商業(yè)用途,版權(quán)歸原作者所有,如有問題請及時聯(lián)系我們以作處理。

作者:Mingyueyixi

PS:如有需要Python學習資料的小伙伴可以私信小編獲取

Python破解BiliBili滑塊驗證碼,完美避開人機識別

 

準備工作

  • B站登錄頁 https://passport.bilibili.com/login
  • python3
  • pip install selenium (webdriver框架)
  • pip install PIL (圖片處理)
  • chrome driver:http://chromedriver.storage.googleapis.com/index.html
  • firefox driver:https://github.com/mozilla/geckodriver/releases

B站的滑塊驗證碼如上。

這類驗證碼可以使用 selenium 操作瀏覽器拖拽滑塊來進行破解,難點兩個,一個如何確定拖拽到的位置,另一個是避開人機識別(反爬蟲)。

Python破解BiliBili滑塊驗證碼,完美避開人機識別

 

確定滑塊驗證碼需要拖拽的位移距離

有三種方式

  • 人工智能機器學習,確定滑塊位置
  • 通過完整圖片與缺失滑塊的圖片進行像素對比,確定滑塊位置
  • 邊緣檢測算法,確定位置

各有優(yōu)缺點。人工智能機器學習,確定滑塊位置,需要進行訓練,比較麻煩,也可以看是否存在在線api可以調(diào)用。以下介紹其他兩種方式。

對比完整圖片與缺失滑塊的圖片

僅介紹,本文不進行實現(xiàn)。對于B站來說,是準確率最高的方式(100%),但不能保證未來B站的滑塊驗證升級,導致不可用。

B站的滑塊驗證模塊,一共有三張圖片:完整圖、缺失滑塊圖、滑塊圖,都是由畫布繪制出的。類似于:

完整圖:

Python破解BiliBili滑塊驗證碼,完美避開人機識別

 

缺失滑塊圖:

Python破解BiliBili滑塊驗證碼,完美避開人機識別

 

滑塊圖:

Python破解BiliBili滑塊驗證碼,完美避開人機識別

 

HTML代碼類似于:

<div class="geetest_canvas_img geetest_absolute" style="display: block;">
<div class="geetest_slicebg geetest_absolute">
	<canvas class="geetest_canvas_bg geetest_absolute" height="160" width="260"></canvas>
	<canvas class="geetest_canvas_slice geetest_absolute" width="260" height="160"></canvas>
</div>
<canvas class="geetest_canvas_fullbg geetest_fade geetest_absolute" height="160" width="260" style="display: none;"></canvas>
</div>

只需要通過selenium獲取畫布元素,執(zhí)行js拿到畫布像素,遍歷完整圖和缺失滑塊圖的像素,一旦獲取到差異(需要允許少許像素誤差),像素矩陣x軸方向即是滑塊位置。

另外由于滑塊圖距離畫布坐標原點有距離,還需要減去這部分距離。

最后使用 selenium 拖拽即可。

邊緣檢測算法,確定位置

滑塊基本上是個方形,通過算法確定方形起始位置即可。

Python破解BiliBili滑塊驗證碼,完美避開人機識別

 

介紹兩種方式

  • 滑塊是方形的,存在垂直與水平的邊,該邊在缺失滑塊圖中基本都是灰黑的。遍歷像素找到基本都是灰黑的邊即可。
  • 缺失滑塊圖中滑塊位置是灰黑封閉的。通過算法可以找到封閉區(qū)域,大小與滑塊相近,即是滑塊需要拖拽到的位置。

第二種實現(xiàn)起來有些復雜,不進行實現(xiàn)了。

下面是第一種實現(xiàn)方式(只實現(xiàn)了垂直邊的檢測,水平邊檢測原理一致),會存在檢測不出或錯誤的情況,使用時需要換一張驗證碼。也可能存在檢測出的邊是另一條(因為B站的滑塊不是長方形,存在弧形邊),那么需要減去滑塊寬度

class VeriImageUtil():

    def __init__(self):
        self.defaultConfig = {
            "grayOffset": 20,
            "opaque": 1,
            "minVerticalLineCount": 30
        }
        self.config = copy.deepcopy(self.defaultConfig)

    def updateConfig(self, config):
        # temp = copy.deepcopy(config)
        for k in self.config:
            if k in config.keys():
                self.config[k] = config[k]

    def getMaxOffset(self, *args):
        # 計算偏移平均值最大的數(shù)
        av = sum(args) / len(args)

        maxOffset = 0
        for a in args:
            offset = abs(av - a)
            if offset > maxOffset:
                maxOffset = offset
        return maxOffset

    def isGrayPx(self, r, g, b):
        # 是否是灰度像素點,允許波動offset
        return self.getMaxOffset(r, g, b) < self.config["grayOffset"]

    def isDarkStyle(self, r, g, b):
        # 灰暗風格
        return r < 128 and g < 128 and b < 128

    def isOpaque(self, px):
        # 不透明
        return px[3] >= 255 * self.config["opaque"]

    def getVerticalLineOffsetX(self, bgImage):
        # bgImage = Image.open("./image/bg.png")
        # bgImage.im.mode = 'RGBA'
        bgBytes = bgImage.load()

        x = 0
        while x < bgImage.size[0]:
            y = 0
            # 點》》線,灰度線條數(shù)量
            verticalLineCount = 0

            while y < bgImage.size[1]:
                px = bgBytes[x, y]
                r = px[0]
                g = px[1]
                b = px[2]
                # alph = px[3]
                # print(px)
                if self.isDarkStyle(r, g, b) and self.isGrayPx(r, g, b) and self.isOpaque(px):
                    verticalLineCount += 1
                else:
                    verticalLineCount = 0
                    y += 1
                    continue

                if verticalLineCount >= self.config["minVerticalLineCount"]:
                    # 連續(xù)多個像素都是灰度像素,直線
                    # print(x, y)
                    return x

                y += 1

            x += 1
        pass


if __name__ == '__main__':
    bgImage = Image.open("./image/bg.png")
    veriImageUtil = VeriImageUtil()

    # veriImageUtil.updateConfig({
    #     "grayOffset": 20,
    #     "opaque": 0.6,
    #     "minVerticalLineCount": 10
    # })
        bgOffsetX = veriImageUtil.getVerticalLineOffsetX(bgImage)
    print("bgOffsetX:{} ".format(bgOffsetX))

使用selenium進行滑動驗證(會失敗)

首先,我們需要從html中獲取滑塊驗證的圖片,通過執(zhí)行js,將畫布像素轉(zhuǎn)為base64,然后python即可獲取,進行拖拽處理:

from selenium import webdriver
import time
import base64
from PIL import Image
from io import BytesIO
from selenium.webdriver.support.ui import WebDriverWait

def checkVeriImage(driver):    
    WebDriverWait(driver, 5).until(
        lambda driver: driver.find_element_by_css_selector('.geetest_canvas_bg.geetest_absolute'))
    time.sleep(1)
    im_info = driver.execute_script(
        'return document.getElementsByClassName("geetest_canvas_bg geetest_absolute")[0].toDataURL("image/png");')
    # 拿到base64編碼的圖片信息
    im_base64 = im_info.split(',')[1]
    # 轉(zhuǎn)為bytes類型
    im_bytes = base64.b64decode(im_base64)
    with open('./temp_bg.png', 'wb') as f:
        # 保存圖片到本地,方便查看預覽
        f.write(im_bytes)
        
    image_data = BytesIO(im_bytes)
    bgImage = Image.open(image_data)
    # 滑塊距離左邊有 5~10 像素左右誤差
    offsetX = VeriImageUtil().getVerticalLineOffsetX(bgImage)
    eleDrag = driver.find_element_by_css_selector(".geetest_slider_button")
    action_chains = webdriver.ActionChains(driver)
    action_chains.drag_and_drop_by_offset(eleDrag,offsetX-10,0).perform()

貌似可以了,但實際上,驗證時會遇到“拼圖被怪物吃掉了,請重試”,導致失敗。這是因為被檢測到機器人(爬蟲)操作了。

避開人機識別

B站滑塊驗證碼的人機識別,其實不咋滴,主要靠是否存在停留間隔來判斷。一開始被網(wǎng)上文章誤導,弄了什么距離=初速度乘以時間t + 1/2加速度乘以(時間平方)模擬拖拽,實際上是完全不對路的。

Python破解BiliBili滑塊驗證碼,完美避開人機識別

 

webdriver.ActionChains(driver).drag_and_drop_by_offset(eleDrag,offsetX-10,0).perform() 拖動滑塊會導致驗證失敗。在B站中,這是因為這個動作太快了的緣故。有的同學就打算直接加 time.sleep(1) 了,這么做是不會成功的,會提示拼圖被怪物吃掉了,請重試

實際上人做滑塊驗證的過程可以歸為:手指快速拖拽驗證碼到指定位置,修正誤差,停留一會兒,釋放滑塊。

簡單實現(xiàn)

代碼可以簡單實現(xiàn),都不需要模擬人修正拖拽誤差的過程,普通網(wǎng)站不會去統(tǒng)計這個,至少B站不會。

    def simpleSimulateDragX(self, source, targetOffsetX):
        """
        簡單拖拽模仿人的拖拽:快速沿著X軸拖動,直接一步到達正確位置,再暫停一會兒,然后釋放拖拽動作
        B站是依據(jù)是否有暫停時間來分辨人機的,這個方法適用。
        :param source: 
        :param targetOffsetX: 
        :return: None
        """
		#參考`drag_and_drop_by_offset(eleDrag,offsetX-10,0)`的實現(xiàn),使用move方法
        action_chains = webdriver.ActionChains(self.driver)
        # 點擊,準備拖拽
        action_chains.click_and_hold(source)
        action_chains.pause(0.2)
        action_chains.move_by_offset(targetOffsetX,0)
        action_chains.pause(0.6)
        action_chains.release()
        action_chains.perform()

添加修正過程的實現(xiàn)

其實也就最后一段多出了fix的過程,
action_chains.move_by_offset(10,0)

    def fixedSimulateDragX(self, source, targetOffsetX):
		#參考`drag_and_drop_by_offset(eleDrag,offsetX-10,0)`的實現(xiàn),使用move方法
        action_chains = webdriver.ActionChains(self.driver)
        # 點擊,準備拖拽
        action_chains.click_and_hold(source)
        action_chains.pause(0.2)
        action_chains.move_by_offset(targetOffsetX-10,0)
        action_chains.pause(0.6)
        action_chains.move_by_offset(10,0)
        action_chains.pause(0.6)
        action_chains.release()
        action_chains.perform()

終極版實現(xiàn)

為了更像人類操作,可以進行拖拽間隔時間和拖拽次數(shù)、距離的隨機化。雖然這對B站沒什么用,還可能會導致驗證時間變久一些。

拖拽多次,可以使用循環(huán)遍歷,不過代碼可能不好理解,直接判斷就行,最多也就兩到3次就完成修正誤差的過程。

    def __getRadomPauseScondes(self):
        """
        :return:隨機的拖動暫停時間
        """
        return random.uniform(0.6, 0.9)

    def simulateDragX(self, source, targetOffsetX):
        """
        模仿人的拖拽動作:快速沿著X軸拖動(存在誤差),再暫停,然后修正誤差
        防止被檢測為機器人,出現(xiàn)“圖片被怪物吃掉了”等驗證失敗的情況
        :param source:要拖拽的html元素
        :param targetOffsetX: 拖拽目標x軸距離
        :return: None
        """
        action_chains = webdriver.ActionChains(self.driver)
        # 點擊,準備拖拽
        action_chains.click_and_hold(source)
        # 拖動次數(shù),二到三次
        dragCount = random.randint(2, 3)
        if dragCount == 2:
            # 總誤差值
            sumOffsetx = random.randint(-15, 15)
            action_chains.move_by_offset(targetOffsetX + sumOffsetx, 0)
            # 暫停一會
            action_chains.pause(self.__getRadomPauseScondes())
            # 修正誤差,防止被檢測為機器人,出現(xiàn)圖片被怪物吃掉了等驗證失敗的情況
            action_chains.move_by_offset(-sumOffsetx, 0)
        elif dragCount == 3:
            # 總誤差值
            sumOffsetx = random.randint(-15, 15)
            action_chains.move_by_offset(targetOffsetX + sumOffsetx, 0)
            # 暫停一會
            action_chains.pause(self.__getRadomPauseScondes())

            # 已修正誤差的和
            fixedOffsetX = 0
            # 第一次修正誤差
            if sumOffsetx < 0:
                offsetx = random.randint(sumOffsetx, 0)
            else:
                offsetx = random.randint(0, sumOffsetx)

            fixedOffsetX = fixedOffsetX + offsetx
            action_chains.move_by_offset(-offsetx, 0)
            action_chains.pause(self.__getRadomPauseScondes())

            # 最后一次修正誤差
            action_chains.move_by_offset(-sumOffsetx + fixedOffsetX, 0)
            action_chains.pause(self.__getRadomPauseScondes())

        else:
            raise Exception("莫不是系統(tǒng)出現(xiàn)了問題?!")

        # 參考action_chains.drag_and_drop_by_offset()
        action_chains.release()
        action_chains.perform()

終章(完整代碼)

Python破解BiliBili滑塊驗證碼,完美避開人機識別

 

本文完整示例代碼如下

# -*- coding: utf-8 -*-
# @Date:2020/2/15 2:09
# @Author: Lu
# @Description bilibili滑塊驗證碼識別。B站有反爬限制,過快地拖拽會提示“怪物吃了拼圖,請重試”。
# 目前B站有三張圖片,只要對比完整圖和缺失滑塊背景圖的像素,就可以得到偏移圖片y軸距離,減去滑塊空白距離=需要滑動的像素距離
# 這里采用邊緣檢測,檢測缺失滑塊的底圖是否存在一條灰色豎線,即認為是滑塊目標位置,存在失敗的概率,適用范圍應該更大些。


from selenium import webdriver
import time
import base64
from PIL import Image
from io import BytesIO
from selenium.webdriver.support.ui import WebDriverWait
import random
import copy


class VeriImageUtil():

    def __init__(self):
        self.defaultConfig = {
            "grayOffset": 20,
            "opaque": 1,
            "minVerticalLineCount": 30
        }
        self.config = copy.deepcopy(self.defaultConfig)

    def updateConfig(self, config):
        # temp = copy.deepcopy(config)
        for k in self.config:
            if k in config.keys():
                self.config[k] = config[k]

    def getMaxOffset(self, *args):
        # 計算偏移平均值最大的數(shù)
        av = sum(args) / len(args)

        maxOffset = 0
        for a in args:
            offset = abs(av - a)
            if offset > maxOffset:
                maxOffset = offset
        return maxOffset

    def isGrayPx(self, r, g, b):
        # 是否是灰度像素點,允許波動offset
        return self.getMaxOffset(r, g, b) < self.config["grayOffset"]

    def isDarkStyle(self, r, g, b):
        # 灰暗風格
        return r < 128 and g < 128 and b < 128

    def isOpaque(self, px):
        # 不透明
        return px[3] >= 255 * self.config["opaque"]

    def getVerticalLineOffsetX(self, bgImage):
        # bgImage = Image.open("./image/bg.png")
        # bgImage.im.mode = 'RGBA'
        bgBytes = bgImage.load()

        x = 0
        while x < bgImage.size[0]:
            y = 0
            # 點》》線,灰度線條數(shù)量
            verticalLineCount = 0

            while y < bgImage.size[1]:
                px = bgBytes[x, y]
                r = px[0]
                g = px[1]
                b = px[2]
                # alph = px[3]
                # print(px)
                if self.isDarkStyle(r, g, b) and self.isGrayPx(r, g, b) and self.isOpaque(px):
                    verticalLineCount += 1
                else:
                    verticalLineCount = 0
                    y += 1
                    continue

                if verticalLineCount >= self.config["minVerticalLineCount"]:
                    # 連續(xù)多個像素都是灰度像素,直線,認為需要滑動這么多
                    # print(x, y)
                    return x

                y += 1

            x += 1
        pass


class DragUtil():
    def __init__(self, driver):
        self.driver = driver

    def __getRadomPauseScondes(self):
        """
        :return:隨機的拖動暫停時間
        """
        return random.uniform(0.6, 0.9)

    def simulateDragX(self, source, targetOffsetX):
        """
        模仿人的拖拽動作:快速沿著X軸拖動(存在誤差),再暫停,然后修正誤差
        防止被檢測為機器人,出現(xiàn)“圖片被怪物吃掉了”等驗證失敗的情況
        :param source:要拖拽的html元素
        :param targetOffsetX: 拖拽目標x軸距離
        :return: None
        """
        action_chains = webdriver.ActionChains(self.driver)
        # 點擊,準備拖拽
        action_chains.click_and_hold(source)
        # 拖動次數(shù),二到三次
        dragCount = random.randint(2, 3)
        if dragCount == 2:
            # 總誤差值
            sumOffsetx = random.randint(-15, 15)
            action_chains.move_by_offset(targetOffsetX + sumOffsetx, 0)
            # 暫停一會
            action_chains.pause(self.__getRadomPauseScondes())
            # 修正誤差,防止被檢測為機器人,出現(xiàn)圖片被怪物吃掉了等驗證失敗的情況
            action_chains.move_by_offset(-sumOffsetx, 0)
        elif dragCount == 3:
            # 總誤差值
            sumOffsetx = random.randint(-15, 15)
            action_chains.move_by_offset(targetOffsetX + sumOffsetx, 0)
            # 暫停一會
            action_chains.pause(self.__getRadomPauseScondes())

            # 已修正誤差的和
            fixedOffsetX = 0
            # 第一次修正誤差
            if sumOffsetx < 0:
                offsetx = random.randint(sumOffsetx, 0)
            else:
                offsetx = random.randint(0, sumOffsetx)

            fixedOffsetX = fixedOffsetX + offsetx
            action_chains.move_by_offset(-offsetx, 0)
            action_chains.pause(self.__getRadomPauseScondes())

            # 最后一次修正誤差
            action_chains.move_by_offset(-sumOffsetx + fixedOffsetX, 0)
            action_chains.pause(self.__getRadomPauseScondes())


        else:
            raise Exception("莫不是系統(tǒng)出現(xiàn)了問題?!")

        # 參考action_chains.drag_and_drop_by_offset()
        action_chains.release()
        action_chains.perform()

    def simpleSimulateDragX(self, source, targetOffsetX):
        """
        簡單拖拽模仿人的拖拽:快速沿著X軸拖動,直接一步到達正確位置,再暫停一會兒,然后釋放拖拽動作
        B站是依據(jù)是否有暫停時間來分辨人機的,這個方法適用。
        :param source: 
        :param targetOffsetX: 
        :return: None
        """

        action_chains = webdriver.ActionChains(self.driver)
        # 點擊,準備拖拽
        action_chains.click_and_hold(source)
        action_chains.pause(0.2)
        action_chains.move_by_offset(targetOffsetX, 0)
        action_chains.pause(0.6)
        action_chains.release()
        action_chains.perform()

def checkVeriImage(driver):
    WebDriverWait(driver, 5).until(
        lambda driver: driver.find_element_by_css_selector('.geetest_canvas_bg.geetest_absolute'))
    time.sleep(1)
    im_info = driver.execute_script(
        'return document.getElementsByClassName("geetest_canvas_bg geetest_absolute")[0].toDataURL("image/png");')
    # 拿到base64編碼的圖片信息
    im_base64 = im_info.split(',')[1]
    # 轉(zhuǎn)為bytes類型
    im_bytes = base64.b64decode(im_base64)
    with open('./temp_bg.png', 'wb') as f:
        # 保存圖片到本地
        f.write(im_bytes)

    image_data = BytesIO(im_bytes)
    bgImage = Image.open(image_data)
    # 滑塊距離左邊有 5 像素左右誤差
    offsetX = VeriImageUtil().getVerticalLineOffsetX(bgImage)
    print("offsetX: {}".format(offsetX))
    if not type(offsetX) == int:
        # 計算不出,重新加載
        driver.find_element_by_css_selector(".geetest_refresh_1").click()
        checkVeriImage(driver)
        return
    elif offsetX == 0:
        # 計算不出,重新加載
        driver.find_element_by_css_selector(".geetest_refresh_1").click()
        checkVeriImage(driver)
        return
    else:
        dragVeriImage(driver, offsetX)


def dragVeriImage(driver, offsetX):
    # 可能產(chǎn)生檢測到右邊緣的情況
    # 拖拽
    eleDrag = driver.find_element_by_css_selector(".geetest_slider_button")
    dragUtil = DragUtil(driver)
    dragUtil.simulateDragX(eleDrag, offsetX - 10)
    time.sleep(2.5)

    if isNeedCheckVeriImage(driver):
        checkVeriImage(driver)
        return
    dragUtil.simulateDragX(eleDrag, offsetX - 6)

    time.sleep(2.5)
    if isNeedCheckVeriImage(driver):
        checkVeriImage(driver)
        return
    # 滑塊寬度40左右
    dragUtil.simulateDragX(eleDrag, offsetX - 56)

    time.sleep(2.5)
    if isNeedCheckVeriImage(driver):
        checkVeriImage(driver)
        return
    dragUtil.simulateDragX(eleDrag, offsetX - 52)

    if isNeedCheckVeriImage(driver):
        checkVeriImage(driver)
        return


def isNeedCheckVeriImage(driver):
    if driver.find_element_by_css_selector(".geetest_panel_error").is_displayed():
        driver.find_element_by_css_selector(".geetest_panel_error_content").click();
        return True
    return False


def task():
    # 此步驟很重要,設置chrome為開發(fā)者模式,防止被各大網(wǎng)站識別出來使用了Selenium
    # options = webdriver.ChromeOptions()
    # options.add_experimental_option('excludeSwitches', ['enable-automation'])

    options = webdriver.FirefoxOptions()

    # driver = webdriver.Firefox(executable_path=r"../../../res/webdriver/geckodriver_x64_0.26.0.exe",options=options)
    driver = webdriver.Firefox(executable_path=r"../../../res/webdriver/geckodriver_x64_0.26.0.exe",options=options)

    driver.get('https://passport.bilibili.com/login')
    time.sleep(3)

    driver.find_element_by_css_selector("#login-username").send_keys("1234567")
    driver.find_element_by_css_selector("#login-passwd").send_keys("abcdefg")
    driver.find_element_by_css_selector(".btn.btn-login").click()
    time.sleep(2)
    checkVeriImage(driver)

    pass


#   該方法用來確認元素是否存在,如果存在返回flag=true,否則返回false
def isElementExist(driver, css):
    try:
        driver.find_element_by_css_selector(css)
        return True
    except:
        return False


if __name__ == '__main__':
    task()

分享到:
標簽:驗證碼 滑塊 Python
用戶無頭像

網(wǎng)友整理

注冊時間:

網(wǎng)站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨大挑戰(zhàn)2018-06-03

數(shù)獨一種數(shù)學游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數(shù)有氧達人2018-06-03

記錄運動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定