0 声明
文章仅供技术交流
1 什么是drissionpage
2 分析
当我们访问这个网站的时候,可能会弹出滑块验证码(相较于chrome和edge,360极速浏览器触发概率更大)
进入开发者工具(F12)
看一下具体结构,通过抓包可以找到两个url里返回了一个背景图和一个缺口图
可以在源码里找到这两个图片的url,但是由于是在iframe里,所以通过简单程序请求想拿到这两个url是不容易的
3 思路
0x01 获取iframe里的滑块url
在drissionpage里可通过get_frame()来获取iframe对象
0x02 计算滑块距离
根据网上提供的方法,有下面几种计算滑块距离的方法,个人认为其中方法4的识别成功率更高
方法1:
def distance_puzzle(bg_img_path, puzzle_img_path):
"""
根据背景图和缺口图,计算滑块距离
:param bg_img_path:
:param puzzle_img_path:
:return:
"""
img = cv2.imdecode(np.fromfile(bg_img_path, dtype=np.uint8), cv2.IMREAD_COLOR)
tpl = cv2.imdecode(np.fromfile(puzzle_img_path, dtype=np.uint8), cv2.IMREAD_COLOR)
img_gry = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, img_bw = cv2.threshold(img_gry, 127, 255, cv2.THRESH_BINARY)
tpl = cv2.cvtColor(tpl, cv2.COLOR_BGR2GRAY)
for row in range(tpl.shape[0]):
for col in range(tpl.shape[1]):
if tpl[row, col] == 0:
tpl[row, col] = 96
lower = np.array([96])
upper = np.array([96])
mask = cv2.inRange(tpl, lower, upper)
tpl[mask == 0] = 0
tpl[mask != 0] = 255
result = cv2.matchTemplate(img_bw, tpl, cv2.TM_CCOEFF_NORMED)
_, _, _, max_loc = cv2.minMaxLoc(result)
distance = int(max_loc[0] * 0.5833333333333333) + random.randint(0, 2)
return distance
#
if __name__ == '__main__':
background = './images/background.png'
gap = './images/gap.png'
offset = distance_puzzle(background, gap)
方法2:
import ddddocr
def distance_ddddocr(gap_content, background_content):
det = ddddocr.DdddOcr(det=False, ocr=False, show_ad=False)
result = det.slide_match(gap_content, background_content, simple_target=True)
offset = result['target'][0]
return offset
if __name__ == '__main__':
background = open('./images/background.png', 'rb').read()
gap = open('./images/gap.png', 'rb').read()
offset = distance_ddddocr(gap, background)
方法3:
import cv2
import numpy as np
def distance_cv(slice_url, bg_url):
"""
:param slice_url: 滑块(缺口)图片地址
:param bg_url: 背景图地址
:return: distance
:rtype:integer
"""
slice_image = np.asarray(bytearray(slice_url), dtype=np.uint8)
slice_image = cv2.imdecode(slice_image, 1)
slice_image = cv2.Canny(slice_image, 255, 255)
bg_image = np.asarray(bytearray(bg_url), dtype=np.uint8)
bg_image = cv2.imdecode(bg_image, 1)
bg_image = cv2.pyrMeanShiftFiltering(bg_image, 5, 50)
bg_image = cv2.Canny(bg_image, 255, 255)
result = cv2.matchTemplate(bg_image, slice_image, cv2.TM_CCOEFF_NORMED)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
return max_loc[0]
#
if __name__ == '__main__':
background = open('./images/background.png', 'rb').read()
gap = open('./images/gap.png', 'rb').read()
offset = distance_cv(background, gap)
方法4:
import cv2
import numpy as np
def calculate_offset_masked(bg_path: str,
tpl_path: str,
y_search_range: int = 5) -> int:
"""
掩码模板匹配 + 纵向微调:
- bg_path: 背景图路径(彩色)
- tpl_path: 滑块图路径(带透明或白底)
- y_search_range: 在[-r, +r]范围内微调y
返回: 最佳横向偏移值 x(像素),并生成结果图
若匹配失败则抛出 ValueError。
"""
# 读取背景图和模板
bg_color = cv2.imread(bg_path)
if bg_color is None:
raise FileNotFoundError(f"无法读取背景图: {bg_path}")
bg_gray = cv2.cvtColor(bg_color, cv2.COLOR_BGR2GRAY)
tpl_color = cv2.imread(tpl_path, cv2.IMREAD_UNCHANGED)
if tpl_color is None:
raise FileNotFoundError(f"无法读取滑块图: {tpl_path}")
# 分离模板灰度和掩码
if tpl_color.shape[2] == 4:
b, g, r, a = cv2.split(tpl_color)
tpl_gray = cv2.cvtColor(cv2.merge([b, g, r]), cv2.COLOR_BGR2GRAY)
mask = (a > 0).astype(np.uint8) * 255
else:
tpl_gray = cv2.cvtColor(tpl_color, cv2.COLOR_BGR2GRAY)
_, mask = cv2.threshold(tpl_gray, 10, 255, cv2.THRESH_BINARY)
H, W = bg_gray.shape
h, w = tpl_gray.shape
best_score = -1.0
best_x = None
best_y = None
# 在y轴微调匹配
for dy in range(-y_search_range, y_search_range + 1):
res = cv2.matchTemplate(bg_gray, tpl_gray, cv2.TM_CCORR_NORMED, mask=mask)
# 限制Y范围保持模板在图内
res[:abs(dy), :] = -1
res[H - h - abs(dy) + 1:, :] = -1
# 获取当前最佳
_, max_val, _, max_loc = cv2.minMaxLoc(res)
if max_val > best_score:
best_score = max_val
best_x, best_y = max_loc
# 检查是否匹配到有效结果
if best_x is None:
raise ValueError("模板匹配失败,未找到有效偏移。")
# 在背景图上绘制结果
result_img = bg_color.copy()
top_left = (best_x, best_y)
bottom_right = (best_x + w, best_y + h)
cv2.rectangle(result_img, top_left, bottom_right, (0, 255, 0), 2)
# 保存结果图
result_path = f"{bg_path.replace('.jpg', '')}_result.png"
cv2.imwrite(result_path, result_img)
return best_x
if __name__ == "__main__":
bg_image_path = './images/background.png'
slider_image_path = './images/gap.png'
x_offset = calculate_offset_masked(bg_image_path, slider_image_path)
下面的图片分别是滑块缺口图、背景图和使用方法4识别出来的图片
0x03 滑动轨迹
我用的是网上随便找的一个匀加速和匀减速的滑动轨迹
def get_tracks(distance):
"""
根据偏移量获取移动轨迹
:param distance:偏移量
:return:移动轨迹
"""
tracks = []
current = 0
mid = distance * 4 / 5
t = 0.2
v = 0
while current < distance:
if current < mid:
a = 5
else:
a = -3
v0 = v
v = v0 + a * t
move = v0 * t + 0.5 * a * t * t
current += move
tracks.append(round(move))
return tracks
0x04 drissionpage拖动滑块
- 找到要拖动的元素位置
- 使用动作链的hold方法按住需要拖动的块
- 使用动作链中move方法拖动
- 到达指定位置后使用动作链中的release方法释放
slider = 'x://div[@class="slider"]'
iframe.actions.hold(slider)
for track in tracks:
iframe.actions.move(offset_x=track, offset_y=round(random.uniform(1.0, 3.0), 0), duration=.1)
time.sleep(0.1)
iframe.actions.release(slider)
0x05 其他注意项
- 因滑块在iframe里,要区分要操作的是
page = Chromium(addr_or_opts=self.co).latest_tab
的page还是iframe = self.page.get_frame('t:iframe')
的iframe - 滑块的识别率不可能是100%,需要注意滑动失败后的重试
- 注意休眠,如果不加休眠可能页面没加载出来程序功能就结束了