中职网站建设与维护试卷,国家企业信用信息公示系统查询网,wordpress删除月份归档,国办网站建设指引Python3 爬虫学习笔记第十三章 —— 【验证码对抗系列 — 滑动验证码】文章目录【13.1】关于滑动验证码【13.2】滑动验证码攻克思路【13.3】模拟登录 bilibili — 总体思路【13.4】主函数【13.5】初始化函数【13.6】登录函数【13.7】验证码元素查找函数【13.8】元素可见性设置函… Python3 爬虫学习笔记第十三章 —— 【验证码对抗系列 — 滑动验证码】 文章目录【13.1】关于滑动验证码【13.2】滑动验证码攻克思路【13.3】模拟登录 bilibili — 总体思路【13.4】主函数【13.5】初始化函数【13.6】登录函数【13.7】验证码元素查找函数【13.8】元素可见性设置函数【13.9】验证码截图函数【13.10】滑动函数【13.11】计算滑块移动距离函数【13.12】像素判断函数【13.13】构造移动轨迹函数【13.14】模拟拖动函数【13.15】效果实现动图【13.16】完整代码【13.1】关于滑动验证码
滑动验证码属于行为式验证码需要通过用户的操作行为来完成验证一般是根据提示用鼠标将滑块拖动到指定的位置完成验证此类验证码背景图片采用多种图像加密技术且添加了很多随机效果能有效防止OCR文字识别另外验证码上的文字采用了随机印刷技术能够随机采用多种字体、多种变形的实时随机印刷防止暴力破解斗鱼、哔哩哔哩、淘宝等平台都使用了滑动验证码 【13.2】滑动验证码攻克思路
利用自动化测试工具 Selenium 直接模拟人的行为方式来完成验证首先要分析页面想办法找到滑动验证码的完整图片、带有缺口的图片和需要滑动的图片通过对比原始的图片和带滑块缺口的图片的像素像素不同的地方就是缺口位置计算出滑块缺口的位置得到所需要滑动的距离最后利用 Selenium 进行对滑块的拖拽拖拽时要模仿人的行为由于有个对准过程所以是先快后慢匀速移动、随机速度移动都不会成功
以下以哔哩哔哩为例来做模拟登录练习 【13.3】模拟登录 bilibili — 总体思路
首先使用 Selenium 模拟登陆 bilibili自动输入账号密码查找到登陆按钮并点击使其出现滑动验证码此时分析页面滑动验证组件是由3个 canvas 组成分别代表完整图片、带有缺口的图片和需要滑动的图片3个 canvas 元素包含 CSS display 属性display:block 为可见display:none 为不可见分别获取三张图片时要将其他两张图片设置为 display:none获取元素位置后即可对图片截图并保存通过图片像素对比找到缺口位置即为滑块要移动的距离随后构造滑动轨迹按照先加速后减速的方式移动滑块完成验证。
整个程序包含的函数
def init(): 初始化函数定义全局变量
def login(): 登录函数输入账号密码并点击登录
def find_element(): 验证码元素查找函数查找三张图的元素
def hide_element(): 设置元素不可见函数
def show_element(): 设置元素可见函数
def save_screenshot(): 验证码截图函数截取三张图并保存
def slide(): 滑动函数
def is_pixel_equal(): 像素判断函数寻找缺口位置
def get_distance(): 计算滑块移动距离函数
def get_track(): 构造移动轨迹函数
def move_to_gap(): 模拟拖动函数整个程序用到的库
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【13.4】主函数
if __name__ __main__:init()login()find_element()slide()【13.5】初始化函数
def init():global url, browser, username, password, waiturl https://passport.bilibili.com/loginpath rF:\PycharmProjects\Python3爬虫\chromedriver.exechrome_options Options()chrome_options.add_argument(--start-maximized)browser webdriver.Chrome(executable_pathpath, chrome_optionschrome_options)username 155********password ***********wait WebDriverWait(browser, 20)global 关键字定义了全局变量随后是登录页面url、谷歌浏览器驱动的目录path、实例化 Chrome 浏览器、设置浏览器分辨率最大化、用户名、密码、WebDriverWait() 方法设置等待超时 【13.6】登录函数
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 节点加载出来分析页面可知用户名输入框 idlogin-username密码输入框 idlogin-passwd获取这两个节点调用 send_keys() 方法输入用户名和密码随后获取登录按钮分析页面可知登录按钮 classbtn btn-login随机产生一个数并将其扩大三倍作为暂停时间最后调用 click() 方法实现登录按钮的点击 【13.7】验证码元素查找函数
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_backgroundgeetest_canvas_bg geetest_absolute、需要滑动的图片c_slicegeetest_canvas_slice geetest_absolute、完整图片c_full_bggeetest_canvas_fullbg geetest_fade geetest_absolute随后传值给 save_screenshot() 函数进一步对验证码进行处理 【13.8】元素可见性设置函数
# 设置元素不可见
def hide_element(element):browser.execute_script(arguments[0].stylearguments[1], element, display: none;)# 设置元素可见
def show_element(element):browser.execute_script(arguments[0].stylearguments[1], element, display: block;)【13.9】验证码截图函数
def save_screenshot(obj, name):try:pic_url browser.save_screenshot(.\\bilibili.png)print(%s:截图成功! % pic_url)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 .pngim.save(file_name)except BaseException as msg:print(%s:截图失败! % msg)location 属性可以返回该图片对象在浏览器中的位置坐标轴是以屏幕左上角为原点x轴向右递增y轴向下递增size 属性可以返回该图片对象的高度和宽度由此可以得到验证码的位置信息首先调用 save_screenshot() 属性对整个页面截图并保存然后向 crop() 方法传入验证码的位置信息由位置信息再对验证码进行剪裁并保存 【13.10】滑动函数
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() 函数移动滑块完成验证 【13.11】计算滑块移动距离函数
def get_distance(bg_image, fullbg_image):distance 60for 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() 像素判断函数判断两张图片同一位置的像素是否相同比较两张图 RGB 的绝对值是否均小于定义的阈值 threshold如果绝对值均在阈值之内则代表像素点相同继续遍历否则代表不相同的像素点即缺口的位置 【13.12】像素判断函数
def is_pixel_equal(bg_image, fullbg_image, x, y):bg_pixel bg_image.load()[x, y]fullbg_pixel fullbg_image.load()[x, y]threshold 60if (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 数据差距在一定范围内那就代表两个像素相同继续比对下一个像素点如果差距超过一定范围则代表像素点不同当前位置即为缺口位置 【13.13】构造移动轨迹函数
def get_trace(distance):trace []faster_distance distance * (4 / 5)start, v0, t 0, 0, 0.1while start distance:if start faster_distance:a 10else:a -10move v0 * t 1 / 2 * a * t * tv v0 a * tv0 vstart movetrace.append(round(move))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 记录了每个时间间隔移动了多少位移这样滑块的运动轨迹就得到了 【13.14】模拟拖动函数
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(xoffsetx, yoffset0).perform()time.sleep(0.5)ActionChains(browser).release().perform()传入的参数为运动轨迹首先查找到滑动按钮然后调用 ActionChains 的 click_and_hold() 方法按住拖动底部滑块perform() 方法用于执行遍历运动轨迹获取每小段位移距离调用 move_by_offset() 方法移动此位移最后调用 release() 方法松开鼠标即可 【13.15】效果实现动图
最终实现效果图关键信息已经过打码处理 【13.16】完整代码
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 Imagedef init():global url, browser, username, password, waiturl https://passport.bilibili.com/loginpath rF:\PycharmProjects\Python3爬虫\chromedriver.exechrome_options Options()chrome_options.add_argument(--start-maximized)browser webdriver.Chrome(executable_pathpath, chrome_optionschrome_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].stylearguments[1], element, display: none;)def show_element(element):browser.execute_script(arguments[0].stylearguments[1], element, display: block;)def save_screenshot(obj, name):try:pic_url browser.save_screenshot(.\\bilibili.png)print(%s:截图成功! % pic_url)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 .pngim.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 60for 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 idef is_pixel_equal(bg_image, fullbg_image, x, y):bg_pixel bg_image.load()[x, y]fullbg_pixel fullbg_image.load()[x, y]threshold 60if (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 Falsedef get_trace(distance):trace []faster_distance distance * (4 / 5)start, v0, t 0, 0, 0.1while start distance:if start faster_distance:a 20else:a -20move v0 * t 1 / 2 * a * t * tv v0 a * tv0 vstart movetrace.append(round(move))return tracedef 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(xoffsetx, yoffset0).perform()time.sleep(0.5)ActionChains(browser).release().perform()if __name__ __main__:init()login()find_element()slide()