当前位置: 首页 > news >正文

python爬虫处理滑块验证_python爬虫基础(9:验证识别之滑块验证)

上篇我们借助 tesserocr 库解决了图片码的识别验证,但在实际生活中,用得更多的是滑动验证,这篇就来解决滑动验证的问题

滑动验证

大部分网站都采用的是滑动验证,就是拖动滑块拼接图片,其中又大都采用极验(http://www.geetest.com/)所提供的技术,官方网页如下

b707b362aa730e17d2e196f8c0695099.png

本篇案例选用哔哩哔哩动画验证登录(https://passport.bilibili.com/login)

3fae1c7ca13743a183f894cb1db82735.png

所需工具

chromedriver:浏览器驱动,可以理解为一个没有界面的chrome浏览器

selenium:用于模拟人对浏览器进行点击、输出、拖拽等操作,就相当于是个人在使用浏览器,也常常用来应付反爬虫措施,配合chromedriver使用,使用方法直接粘大神写的:https://cuiqingcai.com/5630.html

Image模块:提供很多对图片进行处理的方法的库,用法请查看https://www..com/kongzhagen/p/6295925.html

解决思路

1. 获取验证图片

通过访问登录页面,分析源码找到完整图片和带滑块缺口的图片 ,通过 selenium 键入登录信息

2. 获取缺口位置

通过对比原始的图片和带滑块缺口的图片的像素,计算出滑块缺口的位置,得到所需要滑动的距离

3.模拟拖动

利用selenium进行对滑块的拖拽,注意模仿人的行为:先快后慢,有个对准过程

案例步骤

第一大步:获取验证图片

1.初始化一些需要用到的参数

from selenium import webdriver

from selenium.webdriver.support.wait import WebDriverWait

# 初始化

def init():

# 定义为全局变量,方便其他模块使用

global url, browser, username, password, wait

# 登录界面的url

url = 'https://passport.bilibili.com/login'

# 实例化一个chrome浏览器

browser = webdriver.Chrome()

# 用户名

username = '***********'

# 密码

password = '***********'

# 设置等待超时

wait = WebDriverWait(browser, 20)

2.通过 selenium 键入登录信息

(by the way:之前用 post 提交表单做过模拟登录,其实用selenium模拟人键入登录信息和点击提交也可以成功登录,但问题是selenium相率太低,所以一般能不用selenium就不用)

from selenium.webdriver.support import expected_conditions as EC

from selenium.webdriver.common.by import By

# 登录

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)

3.获取验证图片

通过分析源码找到原始完整图片和带滑块缺口的图片

from urllib.request import urlretrieve

from bs4 import BeautifulSoup

import re

from PIL import Image

# 获取图片信息

def get_image_info(img):

'''

:param img: (Str)想要获取的图片类型:带缺口、原始

:return: 该图片(Image)、位置信息(List)

'''

# 将网页源码转化为能被解析的lxml格式

soup = BeautifulSoup(browser.page_source, 'lxml')

# 获取验证图片的所有组成片标签

imgs = soup.find_all('div', {'class': 'gt_cut_'+img+'_slice'})

# 用正则提取缺口的小图片的url,并替换后缀

img_url = re.findall('url\(\"(.*)\"\);', imgs[0].get('style'))[0].replace('webp', 'jpg')

# 使用urlretrieve()方法根据url下载缺口图片对象

urlretrieve(url=img_url, filename=img+'.jpg')

# 生成缺口图片对象

image = Image.open(img+'.jpg')

# 获取组成他们的小图片的位置信息

position = get_position(imgs)

# 返回图片对象及其位置信息

return image, position

但是这里有一个问题,验证图片是由两行许多的小图片组成的,而这些小图片不是按顺序排列的,这样就无法计算滑块缺口的位置了

9f104cd346ee22be7890a342bdf57338.png

84c8b4f215461445099771f6742aa92d.png

因此我们上面的代码在有一个获取每个小图片正确位置信息的方法 get_position()

其代码如下:

# 获取小图片位置

def get_position(img):

'''

:param img: (List)存放多个小图片的标签

:return: (List)每个小图片的位置信息

'''

img_position = []

for small_img in img:

position = {}

# 获取每个小图片的横坐标

position['x'] = int(re.findall('background-position: (.*)px (.*)px;', small_img.get('style'))[0][0])

# 获取每个小图片的纵坐标

position['y'] = int(re.findall('background-position: (.*)px (.*)px;', small_img.get('style'))[0][1])

img_position.append(position)

return img_position

4.把这些小图片裁剪下来以方便重新拼成顺序正确的图片

from PIL import Image

# 裁剪图片

def Corp(image, position):

'''

:param image:(Image)被裁剪的图片

:param position: (List)该图片的位置信息

:return: (List)存放裁剪后的每个图片信息

'''

# 第一行图片信息

first_line_img = []

# 第二行图片信息

second_line_img = []

for pos in position:

if pos['y'] == -58:

first_line_img.append(image.crop((abs(pos['x']), 58, abs(pos['x']) + 10, 116)))

if pos['y'] == 0:

second_line_img.append(image.crop((abs(pos['x']), 0, abs(pos['x']) + 10, 58)))

return first_line_img, second_line_img

5.拼接处正确图片

按两行逐次拼接,使用 paste()方法按照位置信息得到正确图片

# 拼接大图

def put_imgs_together(first_line_img, second_line_img, img_name):

'''

:param first_line_img: (List)第一行图片位置信息

:param second_line_img: (List)第二行图片信息

:return: (Image)拼接后的正确顺序的图片

'''

# 新建一个图片,new()第一个参数是颜色模式,第二个是图片尺寸

image = Image.new('RGB', (260,116))

# 初始化偏移量为0

offset = 0

# 拼接第一行

for img in first_line_img:

# past()方法进行粘贴,第一个参数是被粘对象,第二个是粘贴位置

image.paste(img, (offset, 0))

# 偏移量对应增加移动到下一个图片位置,size[0]表示图片宽度

offset += img.size[0]

# 偏移量重置为0

x_offset = 0

# 拼接第二行

for img in second_line_img:

# past()方法进行粘贴,第一个参数是被粘对象,第二个是粘贴位置

image.paste(img, (x_offset, 58))

# 偏移量对应增加移动到下一个图片位置,size[0]表示图片宽度

x_offset += img.size[0]

# 保存图片

image.save(img_name)

# 返回图片对象

return image

此时,我们得到的就是位置正确的图片了

f07bdb2e2aa772d4de6d930ce44a01e4.png

fa3ba75806fd4eef5105192400b39f7c.png

以上,第一大步 获取验证图片 就算是完成了

第二大步:获取缺口位置

1.计算缺口位置(即滑块需要滑动的距离)

# 计算滑块移动距离

def get_distance(bg_image, fullbg_image):

'''

:param bg_image: (Image)缺口图片

:param fullbg_image: (Image)完整图片

:return: (Int)缺口离滑块的距离

'''

# 滑块的初始位置

distance = 57

# 遍历像素点横坐标

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

这其中有个is_pixel_equal()方法用于判断是否为相同像素,从而判断是不是缺口位置

其代码如下:

# 判断像素是否相同

def is_pixel_equal(bg_image, fullbg_image, x, y):

"""

:param bg_image: (Image)缺口图片

:param fullbg_image: (Image)完整图片

:param x: (Int)位置x

:param y: (Int)位置y

:return: (Boolean)像素是否相同

"""

# 获取缺口图片的像素点(按照RGB格式)

bg_pixel = bg_image.load()[x, y]

# 获取完整图片的像素点(按照RGB格式)

fullbg_pixel = fullbg_image.load()[x, y]

# 设置一个判定值,像素值之差超过判定值则认为该像素不相同

threshold = 60

# 判断像素的各个颜色之差,abs()用于取绝对值

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 True

else:

# 如果差值在判断值之外,返回不是相同像素

return False

第三大步:模拟拖动

1.构造模拟人类的滑块移动轨迹:先快后慢,有对准时间

# 构造滑动轨迹

def get_trace(distance):

'''

:param distance: (Int)缺口离滑块的距离

:return: (List)移动轨迹

'''

# 创建存放轨迹信息的列表

trace = []

# 设置加速的距离

faster_distance = distance*(4/5)

# 设置初始位置、初始速度、时间间隔

start, v0, t = 0, 0, 0.2

# 当尚未移动到终点时

while start < distance:

# 如果处于加速阶段

if start < faster_distance:

# 设置加速度为2

a = 1.5

# 如果处于减速阶段

else:

# 设置加速度为-3

a = -3

# 移动的距离公式

move = v0 * t + 1 / 2 * a * t * t

# 此刻速度

v = v0 + a * t

# 重置初速度

v0 = v

# 重置起点

start += move

# 将移动的距离加入轨迹列表

trace.append(round(move))

# 返回轨迹信息

return trace

2.利用selenium拖拽滑块

# 模拟拖动

def move_to_gap(trace):

# 得到滑块标签

slider = wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'gt_slider_knob')))

