import time import random import math def get_cubic_bezier_point(t, p0, p1, p2, p3): x = (1-t)**3 * p0[0] + 3*(1-t)**2 * t * p1[0] + 3*(1-t) * t**2 * p2[0] + t**3 * p3[0] y = (1-t)**3 * p0[1] + 3*(1-t)**2 * t * p1[1] + 3*(1-t) * t**2 * p2[1] + t**3 * p3[1] return (x, y) def ease_out_quad(x): return 1 - (1 - x) * (1 - x) def generate_human_path(start_x, start_y, end_x, end_y, steps=30): path = [] dist = math.hypot(end_x - start_x, end_y - start_y) offset = dist * 0.2 p0 = (start_x, start_y) p3 = (end_x, end_y) p1 = ( start_x + (end_x - start_x) * 0.3 + random.uniform(-offset, offset), start_y + (end_y - start_y) * 0.3 + random.uniform(-offset, offset) ) p2 = ( start_x + (end_x - start_x) * 0.7 + random.uniform(-offset, offset), start_y + (end_y - start_y) * 0.7 + random.uniform(-offset, offset) ) for i in range(steps + 1): t = i / steps eased_t = ease_out_quad(t) point = get_cubic_bezier_point(eased_t, p0, p1, p2, p3) jitter = 1.5 final_x = point[0] + random.uniform(-jitter, jitter) final_y = point[1] + random.uniform(-jitter, jitter) if i == steps: final_x, final_y = end_x, end_y path.append((final_x, final_y)) return path class HumanMouse: def __init__(self, page): self.page = page self.curr_x = random.randint(100, 500) self.curr_y = random.randint(100, 500) self.page.run_cdp('Input.dispatchMouseEvent', **{ 'type': 'mouseMoved', 'x': self.curr_x, 'y': self.curr_y }) def _get_center(self, ele): """兼容性获取中心点""" rect = ele.rect try: tl_x, tl_y = rect.location width, height = rect.size except AttributeError: tl_x, tl_y, width, height = rect return tl_x + (width / 2), tl_y + (height / 2) def move_to(self, ele, duration=0.5): center_x, center_y = self._get_center(ele) # 目标稍微带点随机偏移 target_x = center_x + random.uniform(-3, 3) target_y = center_y + random.uniform(-3, 3) if self.curr_x == 0 and self.curr_y == 0: self.curr_x = target_x - random.randint(300, 500) self.curr_y = target_y - random.randint(300, 500) self.page.run_cdp('Input.dispatchMouseEvent', **{ 'type': 'mouseMoved', 'x': self.curr_x, 'y': self.curr_y }) steps = int(duration * 60) if steps < 10: steps = 10 points = generate_human_path(self.curr_x, self.curr_y, target_x, target_y, steps) for x, y in points: self.page.run_cdp('Input.dispatchMouseEvent', **{ 'type': 'mouseMoved', 'x': x, 'y': y }) self.curr_x = x self.curr_y = y time.sleep(duration / steps * random.uniform(0.8, 1.2)) def scroll_to_visible(self, ele): viewport_height = self.page.run_js("return window.innerHeight") while True: # 使用 arguments[0] 修复 run_js 参数问题 rect = self.page.run_js(""" var rect = arguments[0].getBoundingClientRect(); return {top: rect.top, bottom: rect.bottom, height: rect.height}; """, ele) element_top = rect['top'] element_bottom = rect['bottom'] scroll_needed = False delta_y = 0 # 增加一些缓冲区,不要滚得太极限 if element_top > viewport_height * 0.7: scroll_needed = True delta_y = random.randint(100, 250) elif element_bottom < viewport_height * 0.3: scroll_needed = True delta_y = -random.randint(100, 250) if not scroll_needed: break self._dispatch_scroll(delta_y) time.sleep(random.uniform(0.1, 0.2)) def _dispatch_scroll(self, delta_y): self.page.run_cdp('Input.dispatchMouseEvent', **{ 'type': 'mouseWheel', 'x': self.curr_x, 'y': self.curr_y, 'deltaX': 0, 'deltaY': delta_y, 'modifiers': 0, 'pointerType': 'mouse' })