测试脚本和新的人类,复制打字

This commit is contained in:
sini_chen 2025-10-23 11:50:51 +08:00
parent 3b58e22f91
commit 2f61f0a68e
3 changed files with 635 additions and 284 deletions

View File

@ -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())

View File

@ -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]):
"""更新配置"""

View File

@ -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']))