# 使用click_and_hold()方法悬停在滑块上,perform()方法用于执行

ActionChains(browser).click_and_hold(slider).perform()

for x in trace:

# 使用move_by_offset()方法拖动滑块,perform()方法用于执行

ActionChains(browser).move_by_offset(xoffset=x, yoffset=0).perform()

# 模拟人类对准时间

sleep(0.5)

# 释放滑块

ActionChains(browser).release().perform()

结果展示:

e226f3a5ee2947f6925b97dccdacd8e9.png

程序结构图

802adea6ba75c45d5325e476d7a4917f.png

完整代码

from selenium import webdriver

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

from urllib.request import urlretrieve

from bs4 import BeautifulSoup

import re

from PIL import Image

from time import sleep

# 初始化

def init():

# 定义为全局变量,方便其他模块使用

global url, browser, username, password, wait

# 登录界面的url

url = 'https://passport.bilibili.com/login'

# 实例化一个chrome浏览器

browser = webdriver.Chrome()

# 用户名

username = '***********'

# 密码

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)

# 获取图片信息

def get_image_info(img):

'''

:param img: (Str)想要获取的图片类型:带缺口、原始

:return: 该图片(Image)、位置信息(List)

'''

# 将网页源码转化为能被解析的lxml格式

soup = BeautifulSoup(browser.page_source, 'lxml')

