opencv+playwright滑動驗證碼的實現(xiàn)
在本節(jié),我們將使用opencv和playwright這兩個庫通過QQ空間的滑動驗證碼。
梳理思路
1. 使用playwright打開瀏覽器,訪問qq空間登錄頁面。
2. 點擊密碼登錄。
3. 輸入賬號密碼并點擊登錄。
4. 出現(xiàn)滑動驗證碼圖片后,我們就可以獲取到驗證碼背景圖以及滑塊圖片。驗證碼背景圖片通過元素style中的url鏈接就可以獲取到,由于下載保存的是原圖,所以我們要將寬度調整為280px,280這個值同樣也可以在style中看到。
注:從style中也可以看到height值為200px,但其實這個包含了下方滑軌的高度,因此圖片的真實高度要小于200px。所以我們在調整原圖大小時,高度不要設為200px,而是通過以下公式進行等比縮放。
調整后的高度 = 原圖高/(原圖寬/280)
5. 我們同樣可以找到滑塊圖片的鏈接,但打開后卻是這樣的。
由于不知道滑塊在這張大圖上的位置,所以無法有效截取。另一種方案是直接通過屏幕截取獲得滑塊圖片。首先,對全屏幕進行截圖,然后用playwright獲取到滑塊元素,獲取到該元素的位置和大小后,就可以截取了?;瑝K的起始位置就是style中的left值。
6. 驗證碼背景圖有了,滑塊有了,滑塊初始位置也有了,接下來就是判斷背景圖的缺口位置,再求出滑動距離。我們可以使用opencv-python的matchTemplate()和minMaxLoc()方法獲取缺口的x坐標。拿到缺口x坐標后減去滑塊的x坐標就可以求出滑動距離了。
為了讓matchTemplate()的結果更加準確,我們可以對滑塊圖片做下處理,調整下對比度,讓它暗一些,跟缺口差不多。
注:minMaxLoc()這個函數(shù)返回一個最大值和最小值,因為無法知道哪一個是正確的,所以我們兩個值都應該拿來驗證。也就是說,我們會拿到兩個距離值,并且可能要滑動兩次。當然,這個其實沒啥關系,因為滑動驗證碼驗證失敗了的話,是可以再次滑動的。
7. 位置拿到之后,就是用鼠標控制滑軌上的按鈕并進行滑動操作?;瑒硬僮饕鎸?,不能勻速,滑動時鼠標肯定也會有上下抖動,總之要盡量模擬人的滑動操作。
8. 滑動成功的話下方的滑軌元素就會消失,我們可以通過這點來判斷是否通過了滑動驗證碼。如果滑了兩次都沒有通過(小概率),則刷新驗證碼并再次執(zhí)行步驟4,5,6,7,8。
編寫代碼
根據(jù)以上思路,我們可以編寫出如下代碼。
from playwright.sync_api import sync_playwright from PIL import Image import numpy as np import cv2 as cv import requests import random import re class QQZonSlide: def __init__(self): self.login_url = "https://i.qq.com/" self.username = "你的賬號" self.password = "你的密碼" self.page = None def start(self): with sync_playwright() as p: self.init_page(p) self.login() while True: self.get_slide_bg_img() start_x = self.get_slide_block_img_and_start_x() distance1, distance2 = self.get_slide_distance(start_x) slide_result = self.move_to_notch(distance1, distance2) if not slide_result: self.refresh_captcha() else: break def init_page(self, p): """初始化瀏覽器,獲取page對象""" browser = p.chromium.launch(headless=False) self.page = browser.new_page() def login(self): """通過賬號密碼登錄""" print("開始登錄") # 訪問頁面 self.page.goto(self.login_url) # 定位到登錄框元素并點擊密碼登錄 login_frame = self.page.frame_locator("#login_frame") login_frame.get_by_role("link", name="密碼登錄").click() # 清空賬號框然后輸入賬號 login_frame.locator("#u").clear() login_frame.locator("#u").fill(self.username) # 清空密碼框然后輸入密碼 login_frame.locator("#p").clear() login_frame.locator("#p").fill(self.password) # 點擊登錄按鈕 self.page.wait_for_timeout(1000) login_frame.locator("#login_button").click() def get_slide_bg_img(self): """截取滑動驗證碼背景圖片""" self.page.wait_for_timeout(2000) print("正在獲取滑動驗證碼背景圖片") # 獲取滑動驗證碼所在的iframe captcha_iframe = self.page.frame_locator("#login_frame").frame_locator("#tcaptcha_iframe_dy") # 獲取滑動驗證碼的背景圖 slide_bg_style = captcha_iframe.locator("#slideBg").get_attribute("style") slide_bg_url = re.search(r'url\("(.+)"\)', slide_bg_style).groups()[0] r = requests.get(slide_bg_url) with open("./slide_bg.png", "wb") as f: f.write(r.content) # 調整圖片大小,根據(jù)style內容將寬度調整為280,高度等比例調整 img = Image.open("./slide_bg.png") ratio = img.width / 280 img = img.resize(size=(280, int(img.height/ratio))) img.save("./slide_bg.png") def get_slide_block_img_and_start_x(self): """獲取滑塊圖片以及初始x坐標""" print("正在獲取滑塊圖片") # 首先保存整個登錄背景截圖 self.page.screenshot(path="bg.png") # 獲取滑動驗證碼所在的iframe captcha_iframe = self.page.frame_locator("#login_frame").frame_locator("#tcaptcha_iframe_dy") # 獲取滑塊圖片 # .tc-fg-item對應的有三個元素,一個是目標滑塊,一個是滑軌,還有一個是滑軌上的按鈕 for i in range(3): slide_block_ele = captcha_iframe.locator(".tc-fg-item").nth(i) slide_block_style = slide_block_ele.get_attribute("style") # 滑軌按鈕元素的style值中不包含url字符串 if "url" not in slide_block_style: continue # 從元素的style值中分析得出只有目標滑塊的top值小于150 top_value = re.search(r'top: (.+)px;', slide_block_style).groups()[0] if float(top_value) > 150: continue # 獲取x坐標 slide_block_x = float(re.search(r'left: (.+)px; top: ', slide_block_style).groups()[0]) # 通過滑塊位置,從背景圖中截取滑塊圖片 slide_block_rect = slide_block_ele.bounding_box() bg = Image.open("./bg.png") offset = slide_block_rect["width"] // 4 # 從背景圖上截取會混入滑塊周圍的一些像素點,所以加一個偏移值,截取到滑塊內部的圖片。 slide_block_img = bg.crop((slide_block_rect["x"] + offset, slide_block_rect["y"] + offset, slide_block_rect["x"] + slide_block_rect["width"] - offset, slide_block_rect["y"] + slide_block_rect["height"] - offset)) slide_block_img.save("slide_block.png") return slide_block_x + slide_block_rect["width"] // 4 @staticmethod def set_contrast_brightness(frame, contrast_value, brightness_value): if not contrast_value: contrast_value = 0.0 if not brightness_value: brightness_value = 0 blank = np.zeros(frame.shape, frame.dtype) frame = cv.addWeighted(frame, contrast_value, blank, 1 - contrast_value, brightness_value) return frame def get_slide_distance(self, start_x): """獲取滑動距離""" print("正在獲取滑動距離") # 通過opencv比較圖片,獲取缺口位置 slide_bg_img = cv.imread("./slide_bg.png") slide_block_img = cv.imread("./slide_block.png") slide_block_img = self.set_contrast_brightness(slide_block_img, 0.4, 0) result = cv.matchTemplate(slide_block_img, slide_bg_img, cv.TM_CCOEFF_NORMED) minVal, maxVal, minLoc, maxLoc = cv.minMaxLoc(result) # 缺口的x坐標 notch_x1 = minLoc[0] notch_x2 = maxLoc[0] # 距離 distance1 = notch_x1 - start_x distance2 = notch_x2 - start_x return distance1, distance2 @staticmethod def get_tracks(distance): """獲取移動軌跡""" tracks = [] # 移動軌跡 current = 0 # 當前位移 mid = distance * 4 / 5 # 減速閾值 t = 0.2 # 計算間隔 v = 0 # 初始速度 while current < distance: if current < mid: a = random.randint(3, 5) # 加速度為正5 else: a = random.randint(-5, -3) # 加速度為負3 v0 = v # 初速度 v0 v = v0 + a * t # 當前速度 move = v0 * t + 1 / 2 * a * t * t # 移動距離 current += move tracks.append(round(current)) return tracks def move_to_notch(self, distance1, distance2): """移動滑軌按鈕到缺口處""" # 獲取滑動驗證碼所在的iframe captcha_iframe = self.page.frame_locator("#login_frame").frame_locator("#tcaptcha_iframe_dy") for i in range(2): # 獲取按鈕位置,將鼠標移到上方并按下 slider_btn_rect = captcha_iframe.get_by_alt_text("slider").bounding_box() self.page.mouse.move(slider_btn_rect['x'], slider_btn_rect['y']) self.page.mouse.down() distance = [distance1, distance2][i] if distance <= 0: # 距離不可能小于等于0 continue print(f"正在進行第{i+1}次滑動") tracks = self.get_tracks(distance) for x in tracks: self.page.mouse.move(slider_btn_rect['x']+x, random.randint(-5, 5)+slider_btn_rect['y']) self.page.mouse.move(slider_btn_rect['x'] + tracks[-1] + 5, random.randint(-5, 5) + slider_btn_rect['y']) self.page.mouse.move(slider_btn_rect['x'] + tracks[-1] - 5, random.randint(-5, 5) + slider_btn_rect['y']) self.page.mouse.up() # 滑動結束后等待一段時間 self.page.wait_for_timeout(2000) # 尋找按鈕是否還存在,不存在的話表明已通過滑動驗證碼,存在的話嘗試下一個距離 try: captcha_iframe.get_by_alt_text("slider").wait_for(timeout=2000) except Exception as e: print("已通過滑動驗證碼") return True else: print(f"第{i+1}次滑動失敗") return False def refresh_captcha(self): """刷新驗證碼""" # 獲取滑動驗證碼所在的iframe print("刷新驗證碼") captcha_iframe = self.page.frame_locator("#login_frame").frame_locator("#tcaptcha_iframe_dy") captcha_iframe.locator("#e_reload").click() self.page.wait_for_timeout(2000) if __name__ == "__main__": slide = QQZonSlide() slide.start()
總結與提高
有時候就算通過了滑動驗證碼,QQ空間也會提示當前網(wǎng)絡異?;蛘卟话踩?,導致這種情況出現(xiàn)的原因很可能是軌跡出了問題,我們可以使用更好的算法生成更真實的滑動軌跡來避免這種情況出現(xiàn)。
到此這篇關于opencv+playwright滑動驗證碼的實現(xiàn)的文章就介紹到這了,更多相關opencv playwright滑動驗證碼內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Python數(shù)值方法及數(shù)據(jù)可視化
這篇文章主要介紹了Python數(shù)值方法及數(shù)據(jù)可視化,文章圍繞主題展開詳細的內容介紹,具有一定的參考價值,需要的小伙伴可以參考一下2022-09-09使用python搭建代理IP池實現(xiàn)接口設置與整體調度
在網(wǎng)絡爬蟲中,代理IP池是一個非常重要的組件,由于許多網(wǎng)站對單個IP的請求有限制,因此,我們需要一個代理IP池,在本文中,我們將使用Python來構建一個代理IP池,然后,我們將使用這個代理IP池來訪問我們需要的數(shù)據(jù),文中有相關的代碼示例供大家參考,需要的朋友可以參考下2023-12-12wxpython+pymysql實現(xiàn)用戶登陸功能
這篇文章主要介紹了wxpython+pymysql實現(xiàn)用戶登陸功能,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-11-11