返回博客

如何通过时间攻击避免自动化检测:Selenium和Puppeteer的保护措施

时间攻击是检测自动化最复杂的方法之一。我们将讨论如何防止浏览器中操作执行时间的分析。

📅2026年1月10日
```html

时间攻击是一种基于分析浏览器中操作执行时间的机器人检测方法。现代的反欺诈系统(如Facebook、Google、TikTok等平台)不仅分析你在做什么,还分析你做得有多快。过快的点击、页面瞬间加载、缺乏自然的停顿——这些都表明了自动化。在本文中,我们将讨论针对使用Selenium、Puppeteer和反检测浏览器的开发者的时间攻击保护技术方法。

什么是时间攻击及其工作原理

时间攻击是一种基于测量用户操作之间时间间隔的自动化检测方法。反欺诈系统收集遥测数据:从页面加载到第一次点击的时间、用户滚动的速度、填写表单时是否有停顿。这些数据与真实用户的行为模型进行比较。

保护系统分析的主要时间指标包括:

  • 首次交互时间(TTFI)——从页面加载到第一次操作(点击、滚动、输入文本)的时间。机器人通常在DOM加载后立即开始操作,而人类则在0.5-3秒后。
  • 点击时间模式——点击之间的间隔。自动化脚本通常以相同的频率点击(例如,每2秒一次),而人类则是随机的。
  • 打字速度一致性——输入文本的速度。机器人瞬间输入文本或在字符之间有固定延迟,而人类则以变化的速度和停顿输入。
  • 鼠标移动速度——光标移动的速度。Selenium默认瞬间将光标传送到所需位置,而人类则以加速和减速的方式移动鼠标。
  • 滚动行为——页面滚动模式。机器人通常以固定的像素数滚动,而人类则是不均匀的,伴随停顿。

检测系统使用机器学习分析这些指标。它们构建行为档案并计算用户是机器人还是人的概率。如果时间模式过于完美或过快——这就是一个红色警报。

重要:时间攻击对大规模自动化特别有效。如果你同时运行100个具有相同时间模式的浏览器,反欺诈系统很容易通过统计异常发现它们。

通过时间模式检测自动化的方法

现代反欺诈系统使用多个层次的时间特征分析。我们来看看Facebook、Google、Cloudflare等平台使用的具体技术。

1. 性能API分析

浏览器提供性能API,收集页面加载的详细遥测数据。反欺诈系统分析:

// 性能API数据示例
performance.timing = {
  navigationStart: 1234567890000,
  domLoading: 1234567890150,      // +150ms
  domInteractive: 1234567890300,  // +300ms
  domComplete: 1234567890500,     // +500ms
  loadEventEnd: 1234567890600     // +600ms
}

// 机器人可疑模式:
// - 加载过快(domComplete < 200ms)
// - 事件之间的间隔完全均匀
// - 加载外部资源时没有延迟

无头浏览器(尤其是旧版本的Puppeteer和Selenium)通常会显示异常快速的navigationStart和domLoading值,因为它们不会像普通浏览器那样加载图像、字体和其他资源。

2. 事件时间分析

JavaScript跟踪器跟踪所有事件(点击、鼠标移动、按键)的时间戳并分析模式:

// 事件遥测收集示例
const events = [];

document.addEventListener('click', (e) => {
  events.push({
    type: 'click',
    timestamp: performance.now(),
    x: e.clientX,
    y: e.clientY
  });
});

// 分析可疑模式:
// - 点击每N毫秒发生一次
// - 点击前没有微小的鼠标移动
// - 第一次点击在页面加载后立即发生

3. 按键动态

在填写表单时,反欺诈系统分析按键动态——每个人独特的生物特征指标:

  • 停留时间——按键保持时间(从keydown到keyup)。人类的停留时间在50到200毫秒之间,而机器人的停留时间是常量。
  • 飞行时间——释放一个按键到按下下一个按键之间的时间。人类在100到500毫秒之间变化,而机器人则有固定的延迟。
  • 打字节奏——整体打字节奏。人类在标点符号处会停顿,纠正错误,而机器人则不会。

检测示例:如果你在Selenium中使用element.send_keys("文本"),所有文本在1-2毫秒内输入——这立即暴露了自动化。

在代码中模拟人类延迟

防止时间攻击的第一层保护是为操作之间添加延迟。但重要的是,不仅仅是插入time.sleep(2),而是模拟自然行为。

Python中的基本延迟模拟(Selenium)

import time
import random
from selenium import webdriver
from selenium.webdriver.common.by import By

def human_delay(min_sec=0.5, max_sec=2.0):
    """模拟人类的随机延迟"""
    delay = random.uniform(min_sec, max_sec)
    time.sleep(delay)

driver = webdriver.Chrome()
driver.get("https://example.com")

# 第一个操作前的延迟(人类在阅读页面)
human_delay(1.5, 4.0)

# 点击元素
button = driver.find_element(By.ID, "submit-btn")
button.click()

# 下一个操作前的延迟
human_delay(0.8, 2.5)

使用正态分布的高级模拟

均匀分布(uniform)看起来不自然。人类的延迟遵循正态分布并有异常值:

import numpy as np

def realistic_delay(mean=1.5, std_dev=0.5, min_val=0.3, max_val=5.0):
    """
    正态分布的延迟
    mean: 延迟的平均时间
    std_dev: 标准差
    min_val, max_val: 边界(以避免极端值)
    """
    delay = np.random.normal(mean, std_dev)
    delay = max(min_val, min(max_val, delay))  # 限制范围
    time.sleep(delay)
    return delay

# 使用
realistic_delay(mean=2.0, std_dev=0.7)  # 平均2秒,但有变化

上下文延迟

不同的操作需要不同的时间。为不同场景创建延迟配置文件:

class HumanBehavior:
    """不同类型操作的延迟配置文件"""
    
    @staticmethod
    def page_load_delay():
        """页面加载后的延迟(阅读内容)"""
        return realistic_delay(mean=2.5, std_dev=1.0, min_val=1.0, max_val=6.0)
    
    @staticmethod
    def before_click():
        """点击前的延迟(用眼睛寻找元素)"""
        return realistic_delay(mean=0.8, std_dev=0.3, min_val=0.3, max_val=2.0)
    
    @staticmethod
    def before_typing():
        """开始输入文本前的延迟"""
        return realistic_delay(mean=1.2, std_dev=0.5, min_val=0.5, max_val=3.0)
    
    @staticmethod
    def between_form_fields():
        """表单字段之间的延迟"""
        return realistic_delay(mean=0.6, std_dev=0.2, min_val=0.2, max_val=1.5)

# 在脚本中的使用
driver.get("https://example.com/login")
HumanBehavior.page_load_delay()

username_field = driver.find_element(By.ID, "username")
HumanBehavior.before_typing()
# ... 输入用户名 ...

HumanBehavior.between_form_fields()

password_field = driver.find_element(By.ID, "password")
HumanBehavior.before_typing()
# ... 输入密码 ...

操作执行时间的随机化

操作之间相同的延迟是统计异常。如果你运行100个脚本实例,所有实例在加载后恰好在2.5秒后点击按钮——这很容易被检测到。需要在多个层面进行随机化。

1. 操作顺序的随机化

为操作的顺序添加变化。例如,在填写表单之前,有时滚动页面,有时不滚动:

def fill_form_naturally(driver):
    # 30%概率在填写之前滚动页面
    if random.random() < 0.3:
        driver.execute_script("window.scrollBy(0, 200)")
        human_delay(0.5, 1.5)
    
    # 20%概率点击随机位置(模拟阅读)
    if random.random() < 0.2:
        body = driver.find_element(By.TAG_NAME, "body")
        body.click()
        human_delay(0.3, 0.8)
    
    # 主要操作——填写表单
    username_field = driver.find_element(By.ID, "username")
    type_like_human(username_field, "myusername")

2. 可变的打字速度

与其瞬间输入文本,不如模拟逐字输入,速度变化:

def type_like_human(element, text):
    """模拟人类打字速度输入文本"""
    for char in text:
        element.send_keys(char)
        
        # 字符之间的基本延迟
        base_delay = random.uniform(0.05, 0.15)
        
        # 在空格和标点符号上的额外停顿
        if char in [' ', '.', ',', '!', '?']:
            base_delay += random.uniform(0.1, 0.3)
        
        # 随机的“思考”停顿(5%概率长停顿)
        if random.random() < 0.05:
            base_delay += random.uniform(0.5, 1.5)
        
        time.sleep(base_delay)
    
    # 有时会打错字并纠正(10%概率)
    if random.random() < 0.1:
        time.sleep(random.uniform(0.2, 0.5))
        element.send_keys(Keys.BACKSPACE)
        time.sleep(random.uniform(0.1, 0.3))
        element.send_keys(text[-1])  # 重新输入最后一个字符

3. Puppeteer:慢速输入文本

在Puppeteer中,type()方法有内置的delay选项,但需要随机化:

// 基本用法(不推荐——固定延迟)
await page.type('#username', 'myusername', { delay: 100 });

// 正确的方法——为每个字符随机化
async function typeWithVariableSpeed(page, selector, text) {
  await page.click(selector);
  
  for (const char of text) {
    await page.keyboard.type(char);
    
    // 随机延迟50到150毫秒
    let delay = Math.random() * 100 + 50;
    
    // 在空格上的额外停顿
    if (char === ' ') {
      delay += Math.random() * 200 + 100;
    }
    
    // 随机长停顿(5%概率)
    if (Math.random() < 0.05) {
      delay += Math.random() * 1000 + 500;
    }
    
    await page.waitForTimeout(delay);
  }
}

// 使用
await typeWithVariableSpeed(page, '#username', 'myusername');

自然的鼠标移动和滚动速度

Selenium和Puppeteer默认不移动鼠标光标——它们瞬间将光标传送到所需位置并点击。这是自动化最明显的迹象之一。为了模拟人类的鼠标移动,需要使用特殊的库。

用于平滑鼠标移动的pyautogui库

pyautogui库允许通过贝塞尔曲线以加速和减速的方式移动光标:

import pyautogui
from selenium.webdriver.common.action_chains import ActionChains

def move_mouse_naturally(driver, element):
    """模拟人类平滑地移动鼠标到元素上"""
    # 获取元素的坐标
    location = element.location
    size = element.size
    
    # 目标点(元素中心 + 小的随机性)
    target_x = location['x'] + size['width'] / 2 + random.randint(-5, 5)
    target_y = location['y'] + size['height'] / 2 + random.randint(-5, 5)
    
    # 以可变速度平滑移动
    # duration——移动时间(0.5-1.5秒以实现真实感)
    duration = random.uniform(0.5, 1.5)
    
    # tweening——加速函数(easeInOutQuad模拟人类运动)
    pyautogui.moveTo(target_x, target_y, duration=duration, tween=pyautogui.easeInOutQuad)
    
    # 点击前的小停顿(人类不会立即点击)
    time.sleep(random.uniform(0.05, 0.15))
    
    # 点击
    element.click()

重要:此方法仅在Selenium控制真实浏览器窗口时有效(非无头模式)。对于无头模式,鼠标移动是无用的,因为反欺诈系统看不到光标。

Puppeteer:模拟鼠标移动

在Puppeteer中,可以使用ghost-cursor库实现光标的真实移动:

// 安装:npm install ghost-cursor
const { createCursor } = require("ghost-cursor");
const puppeteer = require("puppeteer");

(async () => {
  const browser = await puppeteer.launch({ headless: false });
  const page = await browser.newPage();
  const cursor = createCursor(page);
  
  await page.goto("https://example.com");
  
  // 平滑移动到元素并点击
  const button = await page.$("#submit-btn");
  await cursor.click(button);  // 光标沿贝塞尔曲线移动!
  
  // 或者——移动到坐标
  await cursor.move("#username");  // 仅移动光标而不点击
  await page.waitForTimeout(300);
  await cursor.click();  // 在当前位置点击
})();

模拟滚动

人类不会一次性将页面滚动500像素。滚动应该是不均匀的,伴随停顿,有时还会向后滚动:

def scroll_like_human(driver, target_position=None):
    """
    模拟人类滚动
    target_position: 目标位置(以像素为单位)(如果为None,则滚动到页面底部)
    """
    current_position = driver.execute_script("return window.pageYOffset;")
    
    if target_position is None:
        # 滚动到页面底部
        target_position = driver.execute_script("return document.body.scrollHeight;")
    
    while current_position < target_position:
        # 随机滚动步长(100-400像素)
        scroll_step = random.randint(100, 400)
        current_position += scroll_step
        
        # 滚动
        driver.execute_script(f"window.scrollTo(0, {current_position});")
        
        # 滚动之间的停顿(人类在阅读内容)
        time.sleep(random.uniform(0.3, 1.2))
        
        # 有时向后滚动一点(10%概率)
        if random.random() < 0.1:
            back_scroll = random.randint(50, 150)
            current_position -= back_scroll
            driver.execute_script(f"window.scrollTo(0, {current_position});")
            time.sleep(random.uniform(0.2, 0.6))

# 使用
scroll_like_human(driver, target_position=2000)  # 滚动到2000像素

页面和AJAX请求的加载时间

反欺诈系统分析的不仅是用户的操作,还有页面加载的特征。无头浏览器通常加载得异常快,因为它们不加载图像、不执行某些脚本、不渲染CSS。

问题:加载过快

比较普通浏览器和无头浏览器的性能API的典型值:

指标 普通浏览器 无头(可疑)
domContentLoaded 800-2000毫秒 50-200毫秒
loadEventEnd 2000-5000毫秒 100-500毫秒
加载的资源数量 50-200(图像、CSS、JS) 5-20(仅关键)

解决方案:强制加载延迟

在页面加载后添加人工延迟,使指标看起来更自然:

def load_page_naturally(driver, url):
    """模拟自然加载时间的页面加载"""
    start_time = time.time()
    driver.get(url)
    
    # 等待DOM完全加载
    WebDriverWait(driver, 10).until(
        lambda d: d.execute_script("return document.readyState") == "complete"
    )
    
    # 计算实际加载时间
    actual_load_time = time.time() - start_time
    
    # 如果加载过快(< 1秒),添加延迟
    if actual_load_time < 1.0:
        additional_delay = random.uniform(1.0, 2.5) - actual_load_time
        time.sleep(additional_delay)
    
    # 额外的“阅读页面”延迟
    time.sleep(random.uniform(0.5, 2.0))

# 使用
load_page_naturally(driver, "https://example.com")

等待AJAX请求

现代网站通过AJAX异步加载内容。如果你的脚本在所有AJAX请求完成之前开始操作——这很可疑。使用显式等待:

from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

def wait_for_ajax(driver, timeout=10):
    """等待所有AJAX请求完成(jQuery)"""
    WebDriverWait(driver, timeout).until(
        lambda d: d.execute_script("return jQuery.active == 0")
    )

# 对于没有jQuery的网站——等待特定元素
def wait_for_dynamic_content(driver, selector, timeout=10):
    """等待动态加载的元素出现"""
    WebDriverWait(driver, timeout).until(
        EC.presence_of_element_located((By.CSS_SELECTOR, selector))
    )
    # 元素出现后的额外延迟
    time.sleep(random.uniform(0.3, 0.8))

在反检测浏览器中设置保护

如果你从事仲裁、多账户或其他需要匿名性的任务,请使用反检测浏览器:Dolphin Anty、AdsPower、Multilogin、GoLogin。它们内置了防止时间攻击的机制,但需要正确配置。

Dolphin Anty:设置人类打字

在Dolphin Anty中,有“人类打字”功能——自动模拟人类输入文本。设置:

  • 打开浏览器配置文件→“自动化”选项卡
  • 启用“人类打字模拟”
  • 配置参数:
    • 平均打字速度:150-250字符/分钟(真实速度)
    • 变化:30-50%(字符之间的速度变化)
    • 标点符号停顿:启用(在标点符号处停顿)
    • 随机错字:2-5%(随机错字并纠正)

之后,通过Dolphin API的任何文本输入将自动模拟人类。

AdsPower:设置鼠标移动

AdsPower允许记录真实用户的鼠标移动模式并重现:

  1. 打开配置文件→“高级设置”→“鼠标行为”
  2. 选择“记录真实用户”模式:
    • 在普通模式下打开浏览器
    • 执行典型操作(点击、滚动、鼠标移动)
    • AdsPower将记录移动轨迹
  3. 通过API进行自动化时,AdsPower将重现记录的模式并进行变化

Multilogin:Canvas噪声和WebGL时间

Multilogin在Canvas和WebGL指纹中添加噪声(noise),这影响与渲染相关的时间攻击:

  • 配置文件→“指纹设置”→“Canvas”
  • 启用“Canvas噪声”(在Canvas渲染中添加微延迟)
  • 启用“WebGL元数据掩蔽”(掩蔽影响渲染速度的GPU特征)

这可以防止通过分析Canvas.toDataURL()和WebGL操作的执行时间进行检测。

建议:如果你在Facebook、Instagram或TikTok上进行多账户操作,一定要使用反检测浏览器,并搭配优质的住宅代理——这将最小化链式封禁和IP检测的风险。

绕过时间检测的高级技术

对于特别受保护的平台(如Google、Facebook、银行网站),基本方法可能不足以应对。我们来看看高级技术。

1. 替换性能API

可以重写性能API的方法,以返回逼真的值而不是实际值:

// 注入脚本以替换performance.now()
const script = `
  (function() {
    const originalNow = performance.now.bind(performance);
    let offset = 0;
    let lastValue = 0;
    
    performance.now = function() {
      const realValue = originalNow();
      // 向时间添加随机噪声
      const noise = Math.random() * 2 - 1; // 从-1到+1毫秒
      let fakeValue = realValue + offset + noise;
      
      // 确保单调性(时间不会倒退)
      if (fakeValue <= lastValue) {
        fakeValue = lastValue + 0.1;
      }
      
      lastValue = fakeValue;
      return fakeValue;
    };
  })();
