如果只需要账号和密码就可以登录,可以直接用playwright录制脚本即可,但是有些网址登录需要验证码。这里讨论的情况是滑动验证码的情况,步骤如下:
playwright输入账号、密码
def run(playwright: Playwright) -> str:
browser = playwright.chromium.launch(headless=False)
context = browser.new_context()
page = context.new_page()
page.goto("http://xxxxx/login")
page.get_by_text("业务中台登录").click()
page.get_by_placeholder("请输入用户名/电子邮件/移动电话/工号/AD域帐号").click()
page.get_by_placeholder("请输入用户名/电子邮件/移动电话/工号/AD域帐号").fill("账号")
page.get_by_placeholder("请输入密码").click()
page.get_by_placeholder("请输入密码").fill("密码")
page.get_by_role("button", name="登录").click()
处理滑动验证码
通常点击登录后,就会弹出滑动验证码
首先需要获取到验证码背景图和缺口图
#bg为背景图的base64地址
img1 = page.get_by_role("img").nth(1)
bg = img1.get_attribute('src')
#bg_width为背景图的宽度(当前playwright打开的界面中的背景图宽度)
bg_box = img1.bounding_box()
bg_width = bg_box["width"]
#hx为缺口的base64地址
img2 = page.get_by_role("img").nth(2)
hx = img2.get_attribute('src')
求出缺口需要移动的距离
def show(name):
'''展示圈出来的位置'''
cv2.imshow('Show', name)
cv2.waitKey(0)
cv2.destroyAllWindows()
def _tran_canny(image):
"""消除噪声"""
image = cv2.GaussianBlur(image, (3, 3), 0)
return cv2.Canny(image, 100, 200)
def get_notch_location(hx, bg,bg_width):
'''
求出缺口需要移动的距离
:param hx: 滑块图片的文件路径
:param bg: 背景图片的文件路径
:param bg_width: 背景图片目前的宽度
:return:
'''
# 从Base64解码图片数据
hx = base64.b64decode(hx.split(',')[1])
bg = base64.b64decode(bg.split(',')[1])
# 将解码后的数据转换为NumPy数组
hx = np.frombuffer(hx, np.uint8)
bg = np.frombuffer(bg, np.uint8)
# 使用OpenCV读取图像
hx_img = cv2.imdecode(hx, 0)
bg_img = cv2.imdecode(bg, 0)
# 寻找最佳匹配
res = cv2.matchTemplate(_tran_canny(hx_img), _tran_canny(bg_img), cv2.TM_CCOEFF_NORMED)
# 最小值,最大值,并得到最小值, 最大值的索引
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
top_left = max_loc[0] # 横坐标
# 展示圈出来的区域
# x, y = max_loc # 获取x,y位置坐标
#
# w, h = hx_img.shape[::-1] # 宽高
# cv2.rectangle(bg_img, (x, y), (x + w, y + h), (7, 249, 151), 2)
# show(bg_img)
# 打印图像的高度和宽度
# 读取输入图像
# 获取图像的高度和宽度
height, width = bg_img.shape
# print("Image width:", width)
# print("Image height:", height)
# 因为原图大小可能和目前页面中图大小不一致
# 所以需要使用bg_width/width换算得到当前缺口需要移动的距离
top_left = top_left*(bg_width/width)
return top_left
得到鼠标需要移动的距离
滑动验证码,最终需要使用鼠标移动,所以需要得到鼠标移动的距离
注意滑块移动长度和缺口移动长度不一定是相等的,但是是正相关的
最好的情况是,我们知道滑块移动距离和缺口移动距离的准确函数,但是这里我获取不到
这里我采用的方法,是手动收集几个点,然后算一个函数出来用(有大佬可以说说有什么更好的方法吗??(>人<;))
import numpy as np
from scipy import stats
# 提供的数据点
y = np.array([0, 40, 60, 100, 200,335.333]) # 滑块滑动距离
x = np.array([0, 35.0483, 52.3636, 88.1663, 178.12,296.65]) # 缺口滑动距离
# 进行线性回归拟合
slope, intercept, r_value, p_value, std_err = stats.linregress(x, y)
# 打印拟合结果
print("斜率 (a):", slope)
print("截距 (b):", intercept)
print("相关系数 (R-squared):", r_value**2)
这样我们就得到了鼠标需要移动的距离
控制鼠标移动速度
过验证码就是需要尽量模拟人类的行为,人类滑动验证码肯定不会像机器这么快
这里我直接用的网友写的
https://www.cnblogs.com/carl-/p/15761861.html
#有的检测移动速度的 如果匀速移动会被识别出来,来个简单点的 渐进
#这里的总距离就是鼠标移动的距离
def get_track(distance): # distance为传入的总距离
# 移动轨迹
track = []
# 当前位移
current = 0
# 减速阈值
mid = distance * 4 / 5
# 计算间隔
t = 0.2
# 初速度
v = 1
while current < distance:
if current < mid:
# 加速度为2
a = 4
else:
# 加速度为-2
a = -3
v0 = v
# 当前速度
v = v0 + a * t
# 移动距离
move = v0 * t + 1 / 2 * a * t * t
# 当前位移
current += move
# 加入轨迹
track.append(round(move))
return track
至此我们就可以开始获取token的功能了
获取token
from playwright.sync_api import Playwright, sync_playwright, expect
import cv2
import requests
import numpy as np
import base64
#需要滑动验证码登录
def run(playwright: Playwright) -> str:
browser = playwright.chromium.launch(headless=False)
context = browser.new_context()
page = context.new_page()
page.goto("http://xxxxx/login")
page.get_by_text("业务中台登录").click()
page.get_by_placeholder("请输入用户名/电子邮件/移动电话/工号/AD域帐号").click()
page.get_by_placeholder("请输入用户名/电子邮件/移动电话/工号/AD域帐号").fill("账号")
page.get_by_placeholder("请输入密码").click()
page.get_by_placeholder("请输入密码").fill("密码")
page.get_by_role("button", name="登录").click()
# 因为没有得到鼠标移动距离和缺口移动距离的准确关系,所以可能会验证失败
# 所以用while尝试直到验证成功才退出
while True:
img1 = page.get_by_role("img").nth(1)
bg = img1.get_attribute('src')
bg_box = img1.bounding_box()
bg_width = bg_box["width"]
img2 = page.get_by_role("img").nth(2)
hx = img2.get_attribute('src')
hx_box = img2.bounding_box()
# 找到这个元素在当前页面的坐标(这个会返回一个字典里边四个数字)
box = page.locator(".slider-move-box").bounding_box()
# 移动鼠标到上边元素的中心(上边四个参数用途来了)
page.mouse.move(box["x"] + box["width"] / 2, box["y"] + box["height"] / 2)
# 按下鼠标
page.mouse.down()
# 这里获取到x坐标中心点位置,x这里就是鼠标的开始的位置
x = box["x"] + box["width"] / 2
# 得到缺口的滑行长度
hx_real_len = get_notch_location(hx, bg, bg_width)
# box_real_len = 1.12754*hx_real_len + 0.33920这个计算参数
# 就是通过刚刚采样点计算得到的
box_real_len = 1.12754 * hx_real_len + 0.33920
tracks = get_track(box_real_len)
for track in tracks:
# 循环鼠标按照轨迹移动
# steps 是控制单次移动速度的比例是1/10 默认是1 相当于 传入的这个距离不管多远0.1秒钟移动完 越大越慢
page.mouse.move(x + track, 0, steps=5)
x += track
# 移动结束鼠标抬起
page.mouse.up()
# 鼠标抬起后,要么验证成功,要么验证失败,等待3s查看结果
# 设置等待时间(以毫秒为单位)
wait_time_ms = 3000 # 3 秒
# 等待一段时间后再查找页面元素
page.wait_for_timeout(wait_time_ms)
# 获取 <span> 元素,如果出现再试一次,说明验证失败重新验证
# 如果没有就说明验证成功了
span_element = page.query_selector(".slider-move-text")
if span_element:
# 获取 <span> 元素的文本内容
span_text = span_element.text_content()
# 检查文本是否包含特定字符串
if '再试一次' not in span_text:
break
else:
print("文本 '再试一次' 存在于 <span> 元素中。")
else:
print("未找到 .slider-move-text <span> 元素。")
break
page.wait_for_load_state('load')
#这里顺利进入后,点击按钮,获取接口里的token和SESSION
page.get_by_role("button", name="更多查询条件").click()
page.get_by_role("button", name="查询", exact=True).click()
storage = context.storage_state(path="state.json")
# storage = context.storage_state()
localStorage_list = storage['origins'][0]["localStorage"]
token = [i['value'] for i in localStorage_list if i['name'] == 'token'][0]
session = 'SESSION=' + storage['cookies'][0]['value']
context.close()
browser.close()
return {'token': token, 'session': session}
参考链接
https://www.cnblogs.com/carl-/p/15761861.html
https://cloud.tencent.com/developer/article/1825224