Python网络爬虫基础
验证码处理
一. OCR识别
OCR(Optical Character Recognition)是指使用扫描仪或数码相机对文本资料进行扫描成图像文件,然后对图像文件进行分析处理,自动识别获取文字信息及版面信息的软件
。
在读取和处理图像、图像相关的机器学习以及创建图像等任务中,Python 一直都是非常出色的语言。虽然有很多库可以进行图像处理,但在这里只重点介绍:Tesseract
1. Tesseract 引擎的安装
windows引擎环境安装
安装包下载链接:
https://wwae.lanzoub.com/b04k0prcj
密码:26yo
- 下载引擎安装包,直接安装。安装方式:–>直接双击安装包 --> 选择安装路径 --> 后续所有选项点 “下一步” 安装。
- 配置环境变量,安装完后如果要在计算机正常使用需要在计算机中配置环境变量,步骤如下所示:
- 右键点击此电脑, 选择属性
- 选择点击**高级系统设置
- 选择点击**环境变量
- 在系统变量中双击Path
- 将Tesseract引擎安装的根路径添加到环境变量中去
- 依次点击确定使环境变量生效
- 打开cmd验证,输入**
tesseract -v
**验证,出现如下说明你配置好了
Mac系统引擎环境安装
安装 tesseract
brew install tesseract
==> Installing dependencies for tesseract: libarchive
==> Installing tesseract dependency: libarchive
==> Pouring libarchive-3.6.1.catalina.bottle.tar.gz
🍺 /usr/local/Cellar/libarchive/3.6.1: 62 files, 3.6MB
==> Installing tesseract
==> Pouring tesseract--5.1.0.catalina.bottle.tar.gz
==> Caveats
This formula contains only the "eng", "osd", and "snum" language data files.
If you need any other supported languages, run `brew install tesseract-lang`.
==> Summary
🍺 /usr/local/Cellar/tesseract/5.1.0: 58 files, 30.0MB
==> Caveats
==> tesseract
This formula contains only the "eng", "osd", and "snum" language data files.
If you need any other supported languages, run `brew install tesseract-lang`.
查看 tesseract 版本
成功安装后查看 tesseract 版本
tesseract --version
tesseract 5.1.0
leptonica-1.82.0
libgif 5.2.1 : libjpeg 9e : libpng 1.6.37 : libtiff 4.3.0 : zlib 1.2.11 : libwebp 1.2.2 : libopenjp2 2.4.0
Found AVX2
Found AVX
Found FMA
Found SSE4.1
Found libarchive 3.6.1 zlib/1.2.11 liblzma/5.2.5 bz2lib/1.0.6 liblz4/1.9.3 libzstd/1.5.2
Found libcurl/7.64.1 SecureTransport (LibreSSL/2.8.3) zlib/1.2.11 nghttp2/1.39.2
安装过程遇到的报错解决方法
错误一:
- 安装tesseract的过程中报缺少依赖的错误
- Error: No such file or directory @ rb_sysopen - /Users/f/Library/Caches/Homebrew/downloads/266702d9bc59c9dfde27ce555b4a3f9ed9d0de770ba697e62a111d74ee0a4231–openjpeg-2.4.0.catalina.bottle.tar.gz
- 针对这类错误单独安装缺少的包即可
- brew install openjpeg
错误二:
- 单独安装依赖出现如下提示:
- Disable this behaviour by setting HOMEBREW_NO_INSTALL_CLEANUP. Hide these hints with HOMEBREW_NO_ENV_HINTS (see man brew).
- 执行如下命令即可: export HOMEBREW_NO_INSTALL_CLEANUP=TRUE
下载中文包
- tesseract默认不支持中文,需要单独下载中文包
- 中文包下载地址: https://tesseract-ocr.github.io/tessdoc/Data-Files
中文包存放目录
/usr/local/Cellar/tesseract/{tesseract版本}/share/tessdata
cd /usr/local/Cellar/tesseract/5.1.0/share/tessdata
查看全部语言库
tesseract --list-langs
List of available languages in "/usr/local/share/tessdata/" (4):
chi_sim
eng
osd
snum
python 安装 pytesseract 和 pillow
pip install pytesseract
pip install pillow
识别图片中文字体
import pytesseract
from PIL import Image
# 读取图片 # 打开对应图片的文件路径
im = Image.open('/Users/f/PycharmProjects/firstProject/a/a.png')
# 识别文字,并指定语言
string = pytesseract.image_to_string(im, lang='chi_sim')
print(string)
Linux系统引擎环境安装
可以通过指令在线安装
sudo apt-get update
sudo apt-get install tesseract-ocr
2. Tesseract 中文语言模型配置
Tesseract各个国家语言地区文字模型在GitHub可以自行下载, 以下是下载链接:
- 中文语言模型下载网址:https://github.com/tesseract-ocr/tessdata
Windows配置中文模型
中文语言模型配置主要是要将模型文件放到引擎的安装目录下
将中文模型放到引擎的 安装目录/tessdata 的目录下
linux配置中文模型
Linux
系统下通过apt get
指令安装的工具默认是在~/etc
下,进入到tesseract安装路径下share下面的tessdata文件夹下,将中文语言包拷贝进去即可
3. Tesserract识别
识别英文
from PIL import Image # pillow模块 安装名和导入名不一样
import pytesseract
# 当找不到识别引擎的情况下, 加一下代码
# pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe'
# 打开加载英文内容图片
image = Image.open('test_english.png')
# 识别图片中的文字, 默认语言模型是英文模型
result = pytesseract.image_to_string(image)
# 打印识别结果
print(result)
"""
默认情况下只要是计算机敲出来的文字, 用这个方法识别率是 100%
"""
识别中文
''' 首先要中文语言模型配置 '''
from PIL import Image # pillow模块 安装名和导入名不一样
import pytesseract
# 打开加载图片
image = Image.open('test_chinese.png')
# 识别图片中的文字
# lang="chi_sim" 指定中文语言模型进行识别
result = pytesseract.image_to_string(image, lang='chi_sim')
# 打印识别结果
print(result)
识别验证码
from PIL import Image # pillow模块 安装名和导入名不一样
import pytesseract
# 打开加载图片
image = Image.open('yzm1.png')
# 识别图片中的文字
# lang="chi_sim" 指定中文语言模型进行识别
result = pytesseract.image_to_string(image)
# 打印识别结果
print(result)
"""
因为我们没有验证码模型, 所以识别验证码图片会识别率会非常低
"""
二. ddddocn 识别
1. 图片的形式和转化
图片在网页页面中的形式
图片在网页页面中的形式一般就两种:一种是以一个链接形式存在html中,另外一种是以字符串的形式存在于html中。
以链接形式存在于html中的图片,意味着每次浏览器执行渲染的时候会发送图片链接地址请求,请求到了之后再渲染到页面。目前大部分网站都是以这种形式去加载图片的。之前我们也学习过通过获取图片的链接地址就可以请求图片数据。
这样的形式存在于页面当中的图片,如果一旦页面图片很多的话,就意味着需要发送很多次网络请求,去请求图片数据。
以字符串形式存在于html中的图片,和上述形式有明显的区别。这样形式的图片在html页面中是以字符串的形式去展示图片,而不是一个链接。
- 这样的好处是加载页面的时候,不用发送网络请求去请求图片数据。因为我们在请求html页面的时候就已经拿到图片的字符串数据,浏览器只需要转换图片为二进制形式展示就可以了。
- 有利也有弊,这样做虽然网络请求的次数少了,但是我们需要把图片转换成字符串形式才可以放到标签里面。如果图片数据过大,那么转换之后的字符长度就会很长。所以一般网页中字符串形式的图片都是数据量比较小的图片,比如验证码。
如何进行图片形式的转化
前面我们讲了图片有两种形式,链接形式的图片我们通过requests发送请求就可以拿到图片的二进制数据,保存下来就可以用看图软件查看。这种形式的图片处理就不做赘述了。
- 那么字符串的形式的图片我们应该如何处理呢?
- 字符串形式的图片我们可以借助 base64 模块进行处理。
# base64.b64decode()
把字符串形式的图片转化为二进制的数据, 传入图片的字符串数据
# base64.b64encode()
把二进制形式的图片转化为字符串数据, 传入图片的二进制数据
2. 字符串图片转化
import base64 # 内置模块
"""把字符串形式的图片转化成二进制"""
img_str = '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'
# b64decode() 把字符串形式的图片转化成二进制
bytes_img = base64.b64decode(img_str)
print(bytes_img)
with open('yzm.png', mode='wb') as f:
f.write(bytes_img)
""" 把二进制形式的图片转换成字符串 """
with open('yzm.png', mode='rb') as f:
img_bytes_data = f.read()
print('读取出的二进制形式的图片: ', img_bytes_data)
# b64encode() 把二进制形式的图片成字符串
str_img = base64.b64encode(img_bytes_data).decode()
print('转换之后的字符串图片: ', str_img)
3. ddddcon识别
import ddddocr
"""创建识别对象"""
ocr = ddddocr.DdddOcr(beta=True) # beta=True 自动识别模型
"""打开图片数据"""
with open('yzm.png', mode='rb') as f:
image = f.read()
"""识别验证码"""
result = ocr.classification(image)
print(result)
"""
只要验证码没有覆盖, 识别率还行
"""
# 数字验证码
ocr = ddddocr.DdddOcr(beta=True)
with open('test.png', mode='rb') as f:
image = f.read()
result2 = ocr.classification(image)
print(result2)
# 英文验证码
ocr = ddddocr.DdddOcr(beta=True)
with open('test2.png', mode='rb') as f:
image = f.read()
result2 = ocr.classification(image)
print(result2)
# 英数混合验证码
ocr = ddddocr.DdddOcr(beta=True)
with open('test3.png', mode='rb') as f:
image = f.read()
result3 = ocr.classification(image)
print(result3)
# 验证码覆盖识别测试
ocr = ddddocr.DdddOcr(beta=True)
with open('验证码4.png', mode='rb') as f:
image = f.read()
result4 = ocr.classification(image)
print(result4)
4. 青灯论坛模拟登录对接
"""
时间戳: 格林威治时间1970年1月1日0时0分0秒开始 到 目前 位置所消耗的时间数
秒级时间戳: 10为数字
毫秒级时间戳: 13为数字
微秒级时间戳: 16为数字
"""
import time
import ddddocr
import requests
def get_time():
"""获取时间戳的函数"""
now_time = str(int(time.time() * 1000))
print('当前时间戳为:', now_time)
return now_time
cookies = {'seesion': 'vnrasebgvi'}
# 创建一个会话位置对象
session = requests.Session()
"""请求验证码, 保存"""
img_time = get_time()
img_url = 'http://118.126.88.143:5000/login/captcha?image_code=' + img_time
print('图片地址:', img_url)
# 使用回话维持对象发送请求
img_response = session.get(url=img_url, cookies=cookies).content
with open('yzm.png', mode='wb') as f:
f.write(img_response)
# # 手动识别验证码
# img_code = input('请输入验证码:')
# print('您输入的验证码为:', img_code)
"""ddddocr识别"""
ocr = ddddocr.DdddOcr(beta=True)
with open('yzm.png', mode='rb') as f:
image = f.read()
img_code = ocr.classification(image)
print(img_code)
"""构建登录请求"""
login_url = 'http://118.126.88.143:5000/api/private/v1/login'
json_data = {
"image_code": img_time,
"username": "admin",
"password": "123456",
"captcha_code": img_code # 手动验证码
}
# 使用回话维持对象维持用户的登录状态
login_response = session.post(url=login_url, json=json_data)
print(login_response.cookies.get_dict())
print(login_response.json())
# 其他网站构建请求联系, 一般是通过cookies字段
5. ddddcon中文识别
import time
import ddddocr
import requests
"""ddddocr识别"""
ocr = ddddocr.DdddOcr(beta=True)
with open('test_chinese.png', mode='rb') as f:
image = f.read()
img_code = ocr.classification(image)
print(img_code)
# 后续可以识别字体图片
三、打码平台识别
- 图鉴平台:http://www.ttshitu.com/ (推荐)
- 使用图鉴识别古诗文网登录中的验证码
'''
使用流程:
- 注册登录图鉴平台
- 登录后,点击开发文档,提取识别的源代码
- 模块(tujian.py)的封装:
'''
import base64
import json
import requests
# 一、图片文字类型(默认 3 数英混合):
# 1 : 纯数字
# 1001:纯数字2
# 2 : 纯英文
# 1002:纯英文2
# 3 : 数英混合
# 1003:数英混合2
# 4 : 闪动GIF
# 7 : 无感学习(独家)
# 11 : 计算题
# 1005: 快速计算题
# 16 : 汉字
# 32 : 通用文字识别(证件、单据)
# 66: 问答题
# 49 :recaptcha图片识别
# 二、图片旋转角度类型:
# 29 : 旋转类型
#
# 三、图片坐标点选类型:
# 19 : 1个坐标
# 20 : 3个坐标
# 21 : 3 ~ 5个坐标
# 22 : 5 ~ 8个坐标
# 27 : 1 ~ 4个坐标
# 48 : 轨迹类型
#
# 四、缺口识别
# 18 : 缺口识别(需要2张图 一张目标图一张缺口图)
# 33 : 单缺口识别(返回X轴坐标 只需要1张图)
# 五、拼图识别
# 53:拼图识别
# 函数实现忽略
def base64_api(uname, pwd, img, typeid):
with open(img, 'rb') as f:
base64_data = base64.b64encode(f.read())
b64 = base64_data.decode()
data = {"username": uname, "password": pwd, "typeid": typeid, "image": b64}
result = json.loads(requests.post("http://api.ttshitu.com/predict", json=data).text)
if result['success']:
return result["data"]["result"]
else:
return result["message"]
return ""
def getImgCodeText(imgPath, imgType): # 直接返回验证码内容
# imgPath:验证码图片地址
# imgType:验证码图片类型
result = base64_api(uname='bb328410948', pwd='bb328410948', img=imgPath, typeid=imgType)
return result
案例 - (古诗文)验证码图片识别
from lxml import etree
import requests
import tujian
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36'
}
# 将验证码图片请求后保存到本地
login_url = 'https://so.gushiwen.cn/user/login.aspx?from=http://so.gushiwen.cn/user/collect.aspx'
page_text = requests.get(url=login_url, headers=headers).text
tree = etree.HTML(page_text)
img_src = 'https://so.gushiwen.cn' + tree.xpath('//*[@id="imgCode"]/@src')[0]
code_data = requests.get(url=img_src, headers=headers).content
with open('./code.jpg', 'wb') as fp:
fp.write(code_data)
# 识别验证码图片内容
result = tujian.getImgCodeText('./code.jpg', 3)
print(result)
在抓包工具里定位点击登录按钮后对应的数据包:
- 只要数据包的请求参数中包含用户名,密码和验证码则该数据包就是我们要定位的
- 首次模拟登录操作:
from lxml import etree
import requests
import tujian
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36'
}
# 将验证码图片请求后保存到本地
login_url = 'https://so.gushiwen.cn/user/login.aspx?from=http://so.gushiwen.cn/user/collect.aspx'
page_text = requests.get(url=login_url, headers=headers).text
tree = etree.HTML(page_text)
img_src = 'https://so.gushiwen.cn' + tree.xpath('//*[@id="imgCode"]/@src')[0]
code_data = requests.get(url=img_src, headers=headers).content
with open('./code.jpg', 'wb') as fp:
fp.write(code_data)
# 识别验证码图片内容
result = tujian.getImgCodeText('./code.jpg', 3)
print(result)
# 模拟登录
url = 'https://so.gushiwen.cn/user/login.aspx?from=http%3a%2f%2fso.gushiwen.cn%2fuser%2fcollect.aspx'
data = {
"__VIEWSTATE": "opfVI7oolwkr7MLRVzsNSMASqLRUuO1dg5ZP5EIRa4FyM+mOYKEs6KWEKQKaba2ulLoZQIaLFiKK4mr5K3ci1v8ua28wtcRtabKWjOtJtU/i2etH+zSduegTMcg=",
"__VIEWSTATEGENERATOR": "C93BE1AE",
"from": "http://so.gushiwen.cn/user/collect.aspx",
"email": "15027900535",
"pwd": "bobo@15027900535",
"code": result,
"denglu": "登录"
}
# 获取了登录成功后的页面源码数据
login_page_text = requests.post(url=url, headers=headers, data=data).text
with open('wushiwen.html', 'w') as fp:
fp.write(login_page_text)
1. 快识别打码平台测试
import base64
import json
import requests
from constants import KUAI_USERNAME, KUAI_PASSWORD
def base64_api(uname, pwd, img, typeid):
"""
识别验证码的函数
:param uname: 用户名
:param pwd: 密码
:param img: 图片路径
:param typeid: 识别类型
:return:
"""
with open(img, 'rb') as f:
# 打开图片图片, 把图片转换成字符串形式
base64_data = base64.b64encode(f.read())
b64 = base64_data.decode()
data = {"username": uname, "password": pwd, "typeid": typeid, "image": b64}
result = requests.post("http://api.ttshitu.com/predict", json=data).json()
print('识别返回结果:', result)
if result['success']:
return result["data"]["result"]
else:
# !!!!!!!注意:返回 人工不足等 错误情况 请加逻辑处理防止脚本卡死 继续重新 识别
return result["message"]
if __name__ == "__main__":
img_path = "验证码4.png"
result = base64_api(uname=KUAI_USERNAME, pwd=KUAI_PASSWORD, img=img_path, typeid=7)
print(result)
# 当我们识别错误可以, 可以换识别类型
2. 凤凰网登录
import base64
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from constants import FENG_USERNAME, FENG_PASSWORD
driver = webdriver.Chrome()
driver.get('https://www.ifeng.com/')
driver.implicitly_wait(10)
driver.maximize_window()
"""找到账号登录点击"""
driver.find_element(By.CSS_SELECTOR, '.login_in_2x-3NxtSKIw').click()
time.sleep(2)
"""注意一定要进入嵌套网页"""
iframe_label = driver.find_element(By.CSS_SELECTOR, '.box-1pZSPyeN>div:nth-child(2)>iframe')
driver.switch_to.frame(iframe_label)
"""点击账号登录"""
driver.find_element(By.CSS_SELECTOR, '.index_tab_FDzng>span:nth-child(1)').click()
time.sleep(1)
"""找用户名和密码框, 输入数据"""
driver.find_element(By.NAME, 'text').send_keys(FENG_USERNAME)
time.sleep(0.5)
driver.find_element(By.NAME, 'password').send_keys(FENG_PASSWORD)
time.sleep(0.5)
"""获取验证码"""
img_label = driver.find_element(By.CSS_SELECTOR, '.index_codeImg_6TcnD>img')
img_str = img_label.get_attribute('src')
print('全部的图片标签数据:', img_str)
base64_str = img_str.split(',')[-1]
print('base64字符串形式的图片:', base64_str)
# 把字符串形式的图片转换成二进制
bytes_img = base64.b64decode(base64_str)
print(bytes_img)
with open('yzm.png', mode='wb') as f:
f.write(bytes_img)
print('验证码保存完毕')
"""调用打码平台识别验证码"""
from img_api import base64_api
code_result = base64_api('yzm.png', 7)
print('验证码识别结果:', code_result)
"""输入验证码"""
driver.find_element(By.CSS_SELECTOR, '.index_input_Lm1EX input').send_keys(code_result)
time.sleep(2)
# 点击登录
driver.find_element(By.CSS_SELECTOR, '.index_submmitBtn_Xd39V').click()
time.sleep(2)
input()
driver.quit()
3. B站点选验证码
import base64
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from constants import BILIBILI_USERNAME, BILIBILI_PASSWORD
from selenium.webdriver import ActionChains
driver = webdriver.Chrome()
driver.get('https://passport.bilibili.com/login')
driver.implicitly_wait(10)
driver.maximize_window()
"""找用户名和密码框, 输入数据"""
driver.find_element(By.XPATH, '//input[@placeholder="请输入账号"]').send_keys(BILIBILI_USERNAME)
time.sleep(2)
driver.find_element(By.XPATH, '//input[@placeholder="请输入密码"]').send_keys(BILIBILI_PASSWORD)
time.sleep(2)
"""点击登录按钮"""
driver.find_element(By.CSS_SELECTOR, '.btn_wp>div:nth-child(2)').click()
time.sleep(2)
"""使用selenium标签对象保存图片"""
img_label = driver.find_element(By.CSS_SELECTOR, 'body>div:last-of-type .geetest_holder.geetest_silver')
img_label.screenshot('yzm2.png')
print('正在保存验证码...')
"""识别图验证码"""
from img_api import base64_api
code_result_list = base64_api('yzm2.png', 21)
print('验证码识别结果为:', code_result_list) # 173,262|112,139|254,224
result_list = code_result_list.split('|') # ['173,262', '112,139', '254,224']
"""
4.0+版本的move_to_element_with_offset方法会以元素中心为基准进行偏移,而4.0版本会以左上角顶点为基准进行偏移
既然是基于中心的偏移,我们只需要获取页面点选图片元素后,获取其长度和宽度,
再在move_to_element_with_offset的后两个参数中,减去一半的长度、宽度再进行偏移就好了。
"""
code_label_half_width = img_label.rect['width'] / 2 # 验证码标签对象一半的宽度
code_label_half_height = img_label.rect['height'] / 2 # 验证码标签对象一半的宽度
for result in result_list:
x = int(result.split(',')[0]) # x轴 str 转 int
y = int(result.split(',')[1]) # y轴 str 转 int
# # move_to_element_with_offset 根据元素执行点击操作
# move_to_element_with_offset 会以元素中心为基准进行偏移
ActionChains(driver).move_to_element_with_offset(
img_label, # 验证码标签对象
x - code_label_half_width, # 计算 x 轴点的位置
y - code_label_half_height # 计算 y 轴点的位置
).click().perform()
# 点击确认
driver.find_element(By.CSS_SELECTOR, '.geetest_commit_tip').click()
input()
driver.quit()
四、滑块验证码
1. 简单滑块
import time
from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
# 绕过检测的代码要放到实例化浏览器对象下面
# 修改selenium打开浏览器的属性特征
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": """
Object.defineProperty(navigator, 'webdriver', {
get: () => false
})
"""
})
driver.get('https://kyfw.12306.cn/otn/resources/login.html')
driver.implicitly_wait(10)
driver.maximize_window()
"""模拟用户名和密码输入"""
driver.find_element(By.CSS_SELECTOR, '#J-userName').send_keys('19999999999')
driver.find_element(By.CSS_SELECTOR, '#J-password').send_keys('1234567890')
driver.find_element(By.CSS_SELECTOR, '#J-login').click()
time.sleep(1)
# 定位滑块元素
h = driver.find_element(By.CSS_SELECTOR, '#nc_1_n1z')
"""使用鼠标动作链滑动"""
action = ActionChains(driver)
action.click_and_hold(h) # click_and_hold 点击按住元素并且保持
action.move_by_offset(340, 0)
# action.move_by_offset(85, 0)
# action.move_by_offset(85, 0)
# action.move_by_offset(85, 0)
# release() 松开鼠标
action.release().perform()
input('阻塞, 回车继续:')
driver.quit()
"""
如果加常规的染过检测的代码还是过不了滑块
那么可以使用本地浏览器 或者 用绕过流行特征的方式, 染过服务器检测
参考: https://blog.csdn.net/weixin_45081575/article/details/126585575
绕过检测方式:
1. driver.execute_cdp_cmd("
2. 刷js文件, 将浏览器的其他特征刷掉
3. 操作本地浏览器
"""
2. 极验验证
分段类型
获取验证码图片
修改网页前端属性显示全部图片
因为在此案例网站中, 滑动验证码的图片默认是隐藏的,所以不方便我们查看分析。可以去进行验证码的滑动,查看在滑动的过程中前端的标签属性哪里会发生变化,然后手动修改变化后的属性值,让验证码显示在页面上来。
- 没滑动前标签样式是这样的:注意查看 div标签的
gt_widget gt_hide
这个类属性
- 滑动后div标签属性发生了变化,其中
gt_hide
属性变成了gt_show
基于上述情况, 可以在Elements元素面板中将
gt_hide
修改成gt_show
即可查看到显示出来的滑动验证码,鼠标双击此属性即可键入修改。
但是修改后有时效性,后续有需要的话需要重复多次修改
下载验证码
这种类型的滑块验证码有一个特点,在前端中整个滑动验证码将图片分割成了若干份,并且在前端中将图片顺序打乱了。那么问题来了,为什么在前端网页中我们看到的图片是一个正常的图片呢?原因在于前端中将乱序的图片通过css偏移,让每个分割的图片显示到了特定的位置,从而用户看到的是一个正常的图片。
- 当使用元素选择工具选取验证码部分标签对象的时候,看到的就是一个一个分块的图片
- 在每个分块图片的div标签中,包含了整张图片的链接地址、分块图片的偏移数据,因此我们需要提取这些数据。整张图片的链接地址用于请求下载图片,下载的图片是乱序的,因此要根据分块图片的偏移量数据还原。
- 以下代码是下载验证码,并且提取分块图片偏移量的代码
def get_image(div_path, file_name):
"""
下载错乱的图片 获得所有图片的偏移值
:param div_path: 根据xpath提取图片,传入xpath规则
:param file_name: 保存的图片的文件名
:return: 返回图片的偏移值
"""
background_images = driver.find_elements(By.CSS_SELECTOR, div_path) # 根据css语法提取图片所在的标签
location_list = [] # 定义一个空列表, 后期存放所有图片偏移值的信息
for background_image in background_images:
location = {} # 定义一个空字典, 后以键值对的形式记录每一张图片的偏移量
result = re.findall('background-image: url\("(.*?)"\); background-position: (.*?)px (.*?)px;',
background_image.get_attribute('style')) # 根据每一个片段的图片对应得标签
location['x'] = int(result[0][1]) # 第一个偏移量用 x 做字典的第一个键
location['y'] = int(result[0][2]) # 第二个偏移量用 y 做字典的第二个键
image_url = result[0][0] # 取出匹配结果中图片的url地址, 后期需要下载图片
location_list.append(location)
# 替换图片 url 后缀, 避免在pycharm显示不了
image_url = image_url.replace('webp', 'jpg')
image_result = requests.get(image_url).content # 请求图片数据
with open(file_name, 'wb') as f:
f.write(image_result)
return location_list
-
要下载两张验证码图片, 一张是完整的验证码图片,一张是带缺口的验证码图片
- 以下标签是完整验证码图片对应的标签
- 以下标签是带缺口的验证码图片对应的标签
- 以下标签是完整验证码图片对应的标签
-
下载好的图片是乱序的图片
- 完整验证码如下所示:
- 带缺口的验证码如下所示:
- 验证码会实时刷新,所以课件中所示验证码不一样
- 完整验证码如下所示:
还原验证码图片
- 使用pillow模块对乱序图片进行分割,按照偏移量规则进行还原
根据上述操作咱们已经获取到两张验证码图片,但是图片显示出来是错乱乱序的。我们可以根据提取到的css偏移量对图片进行裁剪,然后贴到一张空白图像上对图片进行还原。具体代码如下:
def merge_image(image_file, location_list, restore_name):
"""
还原图片数据 还原图片
:param image_file: 传递一个图片文件路径
:param location_list: 传递图片的偏移量数据
:param restore_name: 还原图片后保存的文件名
:return: None
"""
im = Image.open(image_file) # 打开传入的文件对象
new_im = Image.new('RGB', (260, 116)) # 创建一个空白的图片
im_list_upper = [] # 存放上半部分的图片
im_list_down = [] # 存放下半部分的图片
for location in location_list: # 遍历 location_list 列表中的所有字典
# print('乱序图片的偏移量', location)
print(location)
if location['y'] == -58: # 上半边
# crop() 方法返一个图像的矩形区域,需要指定 "左上顶点" 和 "右下点" 的坐标,返回一个指定区域的图片对象
# abs() 方法是取数字的绝对值
im_list_upper.append(im.crop((abs(location['x']), 58, abs(location['x']) + 10, 116)))
if location['y'] == 0: # 下半边
im_list_down.append(im.crop((abs(location['x']), 0, abs(location['x']) + 10, 58)))
x_offset = 0
for im in im_list_upper:
new_im.paste(im, (x_offset, 0)) # 把小图片放到 新的空白图片上 放上半部分
x_offset += im.size[0]
x_offset = 0
for im in im_list_down:
new_im.paste(im, (x_offset, 58)) # 把小图片放到 新的空白图片上 放下半部分
x_offset += im.size[0]
new_im.save(restore_name)
return None
- 完整滑动验证码图片和带缺口的验证码图片都需要还原
- 还原后的完整验证码图片如下:
- 还原后的带缺口的验证码图片如下:
- 还原后的完整验证码图片如下:
获取验证码缺口距离
对比两张验证码图片,使用ddddocr调用接口识别出缺口距离,代码如下所示:
def get_gap(notch_img, all_img):
"""
验证码缺口距离识别, 获取缺口偏移量
:param notch_img: 带缺口的验证码文件路径
:param all_img: 完整的验证码文件路径
:return: 验证码缺口横向距离
"""
slide = ddddocr.DdddOcr(beta=True) # beta=True 通用识别
# 打开带缺口的图片
with open(notch_img, 'rb') as f:
target_bytes = f.read()
# 打开完整图片
with open(all_img, 'rb') as f:
background_bytes = f.read()
# slide_comparison(二进制带缺口图片, 二进制完整图片) --> 识别方法
res = slide.slide_comparison(target_bytes, background_bytes)
if res:
# 识别结果样式: {'target': [117, 72]} --> 其中列表中第一个数字就是缺口的横向距离
print("识别结果: ", res)
print("缺口的横向距离: ", res['target'][0])
else:
raise Exception('验证码识别失败')
return res['target'][0]
动验证码模拟滑动
使用pyautogui自动化工具做模拟滑动
PyAutoGUI 是一个面向人类的跨平台 GUI 自动化 Python 模块。用于以编程方式控制鼠标和键盘。
- 安装:
pip install pyautogui
- 英文文档:https://pyautogui.readthedocs.org
- 中文示例:https://github.com/asweigart/pyautogui/blob/master/docs/simplified-chinese.ipynb
- 依赖关系
macOS 需要安装 pyobjc-core 和 pyobjc 模块(按顺序)。
Linux 需要安装 python3-xlib(或 Python 2 的 python-xlib)模块。
需要安装 Pillow,在 Linux 上您可能需要安装额外的库以确保 Pillow 的 PNG/JPEG 正常工作
-
为什么使用pyautogui?
极验的滑动验证码并不是简单的将滑块滑动到缺口位置就能成功的,滑动到缺口位置只是其中一个必要条件,除此以外极验滑动验证码还会校验用户的滑动轨迹,如果滑动的轨迹校验不是一个正常的人为滑动轨迹,即使滑动到了缺口位置也不会成功通过。
鼠标动作链(ActionChains)是一个不错的选择,但是操作的是浏览器对象,即使轨迹构建好滑到缺口位置也会经常性的不能通过极验滑动验证码。
因此使用pyautogui操作系统的鼠标,构建好滑动轨迹,那么一般情况下极验验证码就很难做识别了。
-
具体具体代码实现如下:
def move_slide(offset_x, offset_y, left):
"""
执行滑块的移动
:param offset_x: 滑块的x轴坐标
:param offset_y: 滑块的y轴坐标
:param left: 需要移动的距离
:return:
"""
# 移动到滑块的位置
# duration为持续时间
# random.uniform(参数1,参数2) 返回参数1和参数2之间的任意值
pyautogui.moveTo(
offset_x,
offset_y,
duration=0.1 + random.uniform(0, 0.1 + random.randint(1, 100) / 100))
# 按下鼠标 准备开始滑动
pyautogui.mouseDown()
# random.randint(参数1, 参数2) 函数返回参数1和参数2之间的任意整数
offset_y += random.randint(9, 19)
pyautogui.moveTo(
offset_x + int(left * random.randint(15, 25) / 20),
offset_y,
duration=0.28)
offset_y += random.randint(-9, 0)
pyautogui.moveTo(
offset_x + int(left * random.randint(18, 22) / 20),
offset_y,
duration=random.randint(19, 31) / 100)
offset_y += random.randint(0, 8)
pyautogui.moveTo(
offset_x + int(left * random.randint(19, 21) / 20),
offset_y,
duration=random.randint(20, 40) / 100)
offset_y += random.randint(-3, 3)
pyautogui.moveTo(
left + offset_x + random.randint(-3, 3),
offset_y,
duration=0.5 + random.randint(-10, 10) / 100)
offset_y += random.randint(-2, 2)
pyautogui.moveTo(
left + offset_x + random.randint(-2, 2),
offset_y,
duration=0.5 + random.randint(-3, 3) / 100)
# 释放鼠标
pyautogui.mouseUp()
time.sleep(3)
练习案例
import random
import re
import time
import ddddocr
import requests
from PIL import Image
from selenium import webdriver
from selenium.webdriver.common.by import By
import pyautogui
def get_image(div_path, file_name):
"""
下载乱序图片, 获取每张小图的偏移量
:param div_path: 解析语法定位到图片的标签对象
:param file_name: 保存的文件名字
:return: 返回每一个小图的偏移量数据
"""
# 根据解析语法定位标签
background_images = driver.find_elements(By.CSS_SELECTOR, div_path)
location_list = [] # 定义空列表, 存放所有图片偏移量信息
for background_image in background_images:
location = {} # 定义一个空字典, 后续以键值对的形式记录每一个小图的哦爱你一辆
# 提取图片地址, 每一个小图的偏移量数据
result = re.findall('background-image: url\("(.*?)"\); background-position: (.*?)px (.*?)px;',
background_image.get_attribute('style'))
location['x'] = int(result[0][1]) # 第一个偏移量用 x 做字典的第一个键
location['y'] = int(result[0][2]) # 第二个偏移量用 y 做字典的第二个键
image_url = result[0][0] # 取出匹配到的图片链接, 后续请求保存图片
location_list.append(location)
# 替换图片 url 后缀, 避免在pycharm显示不了
image_url = image_url.replace('webp', 'jpg')
image_result = requests.get(image_url).content # 请求图片数据
with open(file_name, 'wb') as f:
f.write(image_result)
return location_list # 返回当前图片的偏移量信息
def merge_image(image_file, location_list, restore_name):
"""
还原乱序的图片
:param image_file: 文件名路径
:param location_list: 偏移量规则
:param restore_name: 还原后保存的图片名字
:return: None
"""
im = Image.open(image_file) # 打开传入的文件对象
new_im = Image.new('RGB', (260, 116)) # 创建一个空白的图片
im_list_upper = [] # 存放上半部分的图片
im_list_down = [] # 存放下半部分的图片
for location in location_list:
print(location)
if location['y'] == -58: # 上半边
# crop() 方法返一个图像的矩形区域,需要指定 "左上顶点" 和 "右下点" 的坐标,返回一个指定区域的图片对象
# abs() 方法是取数字的绝对值
im_list_upper.append(im.crop((abs(location['x']), 58, abs(location['x']) + 10, 116)))
if location['y'] == 0: # 下半边
im_list_down.append(im.crop((abs(location['x']), 0, abs(location['x']) + 10, 58)))
x_offset = 0 # 初始值
for im in im_list_upper:
#
new_im.paste(im, (x_offset, 0)) # 把小图片放到 新的空白图片上 放上半部分
x_offset += im.size[0]
x_offset = 0
for im in im_list_down:
new_im.paste(im, (x_offset, 58)) # 把小图片放到 新的空白图片上 放下半部分
x_offset += im.size[0]
new_im.save(restore_name) # 保存还原图片
return None
def get_gap(notch_img, all_img):
"""
验证码缺口距离识别, 获取缺口偏移量
:param notch_img: 带缺口的验证码文件路径
:param all_img: 完整的验证码文件路径
:return: 验证码缺口横向距离
"""
slide = ddddocr.DdddOcr(beta=True) # beta=True 通用识别
# 打开带缺口的图片
with open(notch_img, 'rb') as f:
target_bytes = f.read()
# 打开完整图片
with open(all_img, 'rb') as f:
background_bytes = f.read()
# slide_comparison(二进制带缺口图片, 二进制完整图片) --> 识别方法
res = slide.slide_comparison(target_bytes, background_bytes)
if res:
# 识别结果样式: {'target': [117, 72]} --> 其中列表中第一个数字就是缺口的横向距离
print("识别结果: ", res)
print("缺口的横向距离: ", res['target'][0])
else:
raise Exception('验证码识别失败')
return res['target'][0]
def move_slide(offset_x, offset_y, left):
"""
执行滑块的移动
:param offset_x: 滑块的x轴坐标
:param offset_y: 滑块的y轴坐标
:param left: 需要移动的距离
:return:
"""
# 鼠标移动到显示器窗口指定的坐标位置, 后续自己电脑的位置, 需要重新定位
pyautogui.moveTo(
offset_x,
offset_y,
# 设置移动时间
duration=0.1 + random.uniform(0, 0.1 + random.randint(1, 100) / 100))
pyautogui.mouseDown() # 按下鼠标
offset_y += random.randint(9, 19) # 随机偏移Y轴位置
pyautogui.moveTo(
offset_x + int(left * random.randint(15, 25) / 20),
offset_y,
duration=0.28)
offset_y += random.randint(-9, 0)
pyautogui.moveTo(
offset_x + int(left * random.randint(18, 22) / 20),
offset_y,
duration=random.randint(19, 31) / 100)
offset_y += random.randint(0, 8)
pyautogui.moveTo(
offset_x + int(left * random.randint(19, 21) / 20),
offset_y,
duration=random.randint(20, 40) / 100)
offset_y += random.randint(-3, 3)
pyautogui.moveTo(
left + offset_x + random.randint(-3, 3),
offset_y,
duration=0.5 + random.randint(-10, 10) / 100)
offset_y += random.randint(-2, 2)
pyautogui.moveTo(
left + offset_x + random.randint(-2, 2),
offset_y,
duration=0.5 + random.randint(-3, 3) / 100)
pyautogui.mouseUp() # 松开鼠标
time.sleep(3)
if __name__ == '__main__':
# 实例化一个浏览器对象
driver = webdriver.Chrome()
# 打开浏览器页面, 请求url地址
driver.get('http://www.cnbaowen.net/api/geetest/')
driver.maximize_window()
driver.implicitly_wait(10)
time.sleep(2)
"""获取到两张图片(一个是有缺口的图片, 一个是完整的图片)"""
image1_location = get_image('.gt_cut_fullbg.gt_show>div', 'split-1.png') # 获取完整的图片
print('正在保存完整图片......')
image2_location = get_image('.gt_cut_bg.gt_show>div', 'split-2.png') # 获取带缺口的图片
print('正在保存带有缺口的图片......\n')
print('偏移量规则:', image2_location)
"""还原图片"""
merge_image('split-1.png', image1_location, 'output-all.png')
print('正在还原完整的验证码图片......')
merge_image('split-2.png', image2_location, 'output-notch.png')
print('正在还原带缺口的验证码图片......\n')
"""调用ddddocr识别滑块距离"""
# 滑块距离不一定对, 可能会存在偏差
distance = get_gap('output-notch.png', 'output-all.png')
# 鼠标动作链滑动极验验证码经常会被检测到
# 实际滑动距离需要做细微的调整
# distance += 2
"""调用移动滑块的函数"""
# 当调用滑动的时候, 不要动你的鼠标
move_slide(665, 390, distance)
input('阻塞, 回车继续:')
driver.quit()
canvas类型处理
获取验证码图片
目标网址中,滑动验证码图片有三张图片,每张图片对应的标签分别是三个 <canvas>
,并且在前端样式经过处理后,不管选中哪个 <canvas>
标签,选择的都是滑动验证码图片同一块标签区域。
根据分析三张图依次对应的验证码图像如下所示:
基于这样的情况,那么我们怎样去截取验证码图片呢?咱们在前端可以修改 <canvas>
标签的样式,让想要保存的验证码图片显示出来,不想要的验证码图片通过样式修改可以将其隐藏。我们只需要获取带缺口的验证码图片和完整验证码图片就可以了,可以通过执行js代码修改标签样式,如下所示:
通过在Console控制台调试js代码
document.querySelectorAll("canvas")
- 上述js代码可以获取到三个 标签,对应的索引分别是 0 - 1 - 2,其中0代表缺口图,1代表滑块图,2代表完整图
document.querySelectorAll("canvas")[1].style="display: none;"
- 上述js代码是将索引为 [1] 的**
<canvas>
**标签通过.style
修改其属性display
为隐藏,执行后可以看到滑动验证码的滑块图片就被隐藏了
- 当滑块隐藏后那么我们可以将带缺口的验证码截图并保存下来。按照上述相同的逻辑,我们可以把完整的滑动验证码通过修改标签属性显示在前端后,截图保存下来。
js代码:document.querySelectorAll("canvas")[2].style=""
,此js逻辑是移除了索引为 [2] 的**<canvas>
**标签的style
属性。执行js后如下所示:
获取验证码图片的代码逻辑如下:
def get_captcha():
"""保存验证码的缺口图片和完整的图片"""
wait.until(
EC.text_to_be_present_in_element((By.CSS_SELECTOR, ".geetest_radar_tip_content"), "点击按钮进行验证")
)
driver.find_element(By.CSS_SELECTOR, ".geetest_radar_tip_content").click()
# 确定滑块加载出来之后 再进行后续的操作
wait.until(
EC.text_to_be_present_in_element((By.CSS_SELECTOR, ".geetest_slider_tip.geetest_fade"), "拖动滑块完成拼图")
)
time.sleep(1)
print("滑块加载完成")
# 执行以下是隐藏滑块图片 [0]是缺口图片; [1]是滑块图片; [2]是完整图片
driver.execute_script('document.querySelectorAll("canvas")[1].style="display: none;"')
"""标签元素截图保存验证码"""
captcha_tag = driver.find_element(By.CSS_SELECTOR, ".geetest_window")
# 获取有缺口的验证码
captcha_tag.screenshot('output-notch.png')
print('正在保存带缺口的验证码......')
time.sleep(1)
# 先修改CSS样式 得到完整验证码图片, 不带缺口的; 修改后不需要恢复, 因为当后续滑动的时候,验证码前端样式会自动修改
driver.execute_script('document.querySelectorAll("canvas")[2].style=""')
captcha_tag.screenshot('output-all.png')
print('正在保存不带缺口的完整的验证码......')
time.sleep(1)
# 为了更加清楚的看到滑动的效果 恢复一下之前修改的css样式
driver.execute_script('document.querySelectorAll("canvas")[1].style="opacity: 1; display: block;"')
获取验证码缺口距离
对比两张验证码图片,使用ddddocr调用接口识别出缺口距离,代码如下所示:
def get_gap(notch_img, all_img):
"""
验证码缺口距离识别, 获取缺口偏移量
:param notch_img: 带缺口的验证码文件路径
:param all_img: 完整的验证码文件路径
:return: 验证码缺口横向距离
"""
slide = ddddocr.DdddOcr(beta=True) # beta=True 通用识别
# 打开带缺口的图片
with open(notch_img, 'rb') as f:
target_bytes = f.read()
# 打开完整图片
with open(all_img, 'rb') as f:
background_bytes = f.read()
# slide_comparison(二进制带缺口图片, 二进制完整图片) --> 识别方法
res = slide.slide_comparison(target_bytes, background_bytes)
if res:
# 识别结果样式: {'target': [117, 72]} --> 其中列表中第一个数字就是缺口的横向距离
print("识别结果: ", res)
print("缺口的横向距离: ", res['target'][0])
else:
raise Exception('验证码识别失败')
return res['target'][0]
滑动验证码模拟滑动
同样使用pyautogui模拟鼠标对验证码进行滑动, 代码如下所示:
def move_slide(offset_x, offset_y, left):
"""
执行滑块的移动
:param offset_x: 滑块的x轴坐标
:param offset_y: 滑块的y轴坐标
:param left: 需要移动的距离
:return:
"""
# duration为持续时间
# random.uniform(参数1,参数2) 返回参数1和参数2之间的任意值
pyautogui.moveTo(
offset_x,
offset_y,
duration=0.1 + random.uniform(0, 0.1 + random.randint(1, 100) / 100))
# 按下鼠标 准备开始滑动
pyautogui.mouseDown()
# random.randint(参数1, 参数2) 函数返回参数1和参数2之间的任意整数
offset_y += random.randint(9, 19)
pyautogui.moveTo(
offset_x + int(left * random.randint(15, 25) / 20),
offset_y,
duration=0.28)
offset_y += random.randint(-9, 0)
pyautogui.moveTo(
offset_x + int(left * random.randint(18, 22) / 20),
offset_y,
duration=random.randint(19, 31) / 100)
offset_y += random.randint(0, 8)
pyautogui.moveTo(
offset_x + int(left * random.randint(19, 21) / 20),
offset_y,
duration=random.randint(20, 40) / 100)
offset_y += random.randint(-3, 3)
pyautogui.moveTo(
left + offset_x + random.randint(-3, 3),
offset_y,
duration=0.5 + random.randint(-10, 10) / 100)
offset_y += random.randint(-2, 2)
pyautogui.moveTo(
left + offset_x + random.randint(-2, 2),
offset_y,
duration=0.5 + random.randint(-3, 3) / 100)
# 释放鼠标
pyautogui.mouseUp()
time.sleep(3)
练习案例
import random
import time
import ddddocr
import pyautogui
from selenium import webdriver
from selenium.webdriver.common.by import By
def get_captcha():
time.sleep(1)
# 点击人机校验
driver.find_element(By.CSS_SELECTOR, ".geetest_radar_tip_content").click()
time.sleep(2)
print("滑块加载完成")
# 如果不确定下表索引是从零开始的 还是1开始的 可以简单测试一下:从0开始的
# 执行以下是隐藏滑块图片 [0]是缺口图片; [1]是滑块图片; [2]是完整图片
driver.execute_script('document.querySelectorAll("canvas")[1].style="display: none;"')
"""标签元素截图保存验证码"""
captcha_tag = driver.find_element(By.CSS_SELECTOR, ".geetest_window")
captcha_tag.screenshot('output-notch.png')
print('正在保存带缺口的验证码......')
time.sleep(1)
# 执行一下js代码, 可以显示完整图片
driver.execute_script('document.querySelectorAll("canvas")[2].style=""')
captcha_tag.screenshot('output-all.png')
print('正在保存不带缺口的完整的验证码......')
time.sleep(1)
# 为了更加清楚的看到滑动的效果 恢复一下之前修改的css样式; opacity: 1 --> 不透明; display: block --> 显示块级元素
driver.execute_script('document.querySelectorAll("canvas")[1].style="opacity: 1; display: block;"')
def get_gap(notch_img, all_img):
"""
验证码缺口距离识别, 获取缺口偏移量
:param notch_img: 带缺口的验证码文件路径
:param all_img: 完整的验证码文件路径
:return: 验证码缺口横向距离
"""
slide = ddddocr.DdddOcr(beta=True) # beta=True 通用识别
# 打开带缺口的图片
with open(notch_img, 'rb') as f:
target_bytes = f.read()
# 打开完整图片
with open(all_img, 'rb') as f:
background_bytes = f.read()
# slide_comparison(二进制带缺口图片, 二进制完整图片) --> 识别方法
res = slide.slide_comparison(target_bytes, background_bytes)
if res:
# 识别结果样式: {'target': [117, 72]} --> 其中列表中第一个数字就是缺口的横向距离
print("识别结果: ", res)
print("缺口的横向距离: ", res['target'][0])
else:
raise Exception('验证码识别失败')
return res['target'][0]
def move_slide(offset_x, offset_y, left):
"""
执行滑块的移动
:param offset_x: 滑块的x轴坐标
:param offset_y: 滑块的y轴坐标
:param left: 需要移动的距离
:return:
"""
# 鼠标移动到显示器窗口指定的坐标位置, 后续自己电脑的位置, 需要重新定位
pyautogui.moveTo(
offset_x,
offset_y,
# 设置移动时间
duration=0.1 + random.uniform(0, 0.1 + random.randint(1, 100) / 100))
pyautogui.mouseDown() # 按下鼠标
offset_y += random.randint(9, 19) # 随机偏移Y轴位置
pyautogui.moveTo(
offset_x + int(left * random.randint(15, 25) / 20),
offset_y,
duration=0.28)
offset_y += random.randint(-9, 0)
pyautogui.moveTo(
offset_x + int(left * random.randint(18, 22) / 20),
offset_y,
duration=random.randint(19, 31) / 100)
offset_y += random.randint(0, 8)
pyautogui.moveTo(
offset_x + int(left * random.randint(19, 21) / 20),
offset_y,
duration=random.randint(20, 40) / 100)
offset_y += random.randint(-3, 3)
pyautogui.moveTo(
left + offset_x + random.randint(-3, 3),
offset_y,
duration=0.5 + random.randint(-10, 10) / 100)
offset_y += random.randint(-2, 2)
pyautogui.moveTo(
left + offset_x + random.randint(-2, 2),
offset_y,
duration=0.5 + random.randint(-3, 3) / 100)
pyautogui.mouseUp() # 松开鼠标
time.sleep(3)
if __name__ == '__main__':
driver = webdriver.Chrome()
driver.get("https://www.geetest.com/demo/slide-float.html")
driver.maximize_window() # 最大化
driver.implicitly_wait(10)
# 调用保存验证码的函数
get_captcha()
"""调用ddddocr识别滑块距离"""
distance = get_gap('output-notch.png', 'output-all.png')
# 实际滑动距离需要做细微的调整
distance -= 5
"""调用移动滑块的函数"""
move_slide(937, 475, distance)
input("阻塞:")
driver.quit()