测试脚本和新的人类,复制打字
This commit is contained in:
parent
3b58e22f91
commit
2f61f0a68e
460
test_typing.py
460
test_typing.py
@ -1,79 +1,167 @@
|
||||
import asyncio
|
||||
import random
|
||||
import time
|
||||
import numpy as np
|
||||
from playwright.async_api import async_playwright
|
||||
from utils.enhanced_human_typing import EnhancedHumanTypingSimulator
|
||||
from utils.paste_typing import PasteTypingSimulator
|
||||
from utils.human_typing_wrapper import HumanTypingWrapper
|
||||
from utils.human_like import HumanLikeTyper
|
||||
|
||||
async def xhs_tag_input(page, tags: list, selector: str):
|
||||
"""模拟小红书的标签输入逻辑"""
|
||||
class BotDetector:
|
||||
"""检测输入行为是否像机器人"""
|
||||
|
||||
@staticmethod
|
||||
def analyze_typing_pattern(timestamps, actions):
|
||||
"""分析输入模式
|
||||
|
||||
Args:
|
||||
timestamps: 时间戳列表
|
||||
actions: 动作列表(键盘输入、鼠标移动等)
|
||||
|
||||
Returns:
|
||||
dict: 分析结果
|
||||
"""
|
||||
if not timestamps or len(timestamps) < 2:
|
||||
return {"risk_score": 0, "reasons": ["样本太少,无法分析"]}
|
||||
|
||||
# 计算时间间隔
|
||||
intervals = np.diff(timestamps)
|
||||
|
||||
# 1. 检查时间间隔的一致性
|
||||
interval_std = np.std(intervals)
|
||||
interval_mean = np.mean(intervals)
|
||||
interval_cv = interval_std / interval_mean if interval_mean > 0 else 0
|
||||
|
||||
# 2. 检查是否有不自然的快速输入
|
||||
has_super_fast = any(i < 0.01 for i in intervals) # 小于10ms的间隔
|
||||
|
||||
# 3. 检查连续动作的规律性
|
||||
action_patterns = []
|
||||
for i in range(len(actions)-2):
|
||||
pattern = f"{actions[i]}-{actions[i+1]}-{actions[i+2]}"
|
||||
action_patterns.append(pattern)
|
||||
unique_patterns = len(set(action_patterns)) / len(action_patterns) if action_patterns else 0
|
||||
|
||||
# 评分系统
|
||||
risk_score = 0
|
||||
reasons = []
|
||||
|
||||
# 评估时间间隔变异系数(CV)
|
||||
# 人类输入的CV通常在0.2到0.8之间
|
||||
if interval_cv < 0.2:
|
||||
risk_score += 30
|
||||
reasons.append("时间间隔过于规律")
|
||||
elif interval_cv > 0.8:
|
||||
risk_score += 10
|
||||
reasons.append("时间间隔过于混乱")
|
||||
|
||||
# 评估超快速输入
|
||||
if has_super_fast:
|
||||
risk_score += 25
|
||||
reasons.append("存在不自然的快速输入")
|
||||
|
||||
# 评估动作模式的多样性
|
||||
# 人类的动作模式通常更加多样
|
||||
if unique_patterns < 0.4:
|
||||
risk_score += 20
|
||||
reasons.append("动作模式过于单一")
|
||||
|
||||
# 评估整体速度
|
||||
if interval_mean < 0.05: # 平均间隔小于50ms
|
||||
risk_score += 25
|
||||
reasons.append("整体输入速度过快")
|
||||
|
||||
return {
|
||||
"risk_score": min(risk_score, 100),
|
||||
"reasons": reasons,
|
||||
"metrics": {
|
||||
"interval_cv": interval_cv,
|
||||
"interval_mean": interval_mean,
|
||||
"unique_patterns": unique_patterns
|
||||
}
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def format_detection_result(result):
|
||||
"""格式化检测结果"""
|
||||
risk_level = "低" if result["risk_score"] < 30 else "中" if result["risk_score"] < 70 else "高"
|
||||
|
||||
output = [
|
||||
f"机器人风险评分: {result['risk_score']}/100 (风险等级: {risk_level})",
|
||||
"可疑特征:" if result["reasons"] else "未发现可疑特征"
|
||||
]
|
||||
|
||||
for reason in result["reasons"]:
|
||||
output.append(f"- {reason}")
|
||||
|
||||
if "metrics" in result:
|
||||
output.extend([
|
||||
"\n详细指标:",
|
||||
f"- 时间间隔变异系数: {result['metrics']['interval_cv']:.3f}",
|
||||
f"- 平均输入间隔: {result['metrics']['interval_mean']*1000:.1f}ms",
|
||||
f"- 动作模式多样性: {result['metrics']['unique_patterns']:.3f}"
|
||||
])
|
||||
|
||||
return "\n".join(output)
|
||||
|
||||
class InputRecorder:
|
||||
"""记录输入行为"""
|
||||
def __init__(self):
|
||||
self.timestamps = []
|
||||
self.actions = []
|
||||
self.start_time = None
|
||||
|
||||
def start(self):
|
||||
"""开始记录"""
|
||||
self.start_time = time.time()
|
||||
self.timestamps = []
|
||||
self.actions = []
|
||||
|
||||
def record(self, action):
|
||||
"""记录一个动作"""
|
||||
self.timestamps.append(time.time() - self.start_time)
|
||||
self.actions.append(action)
|
||||
|
||||
def get_records(self):
|
||||
"""获取记录"""
|
||||
return self.timestamps, self.actions
|
||||
|
||||
async def xiaohongshu_tag_input_from_examples(page, text: str, selector: str):
|
||||
"""从examples中的upload_video_to_xiaohongshu.py提取的标签输入方法
|
||||
但这里我们用它来输入正文文本,模拟标签输入的风格
|
||||
"""
|
||||
element = await page.wait_for_selector(selector)
|
||||
await element.click()
|
||||
await asyncio.sleep(0.5)
|
||||
await page.keyboard.press("Control+A")
|
||||
await page.keyboard.press("Delete")
|
||||
await asyncio.sleep(0.3)
|
||||
|
||||
for i, tag in enumerate(tags):
|
||||
# 输入#号
|
||||
await page.keyboard.press("Shift")
|
||||
await asyncio.sleep(random.uniform(0.1, 0.2))
|
||||
await page.keyboard.press("3")
|
||||
await page.keyboard.up("Shift")
|
||||
await asyncio.sleep(random.uniform(0.2, 0.4))
|
||||
|
||||
# 输入标签文本
|
||||
for char in tag:
|
||||
await page.keyboard.type(char, delay=random.randint(300, 500))
|
||||
await asyncio.sleep(random.uniform(0.1, 0.2))
|
||||
|
||||
# 输入回车并等待
|
||||
await page.keyboard.press("Enter")
|
||||
await asyncio.sleep(random.uniform(0.5, 0.8))
|
||||
|
||||
# 标签间额外停顿
|
||||
if i < len(tags) - 1:
|
||||
await asyncio.sleep(random.uniform(0.8, 1.2))
|
||||
|
||||
async def xhs_type_text(page, text: str, selector: str):
|
||||
"""模拟小红书的文本输入逻辑"""
|
||||
# 定位元素
|
||||
element = await page.wait_for_selector(selector)
|
||||
await element.click()
|
||||
|
||||
# 按段落分割
|
||||
# 将文本按段落分割,并模拟标签输入风格的正文输入
|
||||
paragraphs = text.split('\n\n')
|
||||
for i, paragraph in enumerate(paragraphs):
|
||||
for i, paragraph in enumerate(paragraphs[:3]): # 只取前3段避免过长
|
||||
if not paragraph.strip():
|
||||
continue
|
||||
|
||||
# 逐字符输入
|
||||
for j, char in enumerate(paragraph):
|
||||
# 特殊字符处理
|
||||
if char in ',。!?、':
|
||||
# 中文标点输入稍慢
|
||||
await page.keyboard.type(char, delay=random.randint(200, 300))
|
||||
await asyncio.sleep(random.uniform(0.1, 0.2))
|
||||
elif char in ',.!?':
|
||||
# 英文标点输入较快
|
||||
await page.keyboard.type(char, delay=random.randint(150, 250))
|
||||
await asyncio.sleep(random.uniform(0.08, 0.15))
|
||||
else:
|
||||
# 普通字符正常速度
|
||||
await page.keyboard.type(char, delay=random.randint(100, 200))
|
||||
await asyncio.sleep(random.uniform(0.05, 0.1))
|
||||
|
||||
# 每输入15-25个字符后可能停顿思考
|
||||
if j > 0 and j % random.randint(15, 25) == 0:
|
||||
await asyncio.sleep(random.uniform(0.3, 0.8))
|
||||
|
||||
# 段落间添加换行和思考时间
|
||||
if i < len(paragraphs) - 1:
|
||||
# 段落结束,停顿思考
|
||||
await asyncio.sleep(random.uniform(0.5, 1.0))
|
||||
# 输入两个换行
|
||||
await page.keyboard.press("Enter")
|
||||
for char in paragraph:
|
||||
# 标签输入风格:较慢的速度,更谨慎
|
||||
await page.keyboard.type(char, delay=random.randint(200, 400))
|
||||
await asyncio.sleep(random.uniform(0.1, 0.2))
|
||||
|
||||
# 随机停顿,模拟思考
|
||||
if random.random() < 0.3:
|
||||
await asyncio.sleep(random.uniform(0.5, 1.0))
|
||||
|
||||
# 段落间停顿
|
||||
if i < len(paragraphs) - 1:
|
||||
await asyncio.sleep(random.uniform(0.8, 1.2))
|
||||
await page.keyboard.press("Enter")
|
||||
# 准备输入下一段
|
||||
await asyncio.sleep(random.uniform(0.8, 1.5))
|
||||
await page.keyboard.press("Enter")
|
||||
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
async def test_typing():
|
||||
async def test_typing_methods():
|
||||
"""测试四种不同的输入方法"""
|
||||
test_text = """这是一段测试文本,用来展示人类化输入效果。
|
||||
|
||||
这是第二段落,包含一些标点符号:,。!?
|
||||
@ -86,16 +174,6 @@ async def test_typing():
|
||||
✅自驾导航「北京环球城市大道」,停车场直通园区
|
||||
⏰建议早9点开园前排队,热门项目可节省1小时等待
|
||||
|
||||
🎢【必玩项目清单】
|
||||
🔥哈利波特:对角巷买黄油啤酒,探索霍格沃茨城堡内部奥秘(需提前线上预约)
|
||||
🔥侏罗纪世界:大冒险激流勇进必玩,恐龙特效超震撼
|
||||
🔥变形金刚:霸天虎过山车速度与激情现场版
|
||||
🔥小黄人乐园:适合带娃拍照,旋转扫把项目萌趣满分
|
||||
|
||||
🍽️【美食玄学】
|
||||
✔️好莱坞街区「霓虹街市集」汉堡配奶昔
|
||||
✔️功夫熊猫「平先生面馆」云吞面地道
|
||||
⚠️景区内餐标偏高,建议自带水杯续水
|
||||
"""
|
||||
|
||||
async with async_playwright() as playwright:
|
||||
@ -104,103 +182,261 @@ async def test_typing():
|
||||
context = await browser.new_context(permissions=['clipboard-read', 'clipboard-write'])
|
||||
page = await context.new_page()
|
||||
|
||||
# 创建一个简单的HTML页面
|
||||
# 创建一个简单的HTML页面,包含四个测试区域
|
||||
await page.set_content('''
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>输入测试对比</title>
|
||||
<title>输入方法测试对比</title>
|
||||
<style>
|
||||
body { padding: 20px; font-family: Arial, sans-serif; }
|
||||
.container {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.input-box {
|
||||
flex: 1;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.textarea {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
padding: 10px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
margin-top: 10px;
|
||||
height: 300px;
|
||||
}
|
||||
h2 { margin-bottom: 5px; }
|
||||
h2 { margin-bottom: 10px; color: #333; }
|
||||
h3 { margin-bottom: 5px; color: #666; }
|
||||
.description {
|
||||
color: #666;
|
||||
margin-bottom: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
hr {
|
||||
margin: 20px 0;
|
||||
border: 1px solid #eee;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h2>正文输入测试</h2>
|
||||
<h1>输入方法测试评估</h1>
|
||||
|
||||
<div class="container">
|
||||
<h2>1. 复制粘贴方法测试</h2>
|
||||
<div class="input-box">
|
||||
<h3>优化版输入</h3>
|
||||
<div class="description">使用jieba分词的智能输入</div>
|
||||
<textarea id="enhanced-area" class="textarea" placeholder="优化版输入区域..."></textarea>
|
||||
<h3>PasteTypingSimulator</h3>
|
||||
<div class="description">使用PasteTypingSimulator类,确保文本被复制到剪贴板后再粘贴</div>
|
||||
<textarea id="paste-method" class="textarea" placeholder="复制粘贴方法测试区域..."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
|
||||
<div class="container">
|
||||
<h2>2. 优化人类输入测试</h2>
|
||||
<div class="input-box">
|
||||
<h3>小红书版输入</h3>
|
||||
<div class="description">模拟小红书的输入逻辑</div>
|
||||
<textarea id="xhs-area" class="textarea" placeholder="小红书版输入区域..."></textarea>
|
||||
<h3>HumanTypingWrapper</h3>
|
||||
<div class="description">使用HumanTypingWrapper类,提供更高级的人类化输入功能</div>
|
||||
<textarea id="human-wrapper-method" class="textarea" placeholder="优化人类输入测试区域..."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
|
||||
<div class="container">
|
||||
<h2>3. 模拟人类输入文本测试</h2>
|
||||
<div class="input-box">
|
||||
<h3>复制粘贴版输入</h3>
|
||||
<div class="description">模拟用户复制粘贴行为</div>
|
||||
<textarea id="paste-area" class="textarea" placeholder="复制粘贴版输入区域..."></textarea>
|
||||
<h3>HumanLikeTyper</h3>
|
||||
<div class="description">使用HumanLikeTyper类,模拟基本的人类输入行为</div>
|
||||
<textarea id="human-like-method" class="textarea" placeholder="模拟人类输入测试区域..."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
|
||||
<div class="container">
|
||||
<h2>4. 小红书标签输入风格测试</h2>
|
||||
<div class="input-box">
|
||||
<h3>Xiaohongshu Tag Style</h3>
|
||||
<div class="description">从examples/upload_video_to_xiaohongshu.py提取的标签输入风格,用于输入正文</div>
|
||||
<textarea id="xhs-tag-method" class="textarea" placeholder="小红书标签输入风格测试区域..."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
''')
|
||||
|
||||
# 创建输入模拟器实例
|
||||
enhanced_typer = EnhancedHumanTypingSimulator(page)
|
||||
# 创建行为记录器和检测器
|
||||
recorder = InputRecorder()
|
||||
detector = BotDetector()
|
||||
results = {}
|
||||
|
||||
print("\n=== 开始输入方法测试评估 ===\n")
|
||||
|
||||
# 1. 测试 PasteTypingSimulator
|
||||
print("1. 测试 PasteTypingSimulator (复制粘贴方法)")
|
||||
print("-" * 50)
|
||||
recorder.start()
|
||||
|
||||
# 初始化 PasteTypingSimulator 并执行粘贴操作
|
||||
paste_typer = PasteTypingSimulator(page)
|
||||
recorder.record("prepare")
|
||||
success = await paste_typer.paste_text("#paste-method", test_text)
|
||||
recorder.record("paste_complete")
|
||||
|
||||
# 执行正文输入测试
|
||||
print("\n=== 开始正文输入测试 ===")
|
||||
print("1. 开始复制粘贴版输入测试...")
|
||||
# 找到目标输入框并聚焦
|
||||
text_area = await page.wait_for_selector("#paste-area")
|
||||
await text_area.click()
|
||||
print(f"粘贴操作成功: {success}")
|
||||
|
||||
# 使用 keyboard API 直接模拟输入
|
||||
# 先全选当前内容(如果有的话)
|
||||
await page.keyboard.press("Control+A")
|
||||
await asyncio.sleep(random.uniform(0.1, 0.2))
|
||||
# 分析行为
|
||||
timestamps, actions = recorder.get_records()
|
||||
paste_result = detector.analyze_typing_pattern(timestamps, actions)
|
||||
results["paste_typing"] = paste_result
|
||||
|
||||
# 输入文本
|
||||
await page.keyboard.type(test_text)
|
||||
print("复制粘贴版输入完成!")
|
||||
print("行为分析结果:")
|
||||
print(detector.format_detection_result(paste_result))
|
||||
print("\n")
|
||||
|
||||
await asyncio.sleep(2)
|
||||
|
||||
print("\n2. 开始优化版输入测试...")
|
||||
await enhanced_typer.type_text(test_text, "#enhanced-area")
|
||||
print("优化版输入完成!")
|
||||
# 2. 测试 HumanTypingWrapper
|
||||
print("2. 测试 HumanTypingWrapper (优化人类输入)")
|
||||
print("-" * 50)
|
||||
recorder.start()
|
||||
|
||||
# 初始化 HumanTypingWrapper 并执行输入操作
|
||||
human_wrapper = HumanTypingWrapper(page)
|
||||
# 记录每个字符的输入动作
|
||||
success = await human_wrapper.type_text_human("#human-wrapper-method", test_text)
|
||||
|
||||
# 为每个字符记录动作
|
||||
for char in test_text:
|
||||
recorder.record("type" if char.isalnum() else "special" if char in ",。!?、,.!?" else "space")
|
||||
|
||||
print(f"人类化输入成功: {success}")
|
||||
|
||||
# 分析行为
|
||||
timestamps, actions = recorder.get_records()
|
||||
wrapper_result = detector.analyze_typing_pattern(timestamps, actions)
|
||||
results["human_wrapper"] = wrapper_result
|
||||
|
||||
print("行为分析结果:")
|
||||
print(detector.format_detection_result(wrapper_result))
|
||||
print("\n")
|
||||
|
||||
await asyncio.sleep(2)
|
||||
|
||||
print("\n3. 开始小红书版输入测试...")
|
||||
await xhs_type_text(page, test_text, "#xhs-area")
|
||||
print("小红书版输入完成!")
|
||||
# 3. 测试 HumanLikeTyper
|
||||
print("3. 测试 HumanLikeTyper (模拟人类输入文本)")
|
||||
print("-" * 50)
|
||||
recorder.start()
|
||||
|
||||
# 注意:HumanLikeTyper 是同步的,但我们在异步环境中运行
|
||||
# 我们需要用同步方式处理,或者修改调用方式
|
||||
# 这里我们使用异步方式模拟同步行为
|
||||
try:
|
||||
# 等待元素
|
||||
await page.wait_for_selector("#human-like-method")
|
||||
await page.click("#human-like-method")
|
||||
await asyncio.sleep(0.3)
|
||||
|
||||
# 手动实现类似 HumanLikeTyper 的异步版本
|
||||
current_input = ""
|
||||
for char in test_text[:200]: # 限制字符数避免测试时间过长
|
||||
# 随机停顿
|
||||
if random.random() < 0.1:
|
||||
await asyncio.sleep(random.uniform(0.5, 2.0))
|
||||
|
||||
# 随机错误修正
|
||||
if current_input and random.random() < 0.08:
|
||||
await page.keyboard.press("Backspace")
|
||||
await asyncio.sleep(random.uniform(0.1, 0.3))
|
||||
wrong_char = random.choice("abcdefghijklmnopqrstuvwxyz ")
|
||||
await page.keyboard.type(wrong_char, delay=random.randint(50, 150))
|
||||
await asyncio.sleep(random.uniform(0.1, 0.3))
|
||||
await page.keyboard.press("Backspace")
|
||||
await asyncio.sleep(random.uniform(0.1, 0.3))
|
||||
|
||||
# 计算延迟并输入字符
|
||||
speed = random.uniform(3, 8)
|
||||
delay = 1 / speed + random.uniform(-0.05, 0.1)
|
||||
await page.keyboard.type(char, delay=delay * 1000)
|
||||
|
||||
# 记录动作
|
||||
recorder.record("type" if char.isalnum() else "special" if char in ",。!?、,.!?" else "space")
|
||||
|
||||
current_input += char
|
||||
await asyncio.sleep(random.uniform(0.01, 0.05))
|
||||
|
||||
success = True
|
||||
except Exception as e:
|
||||
print(f"HumanLikeTyper 测试出错: {e}")
|
||||
success = False
|
||||
|
||||
print(f"模拟人类输入成功: {success}")
|
||||
|
||||
# 分析行为
|
||||
timestamps, actions = recorder.get_records()
|
||||
human_like_result = detector.analyze_typing_pattern(timestamps, actions)
|
||||
results["human_like"] = human_like_result
|
||||
|
||||
print("行为分析结果:")
|
||||
print(detector.format_detection_result(human_like_result))
|
||||
print("\n")
|
||||
|
||||
await asyncio.sleep(2)
|
||||
|
||||
# 4. 测试小红书标签输入风格
|
||||
print("4. 测试 小红书标签输入风格")
|
||||
print("-" * 50)
|
||||
recorder.start()
|
||||
|
||||
# 使用从examples中提取的标签输入方法
|
||||
await xiaohongshu_tag_input_from_examples(page, test_text, "#xhs-tag-method")
|
||||
|
||||
|
||||
# 为每个字符记录动作
|
||||
for char in test_text:
|
||||
recorder.record("xhs_tag_type" if char.isalnum() else "xhs_tag_special" if char in ",。!?、,.!?" else "xhs_tag_space")
|
||||
|
||||
print("小红书标签输入风格测试完成")
|
||||
|
||||
# 分析行为
|
||||
timestamps, actions = recorder.get_records()
|
||||
xhs_tag_result = detector.analyze_typing_pattern(timestamps, actions)
|
||||
results["xhs_tag"] = xhs_tag_result
|
||||
|
||||
print("行为分析结果:")
|
||||
print(detector.format_detection_result(xhs_tag_result))
|
||||
print("\n")
|
||||
|
||||
await asyncio.sleep(2)
|
||||
|
||||
# 总结所有方法的测试结果
|
||||
print("=== 输入方法测试评估总结 ===\n")
|
||||
|
||||
method_names = {
|
||||
"paste_typing": "1. PasteTypingSimulator (复制粘贴)",
|
||||
"human_wrapper": "2. HumanTypingWrapper (优化人类输入)",
|
||||
"human_like": "3. HumanLikeTyper (模拟人类输入)",
|
||||
"xhs_tag": "4. 小红书标签输入风格"
|
||||
}
|
||||
|
||||
for method_key, method_name in method_names.items():
|
||||
if method_key in results:
|
||||
result = results[method_key]
|
||||
risk_level = "低" if result["risk_score"] < 30 else "中" if result["risk_score"] < 70 else "高"
|
||||
print(f"{method_name}: 风险评分 {result['risk_score']}/100 (风险等级: {risk_level})")
|
||||
|
||||
if result["reasons"]:
|
||||
print(" 可疑特征:")
|
||||
for reason in result["reasons"]:
|
||||
print(f" - {reason}")
|
||||
else:
|
||||
print(" 未发现可疑特征")
|
||||
print()
|
||||
|
||||
# 找出最佳表现的方法
|
||||
best_method = min(results.items(), key=lambda x: x[1]["risk_score"])
|
||||
print(f"最佳表现方法: {method_names[best_method[0]]} (风险评分: {best_method[1]['risk_score']}/100)")
|
||||
|
||||
# 关闭浏览器
|
||||
await browser.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(test_typing())
|
||||
asyncio.run(test_typing_methods())
|
||||
|
||||
@ -4,59 +4,61 @@ from playwright.sync_api import Page
|
||||
from playwright.async_api import Page as AsyncPage
|
||||
from .human_like import HumanLikeTyper
|
||||
import asyncio
|
||||
from typing import Union, Optional, List, Dict, Any
|
||||
from typing import Union, Optional, Dict, Any
|
||||
|
||||
|
||||
class HumanTypingWrapper:
|
||||
"""
|
||||
人类化输入包装器,提供更高级的人类化输入功能
|
||||
支持同步和异步页面操作
|
||||
人类化输入包装器,提供适度的人类化输入功能
|
||||
支持同步和异步页面操作,设计更加简洁高效
|
||||
"""
|
||||
|
||||
def __init__(self, page: Union[Page, AsyncPage], config: Optional[Dict[str, Any]] = None):
|
||||
self.page = page
|
||||
self.is_async = hasattr(page, 'wait_for_timeout') # 检测是否为异步页面
|
||||
|
||||
# 初始化配置
|
||||
# 初始化配置 - 使用更合理的默认值,简化设计
|
||||
self.config = self._init_config(config)
|
||||
|
||||
# 初始化human_like模块(仅用于同步页面)
|
||||
if not self.is_async:
|
||||
self.human_typer = HumanLikeTyper(page)
|
||||
|
||||
|
||||
def _init_config(self, config: Optional[Dict[str, Any]]) -> Dict[str, Any]:
|
||||
"""初始化配置参数"""
|
||||
"""初始化配置参数
|
||||
采用行业标准的人类行为模拟设计:
|
||||
- 保持合理的输入速度变化
|
||||
- 适度的停顿和错误修正
|
||||
- 简化的参数设置,更易维护
|
||||
"""
|
||||
default_config = {
|
||||
# 输入速度设置(字符/秒)
|
||||
'min_typing_speed': 2,
|
||||
'max_typing_speed': 10,
|
||||
# 基础输入速度设置(更符合真实人类输入速度范围)
|
||||
'min_typing_speed': 2.0,
|
||||
'max_typing_speed': 5.0,
|
||||
|
||||
# 思考停顿设置
|
||||
'pause_probability': 0.15, # 15%的概率停顿
|
||||
'min_pause_duration': 0.3,
|
||||
'max_pause_duration': 2.5,
|
||||
# 适度的思考停顿设置
|
||||
'pause_probability': 0.1, # 每10个字符左右停顿一次
|
||||
'min_pause_duration': 0.2,
|
||||
'max_pause_duration': 1.0,
|
||||
|
||||
# 错误修正设置
|
||||
'correction_probability': 0.0, # 禁用错误修正,确保文字准确性
|
||||
'backspace_probability': 0.0, # 禁用退格重输,确保文字准确性
|
||||
# 简化的错误修正设置 - 不要过多干扰正常输入
|
||||
'correction_probability': 0.05, # 5%的概率进行错误修正
|
||||
|
||||
# 点击前后停顿
|
||||
'click_delay_before': (0.1, 0.4),
|
||||
'click_delay_after': (0.2, 0.6),
|
||||
# 基本的点击前后停顿
|
||||
'click_delay_before': (0.2, 0.5),
|
||||
'click_delay_after': (0.3, 0.7),
|
||||
|
||||
# 输入完成后停顿
|
||||
'finish_pause_probability': 0.4, # 40%的概率在输入完成后停顿
|
||||
'finish_pause_duration': (0.3, 1.2),
|
||||
# 输入完成后的停顿
|
||||
'finish_pause_probability': 0.6,
|
||||
'finish_pause_duration': (0.5, 1.2),
|
||||
|
||||
# 分段输入设置(长文本分段输入)
|
||||
# 分段输入设置 - 更合理的分段长度
|
||||
'chunk_input': True,
|
||||
'max_chunk_length': 50,
|
||||
'chunk_pause_duration': (0.5, 1.5),
|
||||
'max_chunk_length': 50, # 更长的段落长度,更符合人类阅读习惯
|
||||
'chunk_pause_duration': (0.8, 2.0),
|
||||
|
||||
# 模拟疲劳效果
|
||||
'fatigue_effect': True,
|
||||
'fatigue_threshold': 100, # 输入超过100个字符后开始疲劳
|
||||
'fatigue_slowdown': 0.3, # 疲劳后速度减慢30%
|
||||
# 最小延迟设置 - 确保不会有机器特征明显的快速输入
|
||||
'min_char_delay': 0.08, # 确保字符间延迟不会过低
|
||||
}
|
||||
|
||||
if config:
|
||||
@ -64,35 +66,37 @@ class HumanTypingWrapper:
|
||||
|
||||
return default_config
|
||||
|
||||
def _calculate_typing_delay(self, char_index: int = 0) -> float:
|
||||
"""计算每个字符之间的延迟时间,考虑疲劳效果"""
|
||||
def _calculate_typing_delay(self) -> float:
|
||||
"""简化的延迟计算方法
|
||||
保持合理的随机变化,但避免过度复杂的计算
|
||||
"""
|
||||
# 基础延迟计算
|
||||
base_speed = random.uniform(
|
||||
self.config['min_typing_speed'],
|
||||
self.config['max_typing_speed']
|
||||
)
|
||||
|
||||
# 计算疲劳效果
|
||||
if (self.config['fatigue_effect'] and
|
||||
char_index > self.config['fatigue_threshold']):
|
||||
fatigue_factor = 1 + self.config['fatigue_slowdown']
|
||||
base_speed /= fatigue_factor
|
||||
# 基础延迟(字符间)
|
||||
base_delay = 1 / base_speed
|
||||
|
||||
delay = 1 / base_speed + random.uniform(-0.05, 0.15)
|
||||
return max(0.01, delay) # 确保延迟不为负数
|
||||
# 增加一些随机变化,但保持在合理范围内
|
||||
variation = random.uniform(0.8, 1.3)
|
||||
final_delay = base_delay * variation
|
||||
|
||||
# 确保最小延迟,避免机器特征
|
||||
final_delay = max(self.config['min_char_delay'], final_delay)
|
||||
|
||||
return final_delay
|
||||
|
||||
def _should_pause(self) -> bool:
|
||||
"""判断是否应该停顿思考"""
|
||||
"""简化的停顿判断逻辑"""
|
||||
return random.random() < self.config['pause_probability']
|
||||
|
||||
def _should_correct(self) -> bool:
|
||||
"""判断是否应该进行错误修正"""
|
||||
return random.random() < self.config['correction_probability']
|
||||
|
||||
def _should_backspace(self) -> bool:
|
||||
"""判断是否应该退格重新输入"""
|
||||
return random.random() < self.config['backspace_probability']
|
||||
|
||||
def _get_random_pause_duration(self) -> float:
|
||||
def _get_pause_duration(self) -> float:
|
||||
"""获取随机停顿时长"""
|
||||
return random.uniform(
|
||||
self.config['min_pause_duration'],
|
||||
@ -100,26 +104,22 @@ class HumanTypingWrapper:
|
||||
)
|
||||
|
||||
def _get_wrong_char(self, correct_char: str) -> str:
|
||||
"""生成错误字符"""
|
||||
# 键盘相邻字符映射
|
||||
keyboard_neighbors = {
|
||||
'a': 'sqw', 'b': 'vghn', 'c': 'xdfv', 'd': 'erfcxs', 'e': 'wsdr',
|
||||
'f': 'rtgvcd', 'g': 'tyhbvf', 'h': 'yujnbg', 'i': 'ujko', 'j': 'uikmnh',
|
||||
'k': 'iolmj', 'l': 'opmk', 'm': 'njk', 'n': 'bhjm', 'o': 'iklp',
|
||||
'p': 'ol', 'q': 'wa', 'r': 'edft', 's': 'awedxz', 't': 'rfgy',
|
||||
'u': 'yhij', 'v': 'cfgb', 'w': 'qase', 'x': 'zsdc', 'y': 'tghu',
|
||||
'z': 'asx'
|
||||
}
|
||||
|
||||
char_lower = correct_char.lower()
|
||||
if char_lower in keyboard_neighbors:
|
||||
neighbors = keyboard_neighbors[char_lower]
|
||||
wrong_char = random.choice(neighbors)
|
||||
# 保持原始大小写
|
||||
return wrong_char.upper() if correct_char.isupper() else wrong_char
|
||||
else:
|
||||
# 如果不在映射中,返回随机字符
|
||||
return random.choice('abcdefghijklmnopqrstuvwxyz')
|
||||
"""简化的错误字符生成 - 不需要完整的键盘映射表"""
|
||||
# 只保留几个常见的错误类型,减少复杂性
|
||||
if correct_char.isalpha():
|
||||
# 简单的字母错误(前后移位)
|
||||
char_code = ord(correct_char.lower())
|
||||
if char_code > ord('a') and char_code < ord('z'):
|
||||
# 随机前后移位
|
||||
wrong_offset = random.choice([-1, 1])
|
||||
wrong_char = chr(char_code + wrong_offset)
|
||||
return wrong_char.upper() if correct_char.isupper() else wrong_char
|
||||
# 默认返回一个简单的错误字符
|
||||
return random.choice('abcdefghijklmnopqrstuvwxyz')
|
||||
|
||||
def _record_action(self, action_type: str):
|
||||
"""空实现的_record_action方法,保持兼容性"""
|
||||
pass
|
||||
|
||||
async def _sleep(self, duration: float):
|
||||
"""统一的睡眠方法,兼容同步和异步"""
|
||||
@ -129,11 +129,14 @@ class HumanTypingWrapper:
|
||||
time.sleep(duration)
|
||||
|
||||
async def _type_char(self, char: str, delay: float):
|
||||
"""输入单个字符,兼容同步和异步"""
|
||||
"""简化的字符输入方法"""
|
||||
if self.is_async:
|
||||
await self.page.keyboard.type(char, delay=delay * 1000)
|
||||
else:
|
||||
self.page.keyboard.type(char, delay=delay * 1000)
|
||||
|
||||
# 简单的微小停顿,增加自然感
|
||||
await self._sleep(random.uniform(0.02, 0.05))
|
||||
|
||||
async def _press_key(self, key: str):
|
||||
"""按键操作,兼容同步和异步"""
|
||||
@ -141,6 +144,9 @@ class HumanTypingWrapper:
|
||||
await self.page.keyboard.press(key)
|
||||
else:
|
||||
self.page.keyboard.press(key)
|
||||
|
||||
# 记录按键动作
|
||||
self._record_action(f"key_{key}")
|
||||
|
||||
async def _click_element(self, selector: str):
|
||||
"""点击元素,兼容同步和异步"""
|
||||
@ -148,6 +154,9 @@ class HumanTypingWrapper:
|
||||
await self.page.click(selector)
|
||||
else:
|
||||
self.page.click(selector)
|
||||
|
||||
# 记录点击动作
|
||||
self._record_action("click")
|
||||
|
||||
async def _wait_for_selector(self, selector: str, timeout: int = 30000):
|
||||
"""等待选择器,兼容同步和异步"""
|
||||
@ -156,6 +165,11 @@ class HumanTypingWrapper:
|
||||
else:
|
||||
self.page.wait_for_selector(selector, timeout=timeout)
|
||||
|
||||
async def _perform_random_action(self):
|
||||
"""简化的随机动作执行 - 不需要过多的动作类型"""
|
||||
# 简单的微小停顿就足够模拟人类行为的不确定性
|
||||
await self._sleep(random.uniform(0.1, 0.3))
|
||||
|
||||
async def type_text_human(self, selector: str, text: str, clear_first: bool = True) -> bool:
|
||||
"""
|
||||
以人类化方式在指定元素中输入文本
|
||||
@ -169,26 +183,36 @@ class HumanTypingWrapper:
|
||||
bool: 是否成功输入
|
||||
"""
|
||||
try:
|
||||
# 重置历史记录
|
||||
self.last_delays = []
|
||||
self.action_history = []
|
||||
self.input_timestamps = []
|
||||
|
||||
# 等待元素并点击
|
||||
await self._wait_for_selector(selector)
|
||||
|
||||
# 点击前停顿
|
||||
# 点击前停顿 - 增加随机性和持续时间
|
||||
delay_before = random.uniform(*self.config['click_delay_before'])
|
||||
await self._sleep(delay_before)
|
||||
|
||||
# 模拟光标移动到元素的过程
|
||||
await self._sleep(random.uniform(0.1, 0.3))
|
||||
|
||||
# 点击元素
|
||||
await self._click_element(selector)
|
||||
|
||||
# 点击后停顿
|
||||
# 点击后停顿 - 增加随机性和持续时间
|
||||
delay_after = random.uniform(*self.config['click_delay_after'])
|
||||
await self._sleep(delay_after)
|
||||
|
||||
# 清空现有内容
|
||||
# 清空现有内容 - 更自然的清空操作,使用更长的延迟
|
||||
if clear_first:
|
||||
# 增加随机延迟,使清空操作更自然
|
||||
await self._sleep(random.uniform(0.2, 0.4))
|
||||
await self._press_key("Control+A")
|
||||
await self._sleep(0.1)
|
||||
await self._sleep(random.uniform(0.15, 0.3))
|
||||
await self._press_key("Delete")
|
||||
await self._sleep(0.2)
|
||||
await self._sleep(random.uniform(0.3, 0.6))
|
||||
|
||||
# 分段输入长文本
|
||||
if self.config['chunk_input'] and len(text) > self.config['max_chunk_length']:
|
||||
@ -196,11 +220,14 @@ class HumanTypingWrapper:
|
||||
else:
|
||||
await self._type_text_continuously(text)
|
||||
|
||||
# 输入完成后可能停顿
|
||||
# 输入完成后可能停顿 - 更自然的结束停顿
|
||||
if random.random() < self.config['finish_pause_probability']:
|
||||
pause_duration = random.uniform(*self.config['finish_pause_duration'])
|
||||
await self._sleep(pause_duration)
|
||||
|
||||
# 输入完成后的最终停顿
|
||||
await self._sleep(random.uniform(0.2, 0.5))
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
@ -208,73 +235,56 @@ class HumanTypingWrapper:
|
||||
return False
|
||||
|
||||
async def _type_text_continuously(self, text: str):
|
||||
"""连续输入文本,包含人类化效果"""
|
||||
current_input = ""
|
||||
|
||||
for i, char in enumerate(text):
|
||||
# 随机停顿思考
|
||||
"""简化的连续输入方法
|
||||
保留必要的人类特征,但避免过度复杂化
|
||||
"""
|
||||
for char in text:
|
||||
# 合理的停顿思考
|
||||
if self._should_pause():
|
||||
pause_duration = self._get_random_pause_duration()
|
||||
pause_duration = self._get_pause_duration()
|
||||
await self._sleep(pause_duration)
|
||||
|
||||
# 随机错误修正
|
||||
if current_input and self._should_correct():
|
||||
await self._simulate_correction(char)
|
||||
|
||||
# 随机退格重新输入
|
||||
if current_input and self._should_backspace():
|
||||
await self._simulate_backspace_retype(char)
|
||||
# 适度的错误修正
|
||||
if random.random() < self.config['correction_probability']:
|
||||
# 简化的错误修正
|
||||
wrong_char = self._get_wrong_char(char)
|
||||
await self._type_char(wrong_char, self._calculate_typing_delay())
|
||||
await self._sleep(random.uniform(0.1, 0.3)) # 发现错误后的短暂停顿
|
||||
await self._press_key("Backspace") # 删除错误字符
|
||||
await self._sleep(random.uniform(0.1, 0.2)) # 删除后的停顿
|
||||
|
||||
# 计算延迟并输入字符
|
||||
delay = self._calculate_typing_delay(i)
|
||||
delay = self._calculate_typing_delay()
|
||||
await self._type_char(char, delay)
|
||||
|
||||
current_input += char
|
||||
|
||||
# 字符间微小停顿
|
||||
micro_pause = random.uniform(0.01, 0.08)
|
||||
await self._sleep(micro_pause)
|
||||
|
||||
async def _type_text_in_chunks(self, text: str):
|
||||
"""分段输入长文本"""
|
||||
"""简化的分段输入方法
|
||||
保持基本的段落分割逻辑,但简化实现"""
|
||||
chunk_size = self.config['max_chunk_length']
|
||||
chunks = [text[i:i+chunk_size] for i in range(0, len(text), chunk_size)]
|
||||
|
||||
for i, chunk in enumerate(chunks):
|
||||
# 简单的分段处理
|
||||
for i in range(0, len(text), chunk_size):
|
||||
# 获取当前段落,稍微随机化长度
|
||||
actual_chunk_size = min(chunk_size, len(text) - i)
|
||||
|
||||
# 如果不是最后一段,尽量在空格处分段
|
||||
if i + actual_chunk_size < len(text):
|
||||
# 寻找段落内最近的空格
|
||||
space_pos = text.rfind(' ', i, i + actual_chunk_size + 1)
|
||||
if space_pos > i:
|
||||
actual_chunk_size = space_pos - i + 1
|
||||
|
||||
# 输入当前段落
|
||||
chunk = text[i:i + actual_chunk_size]
|
||||
await self._type_text_continuously(chunk)
|
||||
|
||||
# 段落间停顿(除了最后一段)
|
||||
if i < len(chunks) - 1:
|
||||
# 段落间停顿
|
||||
if i + actual_chunk_size < len(text):
|
||||
pause_duration = random.uniform(*self.config['chunk_pause_duration'])
|
||||
await self._sleep(pause_duration)
|
||||
|
||||
async def _simulate_correction(self, next_char: str):
|
||||
"""模拟输入错误并修正"""
|
||||
# 删除最后一个字符
|
||||
await self._press_key("Backspace")
|
||||
await self._sleep(random.uniform(0.1, 0.4))
|
||||
|
||||
# 输入错误字符
|
||||
wrong_char = self._get_wrong_char(next_char)
|
||||
delay = self._calculate_typing_delay()
|
||||
await self._type_char(wrong_char, delay)
|
||||
await self._sleep(random.uniform(0.2, 0.5))
|
||||
|
||||
# 删除错误字符
|
||||
await self._press_key("Backspace")
|
||||
await self._sleep(random.uniform(0.1, 0.3))
|
||||
|
||||
async def _simulate_backspace_retype(self, target_char: str):
|
||||
"""模拟退格后重新输入"""
|
||||
backspace_count = random.randint(1, min(3, len(target_char)))
|
||||
|
||||
# 执行退格
|
||||
for _ in range(backspace_count):
|
||||
await self._press_key("Backspace")
|
||||
await self._sleep(random.uniform(0.1, 0.2))
|
||||
|
||||
# 短暂停顿
|
||||
await self._sleep(random.uniform(0.2, 0.6))
|
||||
# 移除了专门的_simul ate_correction和_simul ate_backspace_retype方法
|
||||
# 将这些功能合并到_type_text_continuously中,简化实现
|
||||
|
||||
async def click_and_type(self, selector: str, text: str, **kwargs) -> bool:
|
||||
"""
|
||||
@ -304,7 +314,7 @@ class HumanTypingWrapper:
|
||||
try:
|
||||
await self._wait_for_selector(selector)
|
||||
|
||||
# 点击前停顿
|
||||
# 点击前停顿 - 增加随机性
|
||||
delay_before = random.uniform(*self.config['click_delay_before'])
|
||||
await self._sleep(delay_before)
|
||||
|
||||
@ -334,9 +344,12 @@ class HumanTypingWrapper:
|
||||
|
||||
for i in range(amount):
|
||||
await self._press_key(key)
|
||||
# 滚动间隔
|
||||
pause = random.uniform(0.3, 0.8)
|
||||
# 滚动间隔 - 增加随机性和一致性
|
||||
pause = random.uniform(0.5, 1.2) # 增加滚动间隔
|
||||
await self._sleep(pause)
|
||||
|
||||
# 记录滚动动作
|
||||
self._record_action(f"scroll_{direction}")
|
||||
|
||||
def update_config(self, new_config: Dict[str, Any]):
|
||||
"""更新配置"""
|
||||
|
||||
@ -17,23 +17,65 @@ class PasteTypingSimulator:
|
||||
'scroll_delay': (0.5, 1.0), # 滚动时的停顿
|
||||
}
|
||||
|
||||
async def paste_text(self, text: str, selector: str = None) -> bool:
|
||||
"""模拟用户粘贴文本的行为"""
|
||||
async def paste_text(self, selector: str, text: str = None) -> bool:
|
||||
"""模拟用户粘贴文本的行为
|
||||
|
||||
Args:
|
||||
selector: 输入框的选择器
|
||||
text: 要粘贴的文本,如果不提供则使用剪贴板中的内容
|
||||
"""
|
||||
try:
|
||||
if selector:
|
||||
# 找到目标输入框并聚焦
|
||||
element = await self.page.wait_for_selector(selector)
|
||||
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))
|
||||
# 1. 先等待目标元素并点击以获取焦点
|
||||
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.type(text, delay=10) # 使用很小的延迟来模拟粘贴的快速输入
|
||||
# 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
|
||||
@ -42,6 +84,45 @@ class PasteTypingSimulator:
|
||||
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:
|
||||
@ -67,40 +148,55 @@ class PasteTypingSimulator:
|
||||
# 模拟思考和准备时间
|
||||
await asyncio.sleep(random.uniform(*self.config['pre_paste_delay']))
|
||||
|
||||
# 模拟按下 Ctrl 键
|
||||
# 模拟按下 Ctrl 键,更真实的用户操作
|
||||
await self.page.keyboard.down("Control")
|
||||
await asyncio.sleep(random.uniform(0.05, 0.1))
|
||||
|
||||
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.05, 0.1))
|
||||
await asyncio.sleep(random.uniform(0.1, 0.2))
|
||||
|
||||
# 释放 Ctrl 键
|
||||
await self.page.keyboard.up("Control")
|
||||
|
||||
async def _perform_paste(self):
|
||||
"""执行粘贴操作"""
|
||||
# 等待内容出现
|
||||
await asyncio.sleep(random.uniform(0.3, 0.5))
|
||||
|
||||
# 等待内容出现,给足够的时间让内容粘贴完成
|
||||
await asyncio.sleep(random.uniform(0.4, 0.7))
|
||||
|
||||
# 验证粘贴是否成功(可选)
|
||||
try:
|
||||
# 可以添加验证逻辑来确认内容是否已粘贴
|
||||
pass
|
||||
except Exception:
|
||||
# 即使验证失败也不中断流程
|
||||
pass
|
||||
|
||||
async def _post_paste_actions(self):
|
||||
"""模拟粘贴后的检查和调整动作"""
|
||||
# 模拟粘贴后的停顿
|
||||
await asyncio.sleep(random.uniform(*self.config['post_paste_delay']))
|
||||
|
||||
# 随机检查内容
|
||||
if random.random() < self.config['review_probability']:
|
||||
# 模拟上下滚动查看内容
|
||||
# 模拟鼠标滚动查看内容
|
||||
if random.random() < self.config['scroll_probability']:
|
||||
# 向下滚动
|
||||
await self.page.keyboard.press("PageDown")
|
||||
await asyncio.sleep(random.uniform(*self.config['scroll_delay']))
|
||||
# 向上滚动
|
||||
await self.page.keyboard.press("PageUp")
|
||||
await asyncio.sleep(random.uniform(*self.config['scroll_delay']))
|
||||
# 向下滚动一点
|
||||
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))
|
||||
|
||||
# 模拟检查时间
|
||||
await asyncio.sleep(random.uniform(*self.config['review_time']))
|
||||
# 随机点击文本框内部的某个位置(模拟检查或准备编辑)
|
||||
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']:
|
||||
@ -114,19 +210,25 @@ class PasteTypingSimulator:
|
||||
async def paste_with_format_check(self, text: str, selector: str = None) -> bool:
|
||||
"""带格式检查的粘贴方法"""
|
||||
try:
|
||||
# 模拟复制文本到剪贴板
|
||||
await self.page.evaluate(f'navigator.clipboard.writeText(`{text}`)')
|
||||
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']))
|
||||
|
||||
if selector:
|
||||
await self._prepare_input(selector)
|
||||
|
||||
# 模拟粘贴操作
|
||||
await self.page.keyboard.down("Control")
|
||||
await asyncio.sleep(random.uniform(0.05, 0.1))
|
||||
await self.page.keyboard.press("v")
|
||||
await asyncio.sleep(random.uniform(0.05, 0.1))
|
||||
await self.page.keyboard.up("Control")
|
||||
# 模拟真实的粘贴操作
|
||||
await self._pre_paste_actions()
|
||||
await self._perform_paste()
|
||||
|
||||
# 模拟粘贴后的检查
|
||||
await asyncio.sleep(random.uniform(*self.config['post_paste_delay']))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user