# 获取验证图片的所有组成片标签

imgs = soup.find_all('div', {'class': 'gt_cut_'+img+'_slice'})

# 用正则提取缺口的小图片的url,并替换后缀

img_url = re.findall('url\(\"(.*)\"\);', imgs[0].get('style'))[0].replace('webp', 'jpg')

# 使用urlretrieve()方法根据url下载缺口图片对象

urlretrieve(url=img_url, filename=img+'.jpg')

# 生成缺口图片对象

image = Image.open(img+'.jpg')

# 获取组成他们的小图片的位置信息

position = get_position(imgs)

# 返回图片对象及其位置信息

return image, position

# 获取小图片位置

def get_position(img):

'''

:param img: (List)存放多个小图片的标签

:return: (List)每个小图片的位置信息

'''

img_position = []

for small_img in img:

position = {}

# 获取每个小图片的横坐标

position['x'] = int(re.findall('background-position: (.*)px (.*)px;', small_img.get('style'))[0][0])

# 获取每个小图片的纵坐标

position['y'] = int(re.findall('background-position: (.*)px (.*)px;', small_img.get('style'))[0][1])

img_position.append(position)

return img_position

# 裁剪图片

def Corp(image, position):

'''

:param image:(Image)被裁剪的图片

:param position: (List)该图片的位置信息

:return: (List)存放裁剪后的每个图片信息

'''

# 第一行图片信息

first_line_img = []

# 第二行图片信息

second_line_img = []

for pos in position:

if pos['y'] == -58:

first_line_img.append(image.crop((abs(pos['x']), 58, abs(pos['x']) + 10, 116)))

if pos['y'] == 0:

second_line_img.append(image.crop((abs(pos['x']), 0, abs(pos['x']) + 10, 58)))

return first_line_img, second_line_img

# 拼接大图

def put_imgs_together(first_line_img, second_line_img, img_name):