`;

// Puppeteer:在创建页面时注入
await page.evaluateOnNewDocument(script);

注意:此方法可能通过检查原生方法的完整性被检测到。仅在没有强保护的网站上使用。

2. 限制CPU和网络

Chrome DevTools协议允许人为地减慢CPU和网络速度,使加载指标看起来更自然:

// Puppeteer:将CPU速度减慢一半
const client = await page.target().createCDPSession();
await client.send('Emulation.setCPUThrottlingRate', { rate: 2 });

// 限制网络速度(模拟3G)
await page.emulateNetworkConditions({
  offline: false,
  downloadThroughput: 1.5 * 1024 * 1024 / 8, // 1.5 Mbps
  uploadThroughput: 750 * 1024 / 8,          // 750 Kbps
  latency: 40                                 // 40毫秒延迟
});

这将增加页面加载和JavaScript执行的时间,使配置文件更像是具有中等互联网速度的真实用户。

3. 模拟后台活动

真实用户不会一直停留在一个标签页上——他们会在标签页之间切换、最小化窗口、分心。模拟这一点:

async function simulateTabSwitch(page) {
  // 模拟切换到另一个标签页(页面可见性API)
  await page.evaluate(() => {
    Object.defineProperty(document, 'hidden', {
      get: () => true,
      configurable: true
    });
    
    document.dispatchEvent(new Event('visibilitychange'));
  });
  
  // 停顿(用户查看另一个标签页)
  await page.waitForTimeout(Math.random() * 3000 + 2000);
  
  // 返回到标签页
  await page.evaluate(() => {
    Object.defineProperty(document, 'hidden', {
      get: () => false,
      configurable: true
    });
    
    document.dispatchEvent(new Event('visibilitychange'));
  });
}

// 使用:在工作期间随机“分心”
if (Math.random() < 0.15) {  // 15%概率
  await simulateTabSwitch(page);
}

4. 使用真实的用户时间标记

一些网站通过用户时间API创建自己的时间标记。添加逼真的标记:

// 创建逼真的时间标记
await page.evaluate(() => {
  // 模拟用户在行动前的“思考”
  performance.mark('user-started-reading');
  
  setTimeout(() => {
    performance.mark('user-found-button');
    performance.measure('reading-time', 'user-started-reading', 'user-found-button');
  }, Math.random() * 2000 + 1000);
});

5. 代理轮换以降低统计相关性

即使你的每个脚本实例都有随机化的延迟,反欺诈系统仍然可以检测到相关性,如果所有请求都来自同一个IP。使用代理轮换:

  • 用于解析和大规模任务:数据中心代理,每5-10分钟自动轮换
  • 用于社交媒体和广告:住宅代理,绑定会话(sticky sessions)
  • 用于移动平台(Instagram、TikTok):移动代理,按时间或按请求轮换

轮换IP降低了反欺诈系统收集足够数据以进行统计分析你时间模式的可能性。

结论

时间攻击是一种复杂的自动化检测方法,需要综合的保护措施。主要结论:

  • 不要使用固定延迟——始终使用正态分布随机化操作之间的时间
  • 模拟上下文延迟——不同操作需要不同的时间(阅读页面≠点击按钮)
  • 为操作顺序添加变化——随机滚动、点击、切换标签页
  • 使用平滑的鼠标移动——使用ghost-cursor(Puppeteer)或pyautogui(Selenium)库
  • 模拟自然的打字速度——逐字输入,标点符号处停顿
  • 配置反检测浏览器——Dolphin Anty、AdsPower和Multilogin具有内置的防止时间攻击的机制
  • 与优质代理结合使用——IP轮换降低统计分析的可能性

请记住:没有完美的保护。反欺诈系统不断进化,增加新的检测方法。你的任务是尽可能使脚本的行为接近真实用户,并定期更新绕过方法。

如果你从事浏览器自动化以进行解析、测试或其他任务,建议使用住宅代理——它们提供最高级别的匿名性,并最小化IP地址检测的风险。对于移动平台,更适合使用移动代理,它们模拟真实的移动运营商用户。

```