Class 18 - 1 圖形驗證碼的識別

1、圖形驗證碼的識別php

  • 先將驗證碼的圖片保存到本。
    • 打開開發者工具,找到驗證碼元素。驗證碼元素是一張圖片,src 屬性是 CheckCode.aspx。打開連接 http://my.cnki.net/elibregister/CheckCode.aspx,保存並命名爲 code.jpg。
  1. 識別測試
    • 新建一個項目,將驗證碼圖片放到項目根目錄下,用 tesserocr 庫識別驗證碼,示例:
      import tesserocr
      from PIL import Image
      
      image = Image.open('code.jpg')
      result = tesserocr.image_to_text(image)
      print(result)

      這裏新建了一個 Image 對象,調用 tesserocr 的 image_to_ text()方法。傳入該 Image 對象 便可完成識別。css

    • tesserocr 還有一個更加簡單的方法,這個方法可直接將圖片文件轉爲字符串,代碼:html

      import tesserocr
      print(tesserocr.file_to_text('image.png'))

       此方法的識別效果不如上一種方法好。python

  2. 驗證碼處理web

    • 換一個驗證碼帶有多線條,命名爲 code2.jpg。從新識別和實際結果有誤差。算法

    • 對於有線條幹擾狀況,還須要作額外處理,如轉灰度、二值化等操做。 能夠利用 Image 對象的 convert()方法參數傳人 L,便可將圖片轉化爲灰度圖像,示例:json

      image = image.convert('L')
      image.show()

      傳入1 便可將圖片進行二值化處理:canvas

      image = image.convert('1')
      image.show()

      還能夠指定二值化的閾值,上面的方法採用的是默認閾(yù)值。以上方法採用默認闊值 127。不能直接轉化原圖,要將原圖先轉爲灰度圖像,再指定二值化闊值,代碼:api

      import tesserocr
      from PIL import Image
      
      image = Image.open('code.jpg')
      image = image.convert('L')
      threshold = 80
      table = []
      for i in range(256):
          if i < threshold:
              table.append(0)
          else:
              table.append(1)
      
      image = image.point(table,'1')
      image.show()

      變量 threshold 表明二值化閾值,閾值設置爲 80。原來驗證碼中的線條已經去除,整個驗證碼將會變得黑向分明。這時再從新識別驗證碼,代碼:瀏覽器

      import tesserocr
      from PIL import Image
      
      image = Image.open('code2.jpg')
      
      image = image.convert('L')
      threshold = 127
      table = []
      for i in range(256):
          if i < threshold:
              table.append(0)
          else:
              table.append(1)
      image = image.point(table,'1')
      result = tesserocr.image_to_text(image)
      print(result)

      針對一些有干擾的圖片,作一些灰度和二值化處理,會提升圖片識別的正確率。

    • 閾值:繼續遍歷圖片。若是圖片灰度值大於閾值說明是背景圖片,將之顏色設置爲白色,不然是文字,顏色設置爲黑色
    • 大概邏輯:

      • (計次循環首(圖片.取寬度(),x)
        計次循環首(圖片.取高度(),y)
        ‘遍歷每個像素點
        ‘取出遍歷到的當前像素點顏色
        顏色值=到字節集(圖片.取某點顏色值(x-1,y-1))
        ‘計算灰度值
        灰度值=(顏色值[1]+顏色值[2]+顏色值[3])/3
        ‘將灰度值放回圖片
        連續賦值(灰度值,顏色值[1],顏色值[2],顏色值[3])
        圖片.寫某點顏色值(x-1,y-1,灰度值)
        計次循環尾()
        計次循環尾()
        圖片.取圖片數據()      

2、極驗滑動驗證碼的識別

    1. 識別思路
      • 首先找到一個帶有極驗驗證的網站,如極驗官方後臺,連接:https://account.geetest.com/login。
      • 可使用Selenium來徹底模擬人的行爲的方式來完成驗證。(通常,驗證須要三步:)

        1. 模擬點擊驗證按鈕: 能夠直接用Selenium 模擬點擊按鈕
        2. 識別滑動缺口的位置: 
        3. 模擬拖動滑塊
          1. 識別滑動缺口的位置的操做比較關鍵,須要用到圖像相關處理方法
            • 首先觀察圖像:缺口的四周邊緣有明顯的斷裂邊緣,邊緣和邊緣周圍有明顯的區別。能夠實現一個邊緣檢測算法來找出缺口的位置。對於極驗驗證碼來講,能夠利用和原圖對比檢測的方式來識別缺口的位置,由於在沒有滑動滑塊以前, 缺口並無呈現。
            • 能夠同時獲取兩張圖片。設定一個對比閾值,而後遍歷兩張圖片,找出相同位置像素 RGB 差距超過此閾值的像素點,那麼此像素點的位置就是缺口的位置。

          2. 模擬拖動滑塊中須要走注意:極驗驗證碼增長了機器軌跡識別,勻速移動、 隨機速度移動等方法都不能經過驗證,只有徹底模擬人的移動軌跡才能夠經過驗證。 人的移動軌跡通常是先加速後減速,須要模擬這個過程才能成功。
    2. 初始化
      • 選定連接:https://account.geetest.com/login,極驗的管理後臺登陸頁面。首先初始化一些配置,如 Selenium 對象的初始化及一些參數的配置,示例:
        from selenium import webdriver
        from selenium.webdriver.support.wait import WebDriverWait
        
        EMAIL = 'test@test.com'
        PASSWORD = '123456'
        
        class CrackGeetest():
            def __init__(self):
                self.url = 'https://account.geetest.com/login'
                self.browser = webdriver.Chrome()
                self.wait = WebDriverWait(self.browser,20)
                self.email = EMAIL
                self.password = PASSWORD

        其中, EMAIL 和 PASSWORD 就是登陸極驗須要的用戶名和密碼,若是沒有需先註冊。

    3. 模擬點擊

      • 實現第一步的操做,也就是模擬點擊初始的驗證按鈕

        • 定義一個方法來獲取這個按鈕,利用顯式等待的方法來實現,示例:

          def get_geetest_button(self):
              "獲取初始驗證按鈕:return:按鈕對象"
              button = self.wait.unil(EC.element_to_be_clickable((By.CLASS_NAME,'geetest_radar_tip')))
              return button

          獲取一個WebElement對象,調用它的click()方法便可模擬點擊,示例:

          #點擊驗證按鈕
          button = get_geetest_button()
          button.click()

          完成第一步模擬點擊驗證按鈕

    4. 識別缺口

      • 識別缺口的位置。

        • 首先獲取先後兩張比對圖片,兩者不一致的地方即爲缺口 。獲取不帶缺口圖片,利用 Selenium選取圖片元素,獲得其所在位置和寬高,而後獲取整個網頁的截圖,圖片裁切出來便可,代碼:

          def get_position(self):
              """獲取驗證碼位置:return:驗證碼位置元組"""
              img = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME,'geetest_canvas_img')))
              time.sleep(2)
              location =img.location
              size = img.size
              top, bottom, left, right = location['y'],location['y']+size['weight'],location['x'],location['x']+size['width']
              return(top, bottom, left, right)
          
          def get_geetest_image(self,name='captcha.png'):
              """獲取驗證碼圖片:return:圖片對象"""
              top,bottom,left,right = self.get_position()
              print('驗證碼位置', top, bottom, left, right)
              screenshot = self.get_screenshot()
              captcha = screenshot.crop((left, top, right, bottom))
              return captcha

          這裏 get_position()函數首先獲取圖片對象,獲取它的位置和寬高,隨後返回其左上角和右下角的座標 get_geetest_image()方法獲取網頁截圖,調用 crop()方法將圖片裁切出來,返回的是 Image 對象

        • 接下來須要獲取第二張圖片,也就是帶缺口的圖片。要使得圖片出現缺口,只須要點擊下方的滑塊便可。這個動做觸發以後,圖片中的缺口就會顯現,代碼:
          def get_slider(self):
              """獲取滑塊:return:滑塊對象"""
              slider = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME,'greetest_slider_button')))
              return slider

          利用 get_slider()方法獲取滑塊對象,調用 click()方法便可觸發點擊,缺口圖片便可呈現,代碼:

          #點按呼出缺口
          slider = self.get_slider()
          slider.click()

          調用 get_geetest_image()方法將第二張圖片獲取下來便可。

        • 如今已經獲得兩張圖片對象,分別賦值給變量 image1 和 image2。再對比圖片獲取缺口。這裏遍歷圖片的每一個座標點,獲取兩張圖片對應像素點的 RGB 數據。若是兩者 RGB 數據差距距在必定範圍內,那就表明兩個像素相同,繼續比對下一個像素點 若是差距超過必定範圍,則表明像素點不一樣,當前位置即爲缺口位置,代碼:
          def is_pixel_equal(self, image1, image2, x, y):
              """判斷兩個像素是否相同
               :param image1:圖片1
               :param image1:圖片1
               :param x:位置x
               :param y:位置y
               :return:像素是否相同"""
              #取兩個圖片的像素點
              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 = 60
              for i in  range(left,image1.size[0]):
                  for j in range(image1.size[1]):
                      if not self.is_pixel_equal(image1, image2, i, j):
                          left = i
                          return left
              return left

          get_gap()方法即獲取缺口位置的方法。此方法的參數是兩張圖片, 一張爲帶缺口圖片,另外一張爲不帶缺口圖片。遍歷兩張圖片的每一個像素,利用 is_pixel_equal()方法判斷兩張圖片同一位置的 像素是杏相同。比較兩張圖 RGB 的絕對值是否均小於定義的閾值 threshold。若是絕對值均在闊值之 內,則表明像素點相同,繼續遍歷。不然表明不相同的像素點,即缺口的位置。

        • 滑塊的位置會出如今左邊位置,缺口會出如今與滑塊同一水平線的位置,因此缺口通常會在滑塊的右側。若是要尋找缺口,直接從滑塊右側尋找便可。直接設置遍歷的起始橫座標爲 60,也就是從滑塊的右側開始識別,這樣識別出的結果就是缺口的位置。
    5. 模擬拖動
      • 徹底模擬加速減速的過程也就是人拖動滑塊的操做經過驗證。前段滑塊作勻加速運動,後段滑塊作勻減速運動,利用加速度公式便可完成驗證。
        • 滑塊滑動的加速度用 a 表示, 當前速度用 v 表示,初速用 v0 表示,位移用 x 表示,所需時間用t 表示,知足關係:
          • x = v0*t + 0.5*a*t*t
          • v = v0 + a*t
        • 利用這兩個公式能夠構造軌跡移動算法,計算出先加速後減速的運動軌跡,代碼:
          def get_track(self, distance):
              """根據偏移量獲取移動軌跡
              :param distance:偏移量
              :return: 移動軌跡"""
              #移動軌跡
              track = []
              #當前位移
              current = 0
              #減速閾值
              mid = distance * 4 / 5
              #計算間隔
              t = 0.2
              #初速度
              v = 0
              
              while current < distance:
                  if current < mid:
                      a = 2
                  else:
                      a = -3
                  #初速度 V0
                  v0 = v
                  v = v0 + a*t
                  move = v0 *t +1/2 *a *t *t
                  current += move
                  #加入軌跡
                  track.append(round(move))
              return track

          定義 get_ rack()方法,傳人的參數爲移動的總距離,返回的是運動軌跡。運動軌跡用 track 表示,是一個列表,列表的每一個元素表明每次移動多少距離

          • 定義變量 mid,即減速的闊值,也就是加速到什麼位置開始減速。這裏 mid 值爲 4/5,即模擬前 4/5 路程是加速過程,後 1/5 路程是減速過程。

          • 接着定義當前位移的距離變量 current,初始爲 0,而後進入 while 循環,循環的條件是當前位移小於總距離。在循環裏咱們分段定義了加速度,其中加速過程的加速度定義爲 2,減速過程的加速度定義爲 -3。以後套用位移公式計算出某個時間段內的位移,將當前位移更新並記錄到軌跡裏便可。
          • 直到運動軌跡達到總距離時,循環終止。最後獲得的 track 記錄了每一個時間間隔移動了多少位移, 這樣滑塊的運動軌跡就獲得了。
          • 最後按照該運動軌跡拖動滑塊便可,方法實現代碼:
            def move_to_gap(self, slider,tracks):
                """拖動滑塊到缺口處
                :params slider:滑塊
                :params tracks:軌跡
                :return:"""
                ActionChains(self,browser).click_and_hold(slider).perform()
                for x in tracks:
                    ActionChains(self,browser).move_by_offset(xoffset=x,yoffset=0).perform()
                time.sleep(0.5)
                ActionChains(self.browser).release().perform()

            傳人的參數爲滑塊對象和運動軌跡。首先調用 ActionChains 的 click_and_ hold()方法按住拖動底部滑塊,遍歷運動軌跡獲取每小段位移距離,調用 move_by_offset()方法移動此位移,最後調用 release()方法鬆開鼠標便可。

    6. 完整代碼(需適當修改參數):

      import time
      from io import BytesIO
      from PIL import Image
      from selenium import webdriver
      from selenium.webdriver import ActionChains
      from selenium.webdriver.common.by import By
      from selenium.webdriver.support.ui import WebDriverWait
      from selenium.webdriver.support import expected_conditions as EC
      
      EMAIL = 'cqc@cuiqingcai.com'
      PASSWORD = ''
      BORDER = 6
      INIT_LEFT = 60
      
      
      class CrackGeetest():
          def __init__(self):
              self.url = 'https://account.geetest.com/login'
              self.browser = webdriver.Chrome()
              self.wait = WebDriverWait(self.browser, 20)
              self.email = EMAIL
              self.password = PASSWORD
          
          def __del__(self):
              self.browser.close()
          
          def get_geetest_button(self):
              """
              獲取初始驗證按鈕
              :return:
              """
              button = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'geetest_radar_tip')))
              return button
          
          def get_position(self):
              """
              獲取驗證碼位置
              :return: 驗證碼位置元組
              """
              img = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'geetest_canvas_img')))
              time.sleep(2)
              location = img.location
              size = img.size
              top, bottom, left, right = location['y'], location['y'] + size['height'], location['x'], location['x'] + size[
                  'width']
              return (top, bottom, left, right)
          
          def get_screenshot(self):
              """
              獲取網頁截圖
              :return: 截圖對象
              """
              screenshot = self.browser.get_screenshot_as_png()
              screenshot = Image.open(BytesIO(screenshot))
              return screenshot
          
          def get_slider(self):
              """
              獲取滑塊
              :return: 滑塊對象
              """
              slider = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'geetest_slider_button')))
              return slider
          
          def get_geetest_image(self, name='captcha.png'):
              """
              獲取驗證碼圖片
              :return: 圖片對象
              """
              top, bottom, left, right = self.get_position()
              print('驗證碼位置', top, bottom, left, right)
              screenshot = self.get_screenshot()
              captcha = screenshot.crop((left, top, right, bottom))
              captcha.save(name)
              return captcha
          
          def open(self):
              """
              打開網頁輸入用戶名密碼
              :return: None
              """
              self.browser.get(self.url)
              email = self.wait.until(EC.presence_of_element_located((By.ID, 'email')))
              password = self.wait.until(EC.presence_of_element_located((By.ID, 'password')))
              email.send_keys(self.email)
              password.send_keys(self.password)
          
          def get_gap(self, image1, image2):
              """
              獲取缺口偏移量
              :param image1: 不帶缺口圖片
              :param image2: 帶缺口圖片
              :return:
              """
              left = 60
              for i in range(left, image1.size[0]):
                  for j in range(image1.size[1]):
                      if not self.is_pixel_equal(image1, image2, i, j):
                          left = i
                          return left
              return left
          
          def is_pixel_equal(self, image1, image2, x, y):
              """
              判斷兩個像素是否相同
              :param image1: 圖片1
              :param image2: 圖片2
              :param x: 位置x
              :param y: 位置y
              :return: 像素是否相同
              """
              # 取兩個圖片的像素點
              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, distance):
              """
              根據偏移量獲取移動軌跡
              :param distance: 偏移量
              :return: 移動軌跡
              """
              # 移動軌跡
              track = []
              # 當前位移
              current = 0
              # 減速閾值
              mid = distance * 4 / 5
              # 計算間隔
              t = 0.2
              # 初速度
              v = 0
              
              while current < distance:
                  if current < mid:
                      # 加速度爲正2
                      a = 2
                  else:
                      # 加速度爲負3
                      a = -3
                  # 初速度v0
                  v0 = v
                  # 當前速度v = v0 + at
                  v = v0 + a * t
                  # 移動距離x = v0t + 1/2 * a * t^2
                  move = v0 * t + 1 / 2 * a * t * t
                  # 當前位移
                  current += move
                  # 加入軌跡
                  track.append(round(move))
              return track
          
          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(self):
              """
              登陸
              :return: None
              """
              submit = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'login-btn')))
              submit.click()
              time.sleep(10)
              print('登陸成功')
          
          def crack(self):
              # 輸入用戶名密碼
              self.open()
              # 點擊驗證按鈕
              button = self.get_geetest_button()
              button.click()
              # 獲取驗證碼圖片
              image1 = self.get_geetest_image('captcha1.png')
              # 點按呼出缺口
              slider = self.get_slider()
              slider.click()
              # 獲取帶缺口的驗證碼圖片
              image2 = self.get_geetest_image('captcha2.png')
              # 獲取缺口位置
              gap = self.get_gap(image1, image2)
              print('缺口位置', gap)
              # 減去缺口位移
              gap -= BORDER
              # 獲取移動軌跡
              track = self.get_track(gap)
              print('滑動軌跡', track)
              # 拖動滑塊
              self.move_to_gap(slider, track)
              
              success = self.wait.until(
                  EC.text_to_be_present_in_element((By.CLASS_NAME, 'geetest_success_radar_tip_content'), '驗證成功'))
              print(success)
              
              # 失敗後重試
              if not success:
                  self.crack()
              else:
                  self.login()
      
      if __name__ == '__main__':
          crack = CrackGeetest()
          crack.crack()
      View Code            

