基于win32实现TB登陆滑动验证

先谈理论,淘宝 taobao.com 的所有登陆系统,都是基于sso来实现的,基本大同小异

在这里插入图片描述

滑动验证触发条件:

  1. 此账户多次异常失败
  2. 该账户在多种ip环境下登陆
  3. 被系统检测到自动化

失败条件:

  1. 失败一次后,继续使用当前cookies
  2. 滑动速度太慢
  3. 网络太忙 ( 滑动成功,但是存在无效cookie)

解决方法:

每次登陆前,必须保证,当前异常cookie,每次登陆前清除一次即可

清除cooKie

由于playwright清理当前cookies不干净,所以采用浏览器强制清除cookies
在这里插入图片描述

def clear_cookie(handle: int, point, point2, point3, point4):
	
    win32gui.SetForegroundWindow(handle)
    win32gui.ShowWindow(handle, 3)  # 窗口最大化
    # left, top, right, bottom = win32gui.GetWindowRect(handle)
    # width = right - left
    # height = bottom - top
    # # # 计算指定检查点的坐标
    # x = left + int(point[0] * width)
    # y = top + int(point[1] * height)
    x = point[0]
    y = point[1]
    win32api.SetCursorPos(point)
    win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, x, y, 0, 0)  # 鼠标左键按下
    win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, x, y, 0, 0)  # 鼠标左键抬起
    x = point2[0]
    y = point2[1]
    win32api.SetCursorPos(point2)
    win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, x, y, 0, 0)  # 鼠标左键按下
    win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, x, y, 0, 0)  # 鼠标左键抬起
    x = point3[0]
    y = point3[1]
    win32api.SetCursorPos(point3)
    time.sleep(0.1)
    for i in range(15):
        win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, x, y, 0, 0)  # 鼠标左键按下
        win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, x, y, 0, 0)  # 鼠标左键抬起
    x = point4[0]
    y = point4[1]
    win32api.SetCursorPos(point4)
    win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, x, y, 0, 0)  # 鼠标左键按下
    win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, x, y, 0, 0)  # 鼠标左键抬起
    time.sleep(0.1)
    win32api.keybd_event(0x0D, 0, 0, 0)
    win32api.keybd_event(0x0D, 0, win32con.KEYEVENTF_KEYUP, 0)

def findTitle(window_title):
    '''
    查找指定标题窗口句柄
    @param window_title: 标题名
    @return: 窗口句柄
    '''
    hWndList = []
    # 函数功能:该函数枚举所有屏幕上的顶层窗口,办法是先将句柄传给每一个窗口,然后再传送给应用程序定义的回调函数。
    win32gui.EnumWindows(lambda hWnd, param: param.append(hWnd), hWndList)
    for hwnd in hWndList:
        # 函数功能:该函数获得指定窗口所属的类的类名。
        # clsname = win32gui.GetClassName(hwnd)
        # 函数功能:该函数将指定窗口的标题条文本(如果存在)拷贝到一个缓存区内
        title = win32gui.GetWindowText(hwnd)
        if (window_title in title):
            return title, hwnd
    return ()



def del_cookies(self, window_title):
      logger.info("清空cookies中")
      try:
          self.context.clear_cookies()
          hwnd = findTitle(window_title)
          if global_config.active == "prod":
              clear_cookie(hwnd[1], (144, 53), (192, 173), (624, 558), (900, 549))  # 生产
          else:
              clear_cookie(hwnd[1], (3032, 51), (3128, 174), (3736, 548), (4006, 554))
      except Exception as e:
          logger.error(f"清除cookie异常, {str(e)}")

滑动验证

方式一:win32 api获取窗口句柄,选择固定位置 成功率高

需要提前录制当前桌面的鼠标轨迹

def move(handle: int, point: tuple[int], move_point: tuple[int]):
    """
        后台移动鼠标
    """
    try:
        # 激活窗口刀前台
        win32gui.SetForegroundWindow(handle)
        win32gui.ShowWindow(handle, 3)  # 窗口最大化
        left, top, right, bottom = win32gui.GetWindowRect(handle)
        width = right - left
        height = bottom - top
        # # 计算指定检查点的坐标
        x = left + int(point[0] * width)
        y = top + int(point[1] * height)

        x1 = left + int(point[0] * width) + random.randint(1, 20)
        y1 = top + int(point[1] * height)
        # x = point[0]
        # y = point[1]
        # 移动鼠标指针
        win32api.SetCursorPos(point)
        win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, x, y, 0, 0)  # 鼠标左键按下
        # time.sleep(0.5)
        # win32api.SetCursorPos(move_point)
        for i in range(x, x1):
            win32api.mouse_event(win32con.MOUSE_MOVED, i, y1, 0, 0)  # 鼠标左键按下
        win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, x1, y1, 0, 0)  # 鼠标左键抬起
    except Exception as e:
        pass

方式二: 原自动化滑动,成功率中

def un_login_lock(self, distance: int, locator: Locator) -> None:
    locator.blur()
    box = locator.bounding_box()
    tracks = get_track(distance)
    x = int(box["x"] + box["width"] / 2)
    y = int(box["y"] + box["height"] / 2)
    locator.hover()
    self.page.mouse.down()
    self.page.mouse.move(x, y + random.randint(10, 20), steps=12)
    for track in tracks:
        self.page.mouse.move(track + x, y + random.randint(10, 20), steps=9)
        x = x + track
    self.page.mouse.up()
    self.page.wait_for_timeout(random.randint(2200, 3200))
附录:绕过Webdriver检测可增加浏览器反识别概率,可选不加
def webdriver(self):
    # 绕过Webdriver检测
    js = """Object.defineProperties(navigator, {webdriver:{get:()=>undefined}});"""
    self.page.add_init_script(js)

案例

if locator := self.is_lock(punish[0]):
    logger.info(f"正在进行滑动验证,{locator.bounding_box()}")
    hwnd = findTitle(window_title)
    conf = {
        "start":[[4077, 583],[4079, 582],[4078,595]], # 鼠标开始的轨迹数组
        "end":[[4823, 623],[4923, 699],[4518,578]] # 鼠标结束的轨迹数组
       }
    points = eval(conf.get("config_value"))
    starts = points.get("start")
    ends = points.get("end")
    # 随机选择一个
    move(hwnd[1], starts[random.randint(0, len(starts) - 1)], ends[random.randint(0, len(ends) - 1)])
    # self.un_login_lock(500, locator)

虽然小概率出现异常,加入重试机制后,基本没出现过问题