import random import asyncio from typing import Optional class PasteTypingSimulator: """模拟用户复制粘贴行为的输入模拟器""" def __init__(self, page=None): self.page = page self.config = { 'pre_paste_delay': (0.5, 1.2), # 粘贴前的停顿时间 'post_paste_delay': (0.8, 1.5), # 粘贴后的停顿时间 'review_probability': 0.7, # 检查内容的概率 'review_time': (1.5, 3.0), # 检查内容的时间 'adjust_probability': 0.4, # 调整格式的概率 'adjust_delay': (0.3, 0.8), # 调整时的停顿 'scroll_probability': 0.6, # 滚动查看的概率 'scroll_delay': (0.5, 1.0), # 滚动时的停顿 } async def paste_text(self, selector: str, text: str = None) -> bool: """模拟用户粘贴文本的行为 Args: selector: 输入框的选择器 text: 要粘贴的文本,如果不提供则使用剪贴板中的内容 """ try: # 1. 先等待目标元素并点击以获取焦点 element = await self.page.wait_for_selector(selector, timeout=5000) await element.click() await asyncio.sleep(random.uniform(0.2, 0.4)) # 2. 全选当前内容(如果有的话)并删除 await self.page.keyboard.press("Control+A") await asyncio.sleep(random.uniform(0.1, 0.2)) await self.page.keyboard.press("Delete") await asyncio.sleep(random.uniform(0.2, 0.4)) # 3. 如果提供了文本,使用更直接可靠的方法 if text: # 直接使用element.fill方法作为备选方案 try: await element.fill(text) print("使用fill方法成功填充文本") # 粘贴后的检查动作 await self._post_paste_actions() return True except Exception as fill_error: print(f"fill方法失败,尝试剪贴板方法: {fill_error}") # 如果fill方法失败,尝试剪贴板方法 # 使用更简单可靠的方式复制文本到剪贴板 await self.page.evaluate('(text) => {' + 'return navigator.clipboard.writeText(text);' + '}', text) await asyncio.sleep(random.uniform(0.3, 0.5)) # 4. 简化的粘贴操作,直接使用Control+V # 确保焦点在元素上 await element.click() await asyncio.sleep(random.uniform(0.1, 0.2)) # 直接执行粘贴 await self.page.keyboard.press("Control+V") await asyncio.sleep(random.uniform(0.4, 0.7)) # 验证文本是否已粘贴 try: current_text = await element.input_value() if not current_text or (text and text not in current_text): print(f"警告: 粘贴可能失败。当前文本长度: {len(current_text)}") # 最后的备选方案:直接设置value await element.evaluate(f'(element) => {{ element.value = "{text}"; }}', element) await asyncio.sleep(0.2) except Exception as verify_error: print(f"验证粘贴时出错: {verify_error}") # 粘贴后的检查动作 await self._post_paste_actions() return True except Exception as e: print(f"粘贴文本时出错: {e}") return False async def _copy_to_clipboard(self, text: str): """将文本可靠地复制到剪贴板 这个方法使用多种方式尝试复制文本,确保文本能正确复制到剪贴板 """ try: # 方式1:使用更安全的JavaScript,避免模板字符串中的转义问题 await self.page.evaluate('(text) => {' + 'const textarea = document.createElement("textarea");' + 'textarea.style.position = "fixed";' + 'textarea.style.left = "-999999px";' + 'textarea.style.top = "-999999px";' + 'textarea.value = text;' + 'document.body.appendChild(textarea);' + 'textarea.focus();' + 'textarea.select();' + 'try {' + 'document.execCommand("copy");' + '} catch (err) {' + 'console.error("复制失败:", err);' + '}' + 'document.body.removeChild(textarea);' + '}', text) except Exception as e: print(f"主要复制方法失败: {e}") # 方式2:备用方法 - 尝试使用navigator.clipboard API try: await self.page.evaluate('(text) => navigator.clipboard.writeText(text)', text) except Exception as e2: print(f"备用复制方法也失败: {e2}") # 方式3:最基本的方法 - 直接设置文本区域的值 await self.page.evaluate('(text) => {' + 'const textarea = document.createElement("textarea");' + 'textarea.value = text;' + 'document.body.appendChild(textarea);' + 'textarea.select();' + 'document.body.removeChild(textarea);' + '}', text) async def _prepare_input(self, selector: str): """准备输入区域""" try: # 等待元素出现并点击 element = await self.page.wait_for_selector(selector, timeout=5000) await element.click() # 模拟点击后的短暂停顿 await asyncio.sleep(random.uniform(0.3, 0.6)) # 清空现有内容 await self.page.keyboard.press("Control+A") await asyncio.sleep(random.uniform(0.1, 0.2)) await self.page.keyboard.press("Delete") await asyncio.sleep(random.uniform(0.2, 0.4)) except Exception as e: print(f"准备输入区域失败: {e}") raise async def _pre_paste_actions(self): """模拟粘贴前的准备动作""" # 模拟思考和准备时间 await asyncio.sleep(random.uniform(*self.config['pre_paste_delay'])) # 模拟按下 Ctrl 键,更真实的用户操作 await self.page.keyboard.down("Control") await asyncio.sleep(random.uniform(0.1, 0.2)) # 增加延迟,更接近真实用户 async def _perform_paste(self): """执行粘贴操作,分离按键按下和释放,更接近真实用户行为""" # 模拟按下 V 键 await self.page.keyboard.press("v") await asyncio.sleep(random.uniform(0.1, 0.2)) # 释放 Ctrl 键 await self.page.keyboard.up("Control") # 等待内容出现,给足够的时间让内容粘贴完成 await asyncio.sleep(random.uniform(0.4, 0.7)) # 验证粘贴是否成功(可选) try: # 可以添加验证逻辑来确认内容是否已粘贴 pass except Exception: # 即使验证失败也不中断流程 pass async def _post_paste_actions(self): """模拟粘贴后的检查和调整动作""" # 随机检查内容 if random.random() < self.config['review_probability']: # 模拟鼠标滚动查看内容 if random.random() < self.config['scroll_probability']: # 向下滚动一点 await self.page.mouse.wheel(0, 100) await asyncio.sleep(random.uniform(0.3, 0.6)) # 停顿一下,像是在阅读 await asyncio.sleep(random.uniform(0.5, 1.0)) # 再滚动回来 await self.page.mouse.wheel(0, -100) await asyncio.sleep(random.uniform(0.3, 0.6)) # 随机点击文本框内部的某个位置(模拟检查或准备编辑) if random.random() < 0.3: # 30%的概率 element = await self.page.query_selector('textarea') if element: box = await element.bounding_box() if box: x = box['x'] + random.uniform(10, box['width'] - 10) y = box['y'] + random.uniform(10, box['height'] - 10) await self.page.mouse.click(x, y) await asyncio.sleep(random.uniform(0.2, 0.5)) # 随机调整格式 if random.random() < self.config['adjust_probability']: # 模拟删除多余空行 for _ in range(random.randint(1, 2)): await self.page.keyboard.press("End") await asyncio.sleep(random.uniform(0.1, 0.2)) await self.page.keyboard.press("Backspace") await asyncio.sleep(random.uniform(*self.config['adjust_delay'])) async def paste_with_format_check(self, text: str, selector: str = None) -> bool: """带格式检查的粘贴方法""" try: if selector: # 如果提供了选择器,先准备输入区域 element = await self.page.wait_for_selector(selector, timeout=5000) await element.click() await asyncio.sleep(random.uniform(0.2, 0.4)) # 清空现有内容 await self.page.keyboard.press("Control+A") await asyncio.sleep(random.uniform(0.1, 0.2)) await self.page.keyboard.press("Delete") await asyncio.sleep(random.uniform(0.2, 0.4)) # 可靠地复制文本到剪贴板 await self._copy_to_clipboard(text) await asyncio.sleep(random.uniform(*self.config['pre_paste_delay'])) # 模拟真实的粘贴操作 await self._pre_paste_actions() await self._perform_paste() # 模拟粘贴后的检查 await asyncio.sleep(random.uniform(*self.config['post_paste_delay'])) # 随机检查内容 if random.random() < self.config['review_probability']: await asyncio.sleep(random.uniform(*self.config['review_time'])) return True except Exception as e: print(f"格式检查粘贴失败: {e}") return False