autoUpload/utils/paste_typing.py

245 lines
11 KiB
Python
Raw Normal View History

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