3、點觸驗證碼的識別

  • 利用在線付費平臺(推薦超級鷹https://www.chaojiying.com)
    1. 獲取API
      • 在官網下載對應 Python API,連接:https://www.chaojiying.com/api-14.html。使用requests庫來實現。
      • 修改後的python3版本的API:
        import requests
        from hashlib import md5
        
        class Chaojiying(object):
            def __init__(self, username, password, soft_id):
                self.username = username
                self.password = md5(password.encode('utf-8')).hexdigest()
                self.soft_id = soft_id
                self.base_params = {
                    'user':self.username,
                    'pass2':self.password,
                    'softid':self.soft_id,
                }
                self.headers = {
                    'Connection':'Keep_Alive',
                    'User-Agent':'Mozilla/4.0(compatible;MSIE 8.0; Windows NT 5.1; Trident/4.0)',
                }
            def post_pic(self, im, codetype):
                """
                im: 圖片字節
                codetype: 題目類型參考 http://www.chaojiying.com/price.html
                """
                params = {
                    'codetype':codetype,
                }
                params.update(self.base_params)
                files = {'userfile':('ccc.jpg',im)}
                r = requests.post('http://upload.chaojiying.net/Upload/Processing.php', data=params, files=files,
                    headers=self.headers)
                return r.json()
            def report_error(self,im_id):
                """
                im_id:報錯題目的圖片ID 
                """
                params = {
                    'id':im_id,
                }
                params.update(self.base_params)
                r = requests.post('http://Upload.chaojiying.net/Upload/ReportError.php',data=params, headers= self.headers)
                return r.json()

        這裏定義一個chaojiying類,構造函數接收三個參數,分別是超級鷹用戶名、密碼以及軟件ID,保存以備使用。最重要的一個方法:post_pic(),須要傳入圖片對象和驗證碼的代號,該方法會將圖片對象和相關信息發給超級鷹後臺進行識別,而後將識別成功的JSON返回

    2. 初始化
      • 首先初始化一些變量,如WebDriver、chaojiying對象等,代碼實現以下:
        EMAIL = 'Mack01'
        PASSWORD = '超級鷹密碼'
        #超級鷹用戶名、密碼、軟件ID、驗證碼類型
        CHAOJIYING_USERNAME = 'Germey'
        CHAOJIYING_PASSWORD = ''
        CHAOJIYING_SOFT_ID = 893590
        CHAOJIYING_KIND = 9102
        
        class CrackTouClick():
            def __init__(self):
                self.url = 'http://admin.touclick.com/login.html'
                self.browser = webdriver.Chrome()
                self.wait = WebDriverWait(self.browser,20)
                self.email = EMAIL
                self.password =PASSWORD
                self.chaojiying = Chaojiying(CHAOJIYING_USERNAME, CHAOJIYING_PASSWORD, CHAOJIYING_SOFT_ID)
    3. 獲取驗證碼

      • 完善表單,模擬點擊呼出驗證碼,代碼:

        def open(self):
            """
            打開網頁輸入用戶名密碼 
            return: None 
            """
            self.browser.get(self.url)
            email =self.wait.until(EC.present_of_element_located((By.ID,'email')))
            password = self.wait.until(EC.present_of_element_located((By.ID,'password')))
            email.send_keys(self.email)
            password.send_keys(self.password)
        
        def get_touclick_button(self):
            """
            獲取初始驗證按鈕
            return: 
            """
            button = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME,'touclick-hod-wrap')))
            return button

        open()方法負責填寫表單,get_touclick_button()方法獲取驗證碼按鈕,以後觸發點擊便可。
        接下來,相似極驗驗證碼圖像獲取同樣,獲取驗證碼圖片的位置和大小,從網頁截圖裏截取相應 的驗證碼圖片,代碼實現以下所示:

        def get_touclick_element(self):
            """
            獲取驗證圖片對象
            return: 圖片對象 
            """
            element = self.wait.until(EC.present_of_element_located((By.CLASS_NAME,'touclick-pub-content')))
            return element
        def get_position(self):
            """
            獲取驗證碼位置 
            return: 驗證碼位置元組
            """
            element =self.get_touclick_element()
            time.sleep(2)
            location = element.location
            size = element.size
            top, bottom, left, right = location['y'],location['y']+size['height'],location['x'],location['x']+size['width']
            return (top, bottom, left, right)
        def get_screenshot(self):
            """
            獲取網頁截圖 
            return: 截圖對象 
            """
            screenshot = self.browser.get_screenshot_as_png()
            screenshot = Image.open(BytesIO(screenshot))
            return screenshot
        def get_touclick_image(self, name='captcha.png'):
            """
            獲取驗證碼圖片
            return: 圖片對象
            """
            top,bottom,left,right =self.get_position()
            print('驗證碼位置',top,bottom, left ,right)
            screenshot = self.get_screenshot()
            captcha = screenshot.crop((left,top,right,bottom))
            return captcha

        get_ touclick_image()方法即爲從網頁截圖中截取對應的驗證碼圖片,其中驗證碼圖片的相對位 置座標由 get position()方法返回獲得。 最後咱們獲得的是 Image 對象

    4. 識別驗證碼

      • 調用 Chaojiying 對象的 post_pic()方法,便可把圖片發送給超級鷹後臺,這裏發送的圖像是字節 流格式,代碼實現以下所示:

        image = self.get_touclick_image()
        bytes_array = BytesIO()
        image.save(bytes_array, format='PNG')
        #識別驗證碼
        result = self.chaojiying.post_pic(bytes_array.getvalue(),CHAOJIYING_KIND)
        print(result)

         運行以後, result 變量就是超級鷹後臺的識別結果。 運行須要等待幾秒。

        返回的結果是一個 JSON。 若是識別成功,典型的返回結果以下所示 :
        {’ err_no’:0, 'err_str’: 'OK', 'pic_id’: ’6002001380949200001', 'pic_str' :'132,127|56, 77 ','md5': '1f8e1d4bef8b11484cb1f1f34299865b'}
        其中,pic_str 就是識別的文字的座標,是以字符串形式返回的,每一個座標都以|分隔。接下來只須要將其解析,而後模擬點擊,代碼:

        def get_points(self, captcha_result):
            """
            解析識別結果
            param captcha_result: 識別結果 
            return: 轉化後的結果
            """
            groups = captcha_result.get('pic_str').split('|')
            locations = [[int(number) for number in group.split(',')] for group in groups]
            return locations
        def touch_click_words(self,locations):
            """
            點擊驗證圖片 
            param locations:點擊位置 
            return: None
            """
            for location in locations:
                print(location)
                ActionChains(self.browser).move_to_element_with_offset(self.get_touclick_element(),location[0],location[1]).click().perform()
                time.sleep(1)

        這裏用 get_points()方法將識別結果變成列表的形式。touch_click_words()方法則經過調用 move_to_element_with_offset()方法依次傳入解析後的座標,點擊便可。
        這樣就模擬完成座標的點選,最後點擊提交驗證的按鈕, 等待驗證經過,再點擊登陸按鈕便可成功登陸。

4、微博宮格驗證碼的識別

  • 通常選用全圖匹配的方式來進行識別。找到匹配的模板以後,就能夠獲得事先爲模板定義的拖動順序,而後模擬拖動便可。

  1. 獲取模板
    • 須要作一下準備工做,先保存全部的 24 張驗證碼全圖。由於驗證碼是隨機的,一共有 4!= 24 種。能夠寫一段程序來批量保存驗證碼圖片,而後從中篩選出須要的圖片,代碼所示:
      import time
      from io import BytesIO
      from PIL import Image
      from selenium import webdriver
      from selenium.common.exceptions import TimeoutException
      from selenium.webdriver.common.by import By
      from selenium.webdriver.support.ui import WebDriverWait
      from selenium.webdriver.support import expected_conditions as EC
      
      USERNAME = ''
      PASSWORD = ''
      
      
      class CrackWeiboSlide():
          def __init__(self):
              self.url = 'https://passport.weibo.cn/signin/login'
              self.browser = webdriver.Chrome()
              self.wait = WebDriverWait(self.browser,20)
              self.username = USERNAME
              self.password = PASSWORD
      
          def __del__(self):
              self.browser.close()
      
          def open(self):
              """
              打開網頁輸入用戶名密碼並點擊
              return: None
              """
              self.browser.get(self.url)
              username = self.wait.until(EC.presence_of_element_located((By.ID,'loginName')))
              password = self.wait.until(EC.presence_of_element_located((By.ID,'loginPassword')))
              submit = self.wait.until(EC.element_to_be_clickable((By.ID,'loginAction')))
              username.send_keys(self.username)
              password.send_keys(self.password)
              submit.click()
      
          def get_position(self):
              """
              獲取驗證碼位置
              return: 驗證碼位置元組
              """
              global img
              try:
                  img = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME,'Patt-shadow')))
              except TimeoutException:
                  print('未出現驗證碼')
                  self.open()
              time.sleep(2)
              location = img.location
              size = img.size
              top,bottom, left, right = location['y'],location['y']+size['height'],location['x'],location['x']+size['width']
              return (top, bottom, left, right)
      
          def get_screenshot(self):
              """
              獲取網頁截圖
              return: 截圖對象
              """
              screenshot = self.browser.get_screenshot_as_png()
              screenshot = Image.open(BytesIO(screenshot))
              return screenshot
      
          def get_image(self, name='captcha.png'):
              """
              獲取驗證碼嗎圖片
              return: 圖片對象
              """
              top, bottom, left, right = self.get_position()
              print('驗證碼位置', top, bottom,left, right)
              screenshot = self.get_screenshot()
              captcha = screenshot.crop((left, top, right, bottom))
              captcha.save(name)
              return captcha
      
          def main(self):
              """
              批量獲取驗證碼
              return: 圖片對象
              """
              count = 0
              while True:
                  self.open()
                  self.get_image(str(count)+'.png')
                  count +=1
      if __name__ =='__main__':
          crack = CrackWeiboSlide()
          crack.main()

      只須要挑選出不一樣的 24 張驗證碼圖片並命名保存。 名稱能夠直接取做宮格的滑動的順序,識別過程只須要遍歷模板進行匹配。

  2. 模板匹配
    • 調用 get_image()方法,獲得驗證碼圖片對象。而後,對驗證碼圖片對象進行模板匹配,定義以下方法:

      from os import listdir
      
      def detect_image(self,image):
          """
          匹配圖片
          param image: 圖片
          return: 拖動順序
          """
          for template_name in listdir(TEMPLATES_FOLDER):
              print('正在匹配', template_name)
              template = Image.open(TEMPLATES_FOLDER + template_name)
              if self.same_image(image, template):
                  #返回順序
                  numbers = [int(number) for number in list(template_name.split('.')[0])]
                  print('拖動順序',numbers)
                  return numbers

      TEMPLATES_FOLDER 就是模板所在的文件夾。 經過 listdir()方法獲取全部模板的文件名稱,而後對其進行遍歷,經過 same_image()方法對驗證碼和模板進行比對。 若是匹配成功,就將匹配到的模板文件名轉換爲列表。例:模板文件 3124.png匹配到,返回結果爲[3,1,2,4].

    • 對比的方法實現以下:

      def is_pixel_equal(self, image1, image2, x, y):
              """
              判斷兩個像素是否相同
              :param image1: 圖片1
              :param image2: 圖片2
              :param x: 位置x
              :param y: 位置y
              :return: 像素是否相同
              """
              # 獲取兩個圖片的像素點
              pixel1 = image1.load()[x, y]
              pixel2 = image2.load()[x, y]
              threshold = 20
              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 same_image(self, image, template):
              """
              識別類似驗證碼
              param image: 待識別驗證碼
              param template: 模板
              return:
              """
              # 相識度閾值
              threshold = 0.99
              count = 0
              for x in range(image.width):
                  for y in range(image.height):
                      # 判斷像素是否相同
                      if self.is_pixel_equal(image, template, x, y):
                          count += 1
              result = float(count) / (image.width * image.height)
              if result > threshold:
                  print('功能匹配')
                  return True
              return False

      在這裏比對圖片也利用了遍歷像素的方法。 same_image()方法接收兩個參數,image 爲待檢測的驗證碼圖片對象,template 是模板對象。因爲兩者大小是徹底一致的,因此在這裏遍歷了圖片的全部像素點。比對兩者同一位置的像素點,若是像素點相同,計數就加 1。最後計算相同的像素點佔總像素的比例。若是該比例超過必定閾值,那就斷定圖片徹底相同,則匹配成功。這裏闊值設定爲 0.99, 即若是兩者有 0.99 以上的類似比,則表明匹配成功。 依次匹配 24 個模板。 若是驗證碼圖片正常,咱們總能找到一個匹配的模板, 這樣就能夠獲得宮格的滑動順序了。

  3. 模擬拖動

    • 根據滑動順序拖動鼠標,鏈接各個宮格,方法實現以下所示:

      def move(self, numbers):
          """
          根據順序拖動 
          """
          #得到四個按點
          circles = self.browser.find_elements_by_css_selector('.patt-wrap. patt-circ')
          dx = dy =0
          for index in range(4):
              circle = circles[numbers[index]-1]
              #若是是第一次循環
              if index == 0:
                  # 點擊第一個按點
                  ActionChains(self.browser)\.move_to_element_with_offset(circle, circle.size['width']/2, circle.size['height']/2 )\.click_and_hold().perform()
              else:
                  #小幅移動次數
                  times = 30
                  #拖動
                  for i in range(times):
                      ActionChains(self.browser).move_by_offset(dx/times, dy/times).perform()
                      time.sleep(1/times)
              # 若是是最後一次循環
              if index == 3:
                  #鬆開鼠標
                  ActionChains(slef.browser).release().perform()
              else:
                  #計算下一次偏移
                  dx = circles[numbers[index +1]-1].location['x'] - circle.location['x']
                  dy = circles[numbers[index +1]-1].location['y'] - circle.location['y']

      這裏方法接收的參數就是宮格的點按順序,如 [3,1,2,4]。首先利用 find_elements_by_css_selector()方法獲取到 4 個宮格元素,它是一個列表形式,每一個元素表明一個宮格。接下來遍歷宮格的點按順序,作一系列對應操做。 

      其中若是當前遍歷的是第一個宮格,那就直接鼠標點擊並保持動做,不然移動到下一個宮格。若是當前遍歷的是最後一個宮格,那就鬆開鼠標,若是不是最後一個宮格,則計算移動到下一個宮格的偏移盤。 

      經過 4 次循環,即可以成功操做瀏覽器完成宮格驗證碼的拖拽填充,鬆開鼠標以後便可識別成功。 鼠標會慢慢從起始位置移動到終止位置。 最後一個宮格鬆開以後,驗證碼的識別便完成了。 

相關文章
相關標籤/搜索