Python3 爬虫实战 — 模拟登陆哔哩哔哩【滑动验证码对抗】
- 登陸時間:2019-10-21
 - 實現難度:★★★☆☆☆
 - 請求鏈接:https://passport.bilibili.com/login
 - 實現目標:模擬登陸嗶哩嗶哩,攻克滑動驗證碼
 - 涉及知識:滑動驗證碼的攻克、自動化測試工具 Selenium 的使用
 - 完整代碼:https://github.com/TRHX/Python3-Spider-Practice/tree/master/AutomationTool/bilibili-login
 - 其他爬蟲實戰代碼合集(持續更新):https://github.com/TRHX/Python3-Spider-Practice
 - 爬蟲實戰專欄(持續更新):https://itrhx.blog.csdn.net/article/category/9351278
 
文章目錄
- 【1x00】思維導圖
 - 【2x00】登陸模塊
 - 【2x01】初始化函數
 - 【2x02】登陸函數
 
- 【3x00】驗證碼處理模塊
 - 【3x01】驗證碼元素查找函數
 - 【3x02】元素可見性設置函數
 - 【3x03】驗證碼截圖函數
 
- 【4x00】驗證碼滑動模塊
 - 【4x01】滑動主函數
 - 【4x02】缺口位置尋找函數
 - 【4x03】計算滑塊移動距離函數
 - 【4x04】構造移動軌跡函數
 - 【4x05】模擬拖動函數
 
- 【5x00】完整代碼
 - 【6x00】效果實現動圖
 
【1x00】思維導圖
-  
利用自動化測試工具 Selenium 直接模擬人的行為方式來完成驗證
 -  
分析頁面,想辦法找到滑動驗證碼的完整圖片、帶有缺口的圖片和需要滑動的圖片
 -  
對比原始的圖片和帶缺口的圖片的像素,像素不同的地方就是缺口位置
 -  
計算出滑塊缺口的位置,得到所需要滑動的距離
 -  
拖拽時要模仿人的行為,由于有個對準過程,所以要構造先快后慢的運動軌跡
 -  
最后利用 Selenium 進行對滑塊的拖拽
 