'''

:param first_line_img: (List)第一行图片位置信息

:param second_line_img: (List)第二行图片信息

:return: (Image)拼接后的正确顺序的图片

'''

# 新建一个图片,new()第一个参数是颜色模式,第二个是图片尺寸

image = Image.new('RGB', (260,116))

# 初始化偏移量为0

offset = 0

# 拼接第一行

for img in first_line_img:

# past()方法进行粘贴,第一个参数是被粘对象,第二个是粘贴位置

image.paste(img, (offset, 0))

# 偏移量对应增加移动到下一个图片位置,size[0]表示图片宽度

offset += img.size[0]

# 偏移量重置为0

x_offset = 0

# 拼接第二行

for img in second_line_img:

# past()方法进行粘贴,第一个参数是被粘对象,第二个是粘贴位置

image.paste(img, (x_offset, 58))

# 偏移量对应增加移动到下一个图片位置,size[0]表示图片宽度

x_offset += img.size[0]

# 保存图片

image.save(img_name)

# 返回图片对象

return image

# 判断像素是否相同

def is_pixel_equal(bg_image, fullbg_image, x, y):

"""

:param bg_image: (Image)缺口图片

:param fullbg_image: (Image)完整图片

:param x: (Int)位置x

:param y: (Int)位置y

:return: (Boolean)像素是否相同

"""

# 获取缺口图片的像素点(按照RGB格式)

bg_pixel = bg_image.load()[x, y]

# 获取完整图片的像素点(按照RGB格式)

fullbg_pixel = fullbg_image.load()[x, y]

# 设置一个判定值,像素值之差超过判定值则认为该像素不相同

threshold = 60

# 判断像素的各个颜色之差,abs()用于取绝对值

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 True

else:

# 如果差值在判断值之外,返回不是相同像素

return False

# 计算滑块移动距离

def get_distance(bg_image, fullbg_image):

'''

:param bg_image: (Image)缺口图片

:param fullbg_image: (Image)完整图片

:return: (Int)缺口离滑块的距离

'''

# 滑块的初始位置

distance = 57

# 遍历像素点横坐标

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 get_trace(distance):

'''

:param distance: (Int)缺口离滑块的距离

:return: (List)移动轨迹

'''

# 创建存放轨迹信息的列表

trace = []

# 设置加速的距离

faster_distance = distance*(4/5)

# 设置初始位置、初始速度、时间间隔

start, v0, t = 0, 0, 0.2

# 当尚未移动到终点时

while start < distance:

# 如果处于加速阶段

if start < faster_distance:

# 设置加速度为2

a = 1.5

# 如果处于减速阶段

else:

# 设置加速度为-3

a = -3

# 移动的距离公式

move = v0 * t + 1 / 2 * a * t * t

# 此刻速度

v = v0 + a * t

# 重置初速度

v0 = v

# 重置起点

start += move

# 将移动的距离加入轨迹列表

trace.append(round(move))

# 返回轨迹信息

return trace

# 模拟拖动

def move_to_gap(trace):

# 得到滑块标签

slider = wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'gt_slider_knob')))

# 使用click_and_hold()方法悬停在滑块上,perform()方法用于执行

ActionChains(browser).click_and_hold(slider).perform()

for x in trace:

# 使用move_by_offset()方法拖动滑块,perform()方法用于执行

ActionChains(browser).move_by_offset(xoffset=x, yoffset=0).perform()

# 模拟人类对准时间

sleep(0.5)

# 释放滑块

ActionChains(browser).release().perform()

# 主程序

def main():

# 初始化

init()

# 登录

login()

# 获取缺口图片及其位置信息

bg, bg_position = get_image_info('bg')

# 获取完整图片及其位置信息

fullbg, fullbg_position = get_image_info('fullbg')

# 将混乱的缺口图片裁剪成小图,获取两行的位置信息

bg_first_line_img, bg_second_line_img = Corp(bg, bg_position)

# 将混乱的完整图片裁剪成小图,获取两行的位置信息

fullbg_first_line_img, fullbg_second_line_img = Corp(fullbg, fullbg_position)

# 根据两行图片信息拼接出缺口图片正确排列的图片

