驗證碼識別

寫在前面

如今,不少網站採起各類各樣的措施來反爬蟲,其中之一就是使用驗證碼。當咱們訪問網頁時,必須先經過驗證碼纔可以訪問頁面。下面咱們便來說2種驗證碼的識別方式和一些思路。固然咱們也能夠直接使用付費的打碼平臺,那樣能夠增長識別的準確度,畢竟出了錢的嘛。哈哈!javascript

PIL庫

其實,驗證碼識別歸根到底仍是對各類各樣圖片的識別和操做,python中有很對圖像處理的庫,其中PIL就是其中之一。 因此在處理驗證碼識別以前,必須先了解PIL庫和tesserocr。 下面附上其API源碼地址,以及對應的學習博客。
源碼地址:https://pillow-cn.readthedocs.io/zh_CN/latest/reference/index.html
參考博客:https://blog.csdn.net/louishao/article/details/69879981
下面咱們就開始驗證碼識別之路了。html

圖形驗證碼

以中國知網爲例:
圖片描述java

首先,咱們先拿到上圖中綠線標記的驗證碼,下載到本地項目文件中,
而後,編寫以下代碼:python

import tesserocr
from PIL import Image

image = Image.open('image.png')
res = tesserocr.image_to_text(image)
print(res)  # F8BS

輸出結果爲:F8BS, 但是實際圖片爲F8B8,這是由於驗證碼內多餘線條幹擾了圖片的識別,像這類狀況,還須要作出額外的處理,好比轉灰度,二值化等。固然,實際處理中並非這樣,通常咱們會先對模糊圖片進行灰度處理後,再設定二值化的閾值,實際處理以下:git

import tesserocr
from PIL import Image

image = Image.open('code.jpg')  # 建立image對象

image = image.convert('L')
threshold = 150  # 指定二值化閾值
table = []
for i in range(256):
    if i < threshold:
        table.append(0)
    else:
        table.append(1)

image = image.point(table, '1')
image.show()
res = tesserocr.image_to_text(image)
print(res)

輸出結果:F8B8
進行識別時,先設定好二值化閾值threshold,進行適當調試,直到圖片能正常識別爲止。github

滑動驗證碼

過程分析:chrome

滑動驗證碼主要的驗證方式是拖動滑塊,拼合圖像;如圖象徹底拼合,則驗證成功,即表單提交成功,不然須要從新驗證。
如圖:
圖片描述canvas

下面,咱們就以極驗的驗證碼爲例,來說訴一下識別方法。
由於極驗的驗證碼在拖動驗證碼後會生成一個加密的表單提交到後臺,全部爲了不麻煩咱們直接用selenium模擬瀏覽器行爲來完成驗證。
登錄網站:極驗官網瀏覽器

目標站點: https://account.geetest.com/l...

圖片描述

首先,咱們發現登錄界面有個智能按鈕,通常來講,在輸入郵箱以後,點擊按鈕就會彈出滑動驗證窗口,而後咱們在拖動驗證碼完成圖像拼接,完成驗證。
圖片描述app

因此,滑塊驗證識別須要完成如下步驟:

  1. 模擬點擊驗證按鈕
  2. 識別滑塊的缺口位置
  3. 模擬拖動滑塊

如何實現以上步驟呢?咱們先須要將任務進行分解,看似只有三大步驟,其實裏面坑還有不少的,稍後會作解釋。

第一步,輸入帳號,獲取智能按鈕,使用selenium模擬點擊,獲取帶有缺口的圖片。

第二步,獲取上面缺口圖片中的完整圖片。這裏有個地方要注意,正常狀況下咱們在網頁源代碼裏是找不到完整圖的,由於它被隱藏了,必須執行javascript語句才能出現完整圖。

圖片描述

咱們將display參數改成block,opacity參數改成1,而後進行截圖,就能夠拿到完整的驗證碼圖片了。

第三步,對比兩張圖片的全部RGB像素點,獲得缺口位置。

第四步,模擬人的拖動習慣,這裏也有坑,極驗的驗證碼增長了機器軌跡識別,勻速移動,或者隨機速度移動滑塊都不能經過驗證,因此咱們將須要拖動的總位移分紅一段一段小的軌跡,先勻加速拖動後勻減速拖動。

第五步,按照規定軌跡進行拖動,完成驗證。

第六步,完成帳號登錄。

過程分析完了,下面咱們就來寫代碼試一下:
首先,咱們先將整個代碼的一個邏輯思路作一個大體的歸納吧。

def main():
    """主函數"""

    # 獲取帶缺口驗證碼圖片image1, 傳入的參數後綴爲: .png
    image1 = get_unFull_captcha('unfull_captcha.png')
    # print(image1.load()[12,25])
    # 獲取完整驗證碼圖片image2
    image2 = get_full_captcha('full_captcha.png')
    # 對比上述圖片像素點,獲取缺口位置,獲得偏移距離
    distance = get_quekou_distance(image1, image2)
    print('缺口偏移量:', distance)
    # 獲取滑塊的移動軌跡
    track = get_track(distance)
    # 模擬人的行爲,拖動滑塊,完成驗證
    slider = get_slider()
    move(slider, track)
    success = wait.until(EC.text_to_be_present_in_element((By.CLASS_NAME, 'geetest_success_radar_tip_content'), '驗證成功'))
    print(success)
    if success:
        login()
    else:
        main()

接下來,咱們便來逐一完成main函數裏要實現的功能了。

代碼示例:
經過以上代碼咱們便拿到了完整的驗證碼和帶有缺口的驗證碼。
缺口圖片:

def get_unFull_captcha(name):
    """
    獲取帶缺口驗證碼圖片
    :return: unfull captcha
    """
    top, bottom, left, right = get_captcha_position('geetest_canvas_slice')
    print('驗證碼1位置:', top, bottom, left, right)
    screenshot = get_screenshot()
    unfull_captcha = screenshot.crop((left, top, right, bottom)) # 按圖片位置裁剪
    unfull_captcha.save(name)     # 這裏傳入的name要以xxx.png命名
    return unfull_captcha

完整圖片:

def get_full_captcha(name):
    """
    獲取完整驗證碼圖片
    :return: full_captcha
    """
    # 這裏要執行JavaScript腳本才能拿到完整圖片的截圖
    show_Full_img1= "document.getElementsByClassName('geetest_canvas_fullbg')[0].style.display='block'"
    browser.execute_script(show_Full_img1)
    show_Full_img2 = "document.getElementsByClassName('geetest_canvas_fullbg')[0].style.opacity=1"
    browser.execute_script(show_Full_img2)
    # 等待完整圖片加載
    time.sleep(2)
    top, bottom, left, right = get_captcha_position('geetest_canvas_fullbg')
    print('驗證碼2位置:', top, bottom, left, right)
    screenshot = get_screenshot()
    full_captcha = screenshot.crop((left, top, right, bottom))  # 同上
    full_captcha.save(name)
    return full_captcha

這裏我在調試的時候碰到一個坑,由於chrome中,location方法不滾動,直接返回相對整個html的座標,個人電腦是15.6寸的,顯示設置上佈局的縮放大小被放大到1.25倍,致使location返回的座標與驗證碼的座標有偏差。修改佈局爲100%後就解決了。 下面即是對比圖片找出缺口位置。這裏咱們須要遍歷圖片的座標點,獲取像素點的RGB數據。

代碼示例

def get_quekou_distance(image1, image2):
    """
    對比像素點,獲取缺口位置
    :param image1: 缺口圖片
    :param image2: 完整圖片
    :return: 缺口的偏移距離
    """
    # 缺口在滑塊右側,設定遍歷初始橫座標left爲59
    left = 60
    # 像素對比閾值
    threshold = 60

    for i in range(left, image2.size[0]):
        for j in range(image2.size[1]):
            rgb1 = image1.load()[i, j]
            rgb2 = image2.load()[i, j]

            res1 = abs(rgb2[0] - rgb1[0])
            res2 = abs(rgb2[1] - rgb1[1])
            res3 = abs(rgb2[2] - rgb1[2])
            if not (res1 < threshold and res2 < threshold and res3 < threshold):
                return i-7 # 返回缺口偏移距離,這裏需測試幾回

接下來就是獲取滑塊的移動路徑和模擬拖動行爲了。

執行代碼:

def get_track(distance):
    """
    獲取移動路徑
    :param distance: 偏移量
    :return: track:移動軌跡
    """
    # 存放移動軌跡
    track = []
    # 當前位置
    current = 0
    # 設定加速段和減速段臨界點爲路徑的3/4處
    mid = distance*4/5
    # 時間間隔time, 取0.2~0.3之間隨機數,避免被網站識別出來
    t = random.randint(2, 3)/10
    # 初速度
    v = 0

    while current < distance:
        if current < mid:
            # 勻加速移動,加速度a
            a = 2
        else:
            a = -3
        # 初速度
        v0 = v
        # 當前速度
        v = v0 + a*t
        # 移動距離
        s = v0*t + 1/2 * a * t*t
        # 當前位移
        current += s
        # 加入到移動軌跡
        track.append(round(s))
    return track

def move(slider, track):
    """
    模擬鼠標操做,點擊,移動滑塊按鈕
    :param: 滑塊
    :param: 軌跡
    :return:
    """
    ActionChains(browser).click_and_hold(slider).perform()
    # 操做鼠標按軌跡移動
    for x in track:
        ActionChains(browser).move_by_offset(xoffset=x, yoffset=0).perform()
    time.sleep(0.3)
    # 鬆開
    ActionChains(browser).release().perform()

最後終於成功了,踩了這麼多坑,終於完成了滑塊驗證碼的破解。。。如今已經實現功能,由於還可能出現其餘狀況,接下來咱們還須要完善一下代碼,其實也可封裝成一個類,測試的時候咱們會發現,圖片會彈出小怪獸被吃了,那是由於系統識別咱們是機器行爲,因此不經過,這裏咱們須要修改加速度參數,再增長一個回調。

這樣咱們就成功破解驗證碼,並登錄到網頁界面了。。。忙活了一上午,吃飯去了。
好像如今極驗官網改了, 可是滑動驗證碼思路基本上就是這樣的...

源碼地址:https://github.com/appleguardu/spider_projects/tree/master/Captcha

相關文章
相關標籤/搜索