【2x00】登陸模塊
【2x01】初始化函數
def init():global url, browser, username, password, waiturl = 'https://passport.bilibili.com/login'# path是谷歌瀏覽器驅動的目錄,如果已經將目錄添加到系統變量,則不用設置此路徑path = r'F:\PycharmProjects\Python3爬蟲\chromedriver.exe'chrome_options = Options()chrome_options.add_argument('--start-maximized')browser = webdriver.Chrome(executable_path=path, chrome_options=chrome_options)# 你的嗶哩嗶哩用戶名username = '155********'# 你的嗶哩嗶哩登陸密碼password = '***********'wait = WebDriverWait(browser, 20)global 關鍵字定義了發起請求的url、用戶名、密碼等全局變量,隨后是登錄頁面url、谷歌瀏覽器驅動的目錄path、實例化 Chrome 瀏覽器、設置瀏覽器分辨率最大化、用戶名、密碼、WebDriverWait() 方法設置等待超時
【2x02】登陸函數
def login():browser.get(url)# 獲取用戶名輸入框user = wait.until(EC.presence_of_element_located((By.ID, 'login-username')))# 獲取密碼輸入框passwd = wait.until(EC.presence_of_element_located((By.ID, 'login-passwd')))# 輸入用戶名user.send_keys(username)# 輸入密碼passwd.send_keys(password)# 獲取登錄按鈕login_btn = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'a.btn.btn-login')))# 隨機暫停幾秒time.sleep(random.random() * 3)# 點擊登陸按鈕login_btn.click()等待用戶名輸入框和密碼輸入框對應的 ID 節點加載出來
獲取這兩個節點,用戶名輸入框 id="login-username",密碼輸入框 id="login-passwd"
調用 send_keys() 方法輸入用戶名和密碼
獲取登錄按鈕 class="btn btn-login"
隨機產生一個數并將其擴大三倍作為暫停時間
最后調用 click() 方法實現登錄按鈕的點擊
【3x00】驗證碼處理模塊
【3x01】驗證碼元素查找函數
def find_element():# 獲取帶有缺口的圖片c_background = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_bg.geetest_absolute')))# 獲取需要滑動的圖片c_slice = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_slice.geetest_absolute')))# 獲取完整的圖片c_full_bg = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_fullbg.geetest_fade.geetest_absolute')))# 隱藏需要滑動的圖片hide_element(c_slice)# 保存帶有缺口的圖片save_screenshot(c_background, 'back')# 顯示需要滑動的圖片show_element(c_slice)# 保存需要滑動的圖片save_screenshot(c_slice, 'slice')# 顯示完整的圖片show_element(c_full_bg)# 保存完整的圖片save_screenshot(c_full_bg, 'full')獲取驗證碼的三張圖片,分別是完整的圖片、帶有缺口的圖片和需要滑動的圖片
分析頁面代碼,三張圖片是由 3 個 canvas 組成,3 個 canvas 元素包含 CSS display 屬性,display:block 為可見,display:none 為不可見,在分別獲取三張圖片時要將其他兩張圖片設置為 display:none,這樣做才能單獨提取到每張圖片
定位三張圖片的 class 分別為:帶有缺口的圖片(c_background):geetest_canvas_bg geetest_absolute、需要滑動的圖片(c_slice):geetest_canvas_slice geetest_absolute、完整圖片(c_full_bg):geetest_canvas_fullbg geetest_fade geetest_absolute
最后傳值給 save_screenshot() 函數,進一步對驗證碼進行處理
【3x02】元素可見性設置函數
# 設置元素不可見 def hide_element(element):browser.execute_script("arguments[0].style=arguments[1]", element, "display: none;")# 設置元素可見 def show_element(element):browser.execute_script("arguments[0].style=arguments[1]", element, "display: block;")【3x03】驗證碼截圖函數
def save_screenshot(obj, name):try:# 首先對出現驗證碼后的整個頁面進行截圖保存pic_url = browser.save_screenshot('.\\bilibili.png')print("%s:截圖成功!" % pic_url)# 計算傳入的obj,也就是三張圖片的位置信息left = obj.location['x']top = obj.location['y']right = left + obj.size['width']bottom = top + obj.size['height']# 打印輸出一下每一張圖的位置信息print('圖:' + name)print('Left %s' % left)print('Top %s' % top)print('Right %s' % right)print('Bottom %s' % bottom)print('')# 在整個頁面截圖的基礎上,根據位置信息,分別剪裁出三張驗證碼圖片并保存im = Image.open('.\\bilibili.png')im = im.crop((left, top, right, bottom))file_name = 'bili_' + name + '.png'im.save(file_name)except BaseException as msg:print("%s:截圖失敗!" % msg)location 屬性可以返回該圖片對象在瀏覽器中的位置,坐標軸是以屏幕左上角為原點,x軸向右遞增,y軸向下遞增
size 屬性可以返回該圖片對象的高度和寬度,由此可以得到驗證碼的位置信息
首先調用 save_screenshot() 屬性對整個頁面截圖并保存
然后向 crop() 方法傳入驗證碼的位置信息,由位置信息再對驗證碼進行剪裁并保存
【4x00】驗證碼滑動模塊
【4x01】滑動主函數
def slide():distance = get_distance(Image.open('.\\bili_back.png'), Image.open('.\\bili_full.png'))print('計算偏移量為:%s Px' % distance)trace = get_trace(distance - 5)move_to_gap(trace)time.sleep(3)向 get_distance() 函數傳入完整的圖片和缺口圖片,計算滑塊需要滑動的距離,再把距離信息傳入 get_trace() 函數,構造滑塊的移動軌跡,最后根據軌跡信息調用 move_to_gap() 函數移動滑塊完成驗證
【4x02】缺口位置尋找函數
def is_pixel_equal(bg_image, fullbg_image, x, y):# 獲取兩張圖片對應像素點的RGB數據bg_pixel = bg_image.load()[x, y]fullbg_pixel = fullbg_image.load()[x, y]# 設定一個閾值threshold = 60# 比較兩張圖 RGB 的絕對值是否均小于定義的閾值if (abs(bg_pixel[0] - fullbg_pixel[0] < threshold) and abs(bg_pixel[1] - fullbg_pixel[1] < threshold) and abs(bg_pixel[2] - fullbg_pixel[2] < threshold)):return Trueelse:return False將完整圖片和缺口圖片兩個對象分別賦值給變量 bg_image 和 fullbg_image,接下來對比圖片獲取缺口。遍歷圖片的每個坐標點,獲取兩張圖片對應像素點的 RGB 數據,判斷像素的各個顏色之差,abs() 用于取絕對值,比較兩張圖 RGB 的絕對值是否均小于定義的閾值 threshold,如果絕對值均在閾值之內,則代表像素點相同,繼續遍歷,否則代表不相同的像素點,即缺口的位置
【4x03】計算滑塊移動距離函數
def get_distance(bg_image, fullbg_image):# 滑塊的初始位置distance = 60# 遍歷兩張圖片的每個像素for i in range(distance, fullbg_image.size[0]):for j in range(fullbg_image.size[1]):# 調用缺口位置尋找函數if not is_pixel_equal(fullbg_image, bg_image, i, j):return iget_distance() 方法即獲取缺口位置的方法,此方法的參數是兩張圖片,一張為完整的圖片,另一張為帶缺口的圖片,distance 為滑塊的初始位置,遍歷兩張圖片的每個像素,利用 is_pixel_equal() 缺口位置尋找函數判斷兩張圖片同一位置的像素是否相同,若不相同則返回該點的值
【4x04】構造移動軌跡函數
def get_trace(distance):trace = []# 設置加速距離為總距離的4/5faster_distance = distance * (4 / 5)# 設置初始位置、初始速度、時間間隔start, v0, t = 0, 0, 0.1while start < distance:if start < faster_distance:a = 10else:a = -10# 位移move = v0 * t + 1 / 2 * a * t * t# 當前時刻的速度v = v0 + a * tv0 = vstart += movetrace.append(round(move))# trace 記錄了每個時間間隔移動了多少位移return traceget_trace() 方法傳入的參數為移動的總距離,返回的是運動軌跡,運動軌跡用 trace 表示,它是一個列表,列表的每個元素代表每次移動多少距離,利用 Selenium 進行對滑塊的拖拽時要模仿人的行為,由于有個對準過程,所以是先快后慢,勻速移動、隨機速度移動都不會成功,因此要設置一個加速和減速的距離,這里設置加速距離 faster_distance 是總距離 distance 的4/5倍,滑塊滑動的加速度用 a 來表示,當前速度用 v 表示,初速度用 v0 表示,位移用 move 表示,所需時間用 t 表示,它們之間滿足以下關系:
move = v0 * t + 0.5 * a * t * t v = v0 + a * t設置初始位置、初始速度、時間間隔分別為0, 0, 0.1,加速階段和減速階段的加速度分別設置為10和-10,直到運動軌跡達到總距離時,循環終止,最后得到的 trace 記錄了每個時間間隔移動了多少位移,這樣滑塊的運動軌跡就得到了
【4x05】模擬拖動函數
def move_to_gap(trace):# 獲取滑動按鈕slider = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'div.geetest_slider_button')))# 點擊并拖動滑塊ActionChains(browser).click_and_hold(slider).perform()# 遍歷運動軌跡獲取每小段位移距離for x in trace:# 移動此位移ActionChains(browser).move_by_offset(xoffset=x, yoffset=0).perform()time.sleep(0.5)# 釋放鼠標ActionChains(browser).release().perform()傳入的參數為運動軌跡,首先查找到滑動按鈕,然后調用 ActionChains 的 click_and_hold() 方法按住拖動底部滑塊,perform() 方法用于執行,遍歷運動軌跡獲取每小段位移距離,調用 move_by_offset() 方法移動此位移,最后調用 release() 方法松開鼠標即可
【5x00】完整代碼
# ============================================= # --*-- coding: utf-8 --*-- # @Time : 2019-10-21 # @Author : TRHX # @Blog : www.itrhx.com # @CSDN : https://blog.csdn.net/qq_36759224 # @FileName: bilibili.py # @Software: PyCharm # =============================================from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.support.wait import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By from selenium.webdriver import ActionChains import time import random from PIL import Image# 初始化函數 def init():global url, browser, username, password, waiturl = 'https://passport.bilibili.com/login'# path是谷歌瀏覽器驅動的目錄,如果已經將目錄添加到系統變量,則不用設置此路徑path = r'F:\PycharmProjects\Python3爬蟲\chromedriver.exe'chrome_options = Options()chrome_options.add_argument('--start-maximized')browser = webdriver.Chrome(executable_path=path, chrome_options=chrome_options)# 你的嗶哩嗶哩用戶名username = '155********'# 你的嗶哩嗶哩登錄密碼password = '***********'wait = WebDriverWait(browser, 20)# 登錄函數 def login():browser.get(url)# 獲取用戶名輸入框user = wait.until(EC.presence_of_element_located((By.ID, 'login-username')))# 獲取密碼輸入框passwd = wait.until(EC.presence_of_element_located((By.ID, 'login-passwd')))# 輸入用戶名user.send_keys(username)# 輸入密碼passwd.send_keys(password)# 獲取登錄按鈕login_btn = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'a.btn.btn-login')))# 隨機暫停幾秒time.sleep(random.random() * 3)# 點擊登陸按鈕login_btn.click()# 驗證碼元素查找函數 def find_element():# 獲取帶有缺口的圖片c_background = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_bg.geetest_absolute')))# 獲取需要滑動的圖片c_slice = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_slice.geetest_absolute')))# 獲取完整的圖片c_full_bg = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_fullbg.geetest_fade.geetest_absolute')))# 隱藏需要滑動的圖片hide_element(c_slice)# 保存帶有缺口的圖片save_screenshot(c_background, 'back')# 顯示需要滑動的圖片show_element(c_slice)# 保存需要滑動的圖片save_screenshot(c_slice, 'slice')# 顯示完整的圖片show_element(c_full_bg)# 保存完整的圖片save_screenshot(c_full_bg, 'full')# 設置元素不可見 def hide_element(element):browser.execute_script("arguments[0].style=arguments[1]", element, "display: none;")# 設置元素可見 def show_element(element):browser.execute_script("arguments[0].style=arguments[1]", element, "display: block;")# 驗證碼截圖函數 def save_screenshot(obj, name):try:# 首先對出現驗證碼后的整個頁面進行截圖保存pic_url = browser.save_screenshot('.\\bilibili.png')print("%s:截圖成功!" % pic_url)# 計算傳入的obj,也就是三張圖片的位置信息left = obj.location['x']top = obj.location['y']right = left + obj.size['width']bottom = top + obj.size['height']# 打印輸出一下每一張圖的位置信息print('圖:' + name)print('Left %s' % left)print('Top %s' % top)print('Right %s' % right)print('Bottom %s' % bottom)print('')# 在整個頁面截圖的基礎上,根據位置信息,分別剪裁出三張驗證碼圖片并保存im = Image.open('.\\bilibili.png')im = im.crop((left, top, right, bottom))file_name = 'bili_' + name + '.png'im.save(file_name)except BaseException as msg:print("%s:截圖失敗!" % msg)# 滑動模塊的主函數 def slide():distance = get_distance(Image.open('.\\bili_back.png'), Image.open('.\\bili_full.png'))print('計算偏移量為:%s Px' % distance)trace = get_trace(distance - 5)move_to_gap(trace)time.sleep(3)# 計算滑塊移動距離函數 def get_distance(bg_image, fullbg_image):# 滑塊的初始位置distance = 60# 遍歷兩張圖片的每個像素for i in range(distance, fullbg_image.size[0]):for j in range(fullbg_image.size[1]):# 調用缺口位置尋找函數if not is_pixel_equal(fullbg_image, bg_image, i, j):return i# 缺口位置尋找函數 def is_pixel_equal(bg_image, fullbg_image, x, y):# 獲取兩張圖片對應像素點的RGB數據bg_pixel = bg_image.load()[x, y]fullbg_pixel = fullbg_image.load()[x, y]# 設定一個閾值threshold = 60# 比較兩張圖 RGB 的絕對值是否均小于定義的閾值if (abs(bg_pixel[0] - fullbg_pixel[0] < threshold) and abs(bg_pixel[1] - fullbg_pixel[1] < threshold) and abs(bg_pixel[2] - fullbg_pixel[2] < threshold)):return Trueelse:return False# 構造移動軌跡函數 def get_trace(distance):trace = []# 設置加速距離為總距離的4/5faster_distance = distance * (4 / 5)# 設置初始位置、初始速度、時間間隔start, v0, t = 0, 0, 0.1while start < distance:if start < faster_distance:a = 10else:a = -10# 位移move = v0 * t + 1 / 2 * a * t * t# 當前時刻的速度v = v0 + a * tv0 = vstart += movetrace.append(round(move))# trace 記錄了每個時間間隔移動了多少位移return trace# 模擬拖動函數 def move_to_gap(trace):# 獲取滑動按鈕slider = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'div.geetest_slider_button')))# 點擊并拖動滑塊ActionChains(browser).click_and_hold(slider).perform()# 遍歷運動軌跡獲取每小段位移距離for x in trace:# 移動此位移ActionChains(browser).move_by_offset(xoffset=x, yoffset=0).perform()time.sleep(0.5)# 釋放鼠標ActionChains(browser).release().perform()if __name__ == '__main__':init()login()find_element()slide()【6x00】效果實現動圖
最終實現效果圖:(關鍵信息已經過打碼處理)
 
總結
以上是生活随笔為你收集整理的Python3 爬虫实战 — 模拟登陆哔哩哔哩【滑动验证码对抗】的全部內容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: 快舟一号甲火箭复飞成功:发射天行一号试验
 - 下一篇: 一分钟直降20℃!魅族 PANDAER