bg_image = put_imgs_together(bg_first_line_img, bg_second_line_img, 'bg.jpg')

# 根据两行图片信息拼接出完整图片正确排列的图片

fullbg_image = put_imgs_together(fullbg_first_line_img, fullbg_second_line_img, 'fullbg.jpg')

# 计算滑块移动距离

distance = get_distance(bg_image, fullbg_image)

# 计算移动轨迹

trace = get_trace(distance-10)

# 移动滑块

move_to_gap(trace)

sleep(5)

# 程序入口

if __name__ == '__main__':

main()

github: https://github.com/JeesonZhang/pythonspider/blob/master/bilibili_crack

相关文章:

  • qmediaplayer进度_QMediaPlayer的duration问题
  • java timer定时执行一次_Java Timer(定时调用、实现固定时间执行)
  • java字串数组_java字符串数组
  • java swing 拖拽文件夹_Java Swing 鼠标拖放文件 代码1
  • java treemap 降序排序_Java TreeMap 升序|降序排列
  • java 流关闭顺序_JAVA的节点流和处理流以及流的关闭顺序
  • java 多层结构故障_多层构架在实践中一些问题
  • java项目提高安全性_Java线程安全与程序性能
  • mysql 获取真是执行计划_Oracle 从缓存里面查找真实的执行计划
  • mysql 越文_mysql数据库乱码之保存越南文乱码解决方法_MySQL
  • java发布_java项目发布的方式
  • python分享的代码怎么写_【图片】分享一段功能非常简陋的python代码实现下载free种【pt吧】_百度贴吧...
  • android js调用java_如何在Android平台上使用JS直接调用Java方法
  • python实现并发和并行的方式有哪些_Python中的并行和并发是什么
  • java a a=null_面试题((A)null).fun()——java中null值的强转
  • EventListener原理
  • js如何打印object对象
  • js写一个简单的选项卡
  • k8s 面向应用开发者的基础命令
  • ubuntu 下nginx安装 并支持https协议
  • vue-cli3搭建项目
  • 从零到一:用Phaser.js写意地开发小游戏(Chapter 3 - 加载游戏资源)
  • 等保2.0 | 几维安全发布等保检测、等保加固专版 加速企业等保合规
  • 多线程事务回滚
  • 基于axios的vue插件,让http请求更简单
  • 基于MaxCompute打造轻盈的人人车移动端数据平台
  • 基于Vue2全家桶的移动端AppDEMO实现
  • 七牛云 DV OV EV SSL 证书上线,限时折扣低至 6.75 折!
  • 如何编写一个可升级的智能合约
  • 提升用户体验的利器——使用Vue-Occupy实现占位效果
  • 详解移动APP与web APP的区别
  • 一个普通的 5 年iOS开发者的自我总结,以及5年开发经历和感想!
  • #Linux(make工具和makefile文件以及makefile语法)
  • #数学建模# 线性规划问题的Matlab求解
  • #图像处理
  • ${factoryList }后面有空格不影响
  • (3)(3.2) MAVLink2数据包签名(安全)
  • (C#)获取字符编码的类
  • (Mac上)使用Python进行matplotlib 画图时,中文显示不出来
  • (阿里巴巴 dubbo,有数据库,可执行 )dubbo zookeeper spring demo
  • (八)Spring源码解析:Spring MVC
  • (附表设计)不是我吹!超级全面的权限系统设计方案面世了
  • (附源码)php投票系统 毕业设计 121500
  • (附源码)springboot工单管理系统 毕业设计 964158
  • (力扣)循环队列的实现与详解(C语言)
  • (六)vue-router+UI组件库
  • (强烈推荐)移动端音视频从零到上手(上)
  • (图)IntelliTrace Tools 跟踪云端程序
  • (转)Android中使用ormlite实现持久化(一)--HelloOrmLite
  • .axf 转化 .bin文件 的方法
  • .cfg\.dat\.mak(持续补充)
  • .net core 6 集成和使用 mongodb
  • .NET/C# 在代码中测量代码执行耗时的建议(比较系统性能计数器和系统时间)
  • /etc/sudoers (root权限管理)
  • @RequestBody的使用