返回博客

如何避免在大量请求时被封锁

我们分析自动化检测机制和针对大规模请求的具体防封技术:从基本的代理轮换到模拟人类行为。

📅2025年12月21日
```html

大量请求的防封锁技术与工具

帐户和IP地址被封锁是爬虫、自动化和社交媒体大规模操作的主要问题。现代反爬虫系统分析数十个参数:从请求频率到浏览器指纹。在本指南中,我们将解析具体的自动化检测机制和实用的绕过方法。

自动化检测机制

现代保护系统使用多层分析来识别机器人。理解这些机制对于选择正确的绕过策略至关重要。

主要分析参数

IP声誉:反爬虫系统检查IP地址的历史、是否属于数据中心、是否在黑名单上。来自知名代理池的IP更容易被封锁。

请求频率(Request Rate):人类无法在一分钟内发送100个请求。系统不仅分析总数,还分析时间分布——请求之间的均匀间隔会显示出机器人行为。

行为模式:操作序列、滚动深度、鼠标移动、页面停留时间。瞬间点击链接而没有延迟的机器人很容易被识别。

技术指纹:User-Agent、HTTP头、头的顺序、TLS指纹、Canvas/WebGL指纹。参数不一致是反爬虫系统的红旗。

参数 分析内容 被检测风险
IP地址 声誉、ASN、地理位置
User-Agent 浏览器版本、操作系统、设备
TLS指纹 加密套件、扩展
HTTP/2指纹 头的顺序、设置
Canvas/WebGL 图形渲染
行为 点击、滚动、时间

速率限制和请求频率控制

控制请求发送速度是防止封锁的第一道防线。即使使用代理轮换,过于激进的爬虫也会导致封锁。

动态延迟

固定间隔(例如,每个请求之间正好2秒)容易被识别。使用具有正态分布的随机延迟:

import time
import random
import numpy as np

def human_delay(min_delay=1.5, max_delay=4.0, mean=2.5, std=0.8):
    """
    生成模拟人类行为的正态分布延迟
    """
    delay = np.random.normal(mean, std)
    # 限制范围
    delay = max(min_delay, min(delay, max_delay))
    
    # 添加微延迟以增加真实感
    delay += random.uniform(0, 0.3)
    
    time.sleep(delay)

# 使用
for url in urls:
    response = session.get(url)
    human_delay(min_delay=2, max_delay=5, mean=3, std=1)

自适应速率限制

更高级的方法是根据服务器的响应调整速度。如果收到429(请求过多)或503的状态码,自动降低速度:

class AdaptiveRateLimiter:
    def __init__(self, initial_delay=2.0):
        self.current_delay = initial_delay
        self.min_delay = 1.0
        self.max_delay = 30.0
        self.error_count = 0
        
    def wait(self):
        time.sleep(self.current_delay + random.uniform(0, 0.5))
        
    def on_success(self):
        # 在成功请求时逐渐加快速度
        self.current_delay = max(
            self.min_delay, 
            self.current_delay * 0.95
        )
        self.error_count = 0
        
    def on_rate_limit(self):
        # 在封锁时急剧减慢速度
        self.error_count += 1
        self.current_delay = min(
            self.max_delay,
            self.current_delay * (1.5 + self.error_count * 0.5)
        )
        print(f"达到速率限制。新延迟:{self.current_delay:.2f}s")

# 应用
limiter = AdaptiveRateLimiter(initial_delay=2.0)

for url in urls:
    limiter.wait()
    response = session.get(url)
    
    if response.status_code == 429:
        limiter.on_rate_limit()
        time.sleep(60)  # 重试前暂停
    elif response.status_code == 200:
        limiter.on_success()
    else:
        # 处理其他错误
        pass

实用建议:不同网站的最佳速度各不相同。大型平台(如Google、Facebook)允许每个IP每分钟5-10个请求。小型网站可能在每小时20-30个请求时就会封锁。始终从保守的速度开始,并逐渐增加负载,同时监控错误率。

代理轮换和IP地址管理

使用单个IP地址进行大量请求会导致封锁。代理轮换可以分散负载并降低被检测的风险。

轮换策略

1. 按请求轮换:每个请求或每N个请求后更换IP。适用于搜索引擎爬虫,其中每个请求的匿名性很重要。

2. 按时间轮换:每5-15分钟更换IP。适用于社交媒体操作,其中会话的稳定性很重要。

3. Sticky sessions:在整个用户会话中使用一个IP(登录、操作序列)。对于具有CSRF保护的网站至关重要。

import requests
from itertools import cycle

class ProxyRotator:
    def __init__(self, proxy_list, rotation_type='request', rotation_interval=10):
        """
        rotation_type: 'request'(每个请求)或'time'(按时间)
        rotation_interval: 请求数量或秒数
        """
        self.proxies = cycle(proxy_list)
        self.current_proxy = next(self.proxies)
        self.rotation_type = rotation_type
        self.rotation_interval = rotation_interval
        self.request_count = 0
        self.last_rotation = time.time()
        
    def get_proxy(self):
        if self.rotation_type == 'request':
            self.request_count += 1
            if self.request_count >= self.rotation_interval:
                self.current_proxy = next(self.proxies)
                self.request_count = 0
                print(f"切换到:{self.current_proxy}")
                
        elif self.rotation_type == 'time':
            if time.time() - self.last_rotation >= self.rotation_interval:
                self.current_proxy = next(self.proxies)
                self.last_rotation = time.time()
                print(f"切换到:{self.current_proxy}")
                
        return {'http': self.current_proxy, 'https': self.current_proxy}

# 示例使用
proxy_list = [
    'http://user:pass@proxy1.example.com:8000',
    'http://user:pass@proxy2.example.com:8000',
    'http://user:pass@proxy3.example.com:8000',
]

rotator = ProxyRotator(proxy_list, rotation_type='request', rotation_interval=5)

for url in urls:
    proxies = rotator.get_proxy()
    response = requests.get(url, proxies=proxies, timeout=10)

选择代理类型

代理类型 信任级别 速度 应用
数据中心 简单爬虫、API
住宅代理 社交媒体、受保护的网站
移动代理 非常高 Instagram、TikTok、反欺诈

对于社交媒体和具有强大保护的平台的大规模操作,使用住宅代理。它们看起来像普通的家庭连接,较少被列入黑名单。数据中心适合于保护较少的资源,速度更重要。

浏览器指纹识别和TLS指纹

即使使用IP轮换,您也可能因浏览器和TLS连接的技术指纹而被识别。这些参数对于每个客户端都是独特的,伪造起来很困难。

TLS指纹识别

在建立HTTPS连接时,客户端发送ClientHello,包含一组支持的加密套件和扩展。这种组合对于每个库都是独特的。例如,Python requests使用OpenSSL,其指纹很容易与Chrome区分开。

问题:标准库(requests、urllib、curl)的指纹与真实浏览器不同。Cloudflare、Akamai、DataDome等服务积极使用TLS指纹识别来封锁机器人。

解决方案:使用模拟浏览器TLS指纹的库。对于Python,可以使用curl_cffi、tls_client或playwright/puppeteer进行完整的浏览器模拟。

# 安装:pip install curl-cffi
from curl_cffi import requests

# 模拟Chrome 110
response = requests.get(
    'https://example.com',
    impersonate="chrome110",
    proxies={'https': 'http://proxy:port'}
)

# 替代方案:tls_client
import tls_client

session = tls_client.Session(
    client_identifier="chrome_108",
    random_tls_extension_order=True
)

response = session.get('https://example.com')

HTTP/2指纹识别

除了TLS,反爬虫系统还分析HTTP/2的参数:头的顺序、SETTINGS帧的设置、流的优先级。标准库不遵循Chrome或Firefox的确切头顺序。

# Chrome的正确头顺序
headers = {
    ':method': 'GET',
    ':authority': 'example.com',
    ':scheme': 'https',
    ':path': '/',
    'sec-ch-ua': '"Not_A Brand";v="8", "Chromium";v="120"',
    'sec-ch-ua-mobile': '?0',
    'sec-ch-ua-platform': '"Windows"',
    'upgrade-insecure-requests': '1',
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)...',
    'accept': 'text/html,application/xhtml+xml...',
    'sec-fetch-site': 'none',
    'sec-fetch-mode': 'navigate',
    'sec-fetch-user': '?1',
    'sec-fetch-dest': 'document',
    'accept-encoding': 'gzip, deflate, br',
    'accept-language': 'en-US,en;q=0.9',
}

Canvas和WebGL指纹识别

浏览器根据GPU、驱动程序和操作系统以不同方式渲染图形。网站利用这一点创建设备的唯一指纹。在使用无头浏览器(Selenium、Puppeteer)时,重要的是掩盖自动化的迹象:

// Puppeteer:隐藏无头模式
const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');

puppeteer.use(StealthPlugin());

const browser = await puppeteer.launch({
    headless: true,
    args: [
        '--disable-blink-features=AutomationControlled',
        '--no-sandbox',
        '--disable-setuid-sandbox',
        `--proxy-server=${proxyUrl}`
    ]
});

const page = await browser.newPage();

// 重写navigator.webdriver
await page.evaluateOnNewDocument(() => {
    Object.defineProperty(navigator, 'webdriver', {
        get: () => false,
    });
});

请求头、cookies和会话管理

正确处理HTTP头和cookies对于模拟真实用户至关重要。这些参数的错误是导致封锁的常见原因。

必需的请求头

模拟Chrome浏览器的最小请求头集:

import requests

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
    'Accept-Language': 'en-US,en;q=0.9',
    'Accept-Encoding': 'gzip, deflate, br',
    'DNT': '1',
    'Connection': 'keep-alive',
    'Upgrade-Insecure-Requests': '1',
    'Sec-Fetch-Dest': 'document',
    'Sec-Fetch-Mode': 'navigate',
    'Sec-Fetch-Site': 'none',
    'Sec-Fetch-User': '?1',
    'Cache-Control': 'max-age=0',
}

session = requests.Session()
session.headers.update(headers)

cookies管理

许多网站在首次访问时设置跟踪cookies,并在后续请求中检查它们的存在。缺少cookies或不匹配的cookies是机器人的标志。

import requests
import pickle

class SessionManager:
    def __init__(self, session_file='session.pkl'):
        self.session_file = session_file
        self.session = requests.Session()
        self.load_session()
        
    def load_session(self):
        """加载保存的会话"""
        try:
            with open(self.session_file, 'rb') as f:
                cookies = pickle.load(f)
                self.session.cookies.update(cookies)
        except FileNotFoundError:
            pass
            
    def save_session(self):
        """保存cookies以供重复使用"""
        with open(self.session_file, 'wb') as f:
            pickle.dump(self.session.cookies, f)
            
    def request(self, url, **kwargs):
        response = self.session.get(url, **kwargs)
        self.save_session()
        return response

# 使用
manager = SessionManager('instagram_session.pkl')
response = manager.request('https://www.instagram.com/explore/')

重要:在轮换代理时,如果cookies与特定IP绑定,请记得重置cookies。IP与cookies不匹配(例如,带有美国地理位置的cookies和来自德国的IP)会引起怀疑。

Referer和Origin

RefererOrigin头显示用户的来源。缺少或不正确的值是红旗。

# 正确的顺序:主页 → 类别 → 商品
session = requests.Session()

# 步骤1:访问主页
response = session.get('https://example.com/')

# 步骤2:转到类别
response = session.get(
    'https://example.com/category/electronics',
    headers={'Referer': 'https://example.com/'}
)

# 步骤3:查看商品
response = session.get(
    'https://example.com/product/12345',
    headers={'Referer': 'https://example.com/category/electronics'}
)

模拟人类行为

技术参数只是问题的一半。现代反爬虫系统分析行为模式:用户如何与页面互动,停留多长时间,鼠标如何移动。

滚动和鼠标移动

在使用Selenium或Puppeteer时,添加随机的鼠标移动和页面滚动:

from selenium import webdriver
from selenium.webdriver.common.action_chains import ActionChains
import random
import time

def human_like_mouse_move(driver):
    """模拟随机的鼠标移动"""
    action = ActionChains(driver)
    
    for _ in range(random.randint(3, 7)):
        x = random.randint(0, 1000)
        y = random.randint(0, 800)
        action.move_by_offset(x, y)
        action.pause(random.uniform(0.1, 0.3))
    
    action.perform()

def human_like_scroll(driver):
    """模拟自然的滚动"""
    total_height = driver.execute_script("return document.body.scrollHeight")
    current_position = 0
    
    while current_position < total_height:
        # 随机滚动步长
        scroll_step = random.randint(100, 400)
        current_position += scroll_step
        
        driver.execute_script(f"window.scrollTo(0, {current_position});")
        
        # 带有变化的暂停
        time.sleep(random.uniform(0.5, 1.5))
        
        # 有时向后滚动一点(如人类所做)
        if random.random() < 0.2:
            back_scroll = random.randint(50, 150)
            current_position -= back_scroll
            driver.execute_script(f"window.scrollTo(0, {current_position});")
            time.sleep(random.uniform(0.3, 0.8))

# 使用
driver = webdriver.Chrome()
driver.get('https://example.com')

human_like_mouse_move(driver)
time.sleep(random.uniform(2, 4))
human_like_scroll(driver)

页面停留时间

真实用户在页面上花时间:阅读内容、查看图像。瞬间点击链接的机器人很容易被识别。

def realistic_page_view(driver, url, min_time=5, max_time=15):
    """
    具有活动的真实页面浏览
    """
    driver.get(url)
    
    # 初始延迟(加载和“阅读”)
    time.sleep(random.uniform(2, 4))
    
    # 滚动
    human_like_scroll(driver)
    
    # 额外活动
    total_time = random.uniform(min_time, max_time)
    elapsed = 0
    
    while elapsed < total_time:
        action_choice = random.choice(['scroll', 'mouse_move', 'pause'])
        
        if action_choice == 'scroll':
            # 小幅向上/向下滚动
            scroll_amount = random.randint(-200, 300)
            driver.execute_script(f"window.scrollBy(0, {scroll_amount});")
            pause = random.uniform(1, 3)
            
        elif action_choice == 'mouse_move':
            human_like_mouse_move(driver)
            pause = random.uniform(0.5, 2)
            
        else:  # pause
            pause = random.uniform(2, 5)
        
        time.sleep(pause)
        elapsed += pause

导航模式

避免可疑的模式:直接跳转到深层页面、忽略主页、顺序访问所有元素而不跳过。

良好实践:

  • 从主页或热门部分开始
  • 使用网站的内部导航,而不是直接URL
  • 有时返回或转到其他部分
  • 变化查看深度:不总是到达底部
  • 添加“错误”:点击不存在的链接、返回

绕过Cloudflare、DataDome和其他保护

专门的反爬虫系统需要综合的方法。它们使用JavaScript挑战、CAPTCHA、实时行为分析。

Cloudflare

Cloudflare使用多个保护层:浏览器完整性检查、JavaScript挑战、CAPTCHA。绕过基础保护只需正确的TLS指纹和执行JavaScript:

# 选项1:cloudscraper(自动解决JS挑战)
import cloudscraper

scraper = cloudscraper.create_scraper(
    browser={
        'browser': 'chrome',
        'platform': 'windows',
        'desktop': True
    }
)

response = scraper.get('https://protected-site.com')

# 选项2:undetected-chromedriver(用于复杂情况)
import undetected_chromedriver as uc

options = uc.ChromeOptions()
options.add_argument('--proxy-server=http://proxy:port')

driver = uc.Chrome(options=options)
driver.get('https://protected-site.com')

# 等待通过挑战
time.sleep(5)

# 获取cookies以供requests使用
cookies = driver.get_cookies()
session = requests.Session()
for cookie in cookies:
    session.cookies.set(cookie['name'], cookie['value'])

DataDome

DataDome实时分析用户行为:鼠标移动、键盘输入、时间间隔。绕过需要一个完整的浏览器来模拟活动:

from playwright.sync_api import sync_playwright
import random

def bypass_datadome(url, proxy=None):
    with sync_playwright() as p:
        browser = p.chromium.launch(
            headless=False,  # DataDome检测无头模式
            proxy={'server': proxy} if proxy else None
        )
        
        context = browser.new_context(
            viewport={'width': 1920, 'height': 1080},
            user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64)...'
        )
        
        page = context.new_page()
        
        # 注入脚本以掩盖自动化
        page.add_init_script("""
            Object.defineProperty(navigator, 'webdriver', {get: () => false});
            window.chrome = {runtime: {}};
        """)
        
        page.goto(url)
        
        # 模拟人类行为
        time.sleep(random.uniform(2, 4))
        
        # 随机鼠标移动
        for _ in range(random.randint(5, 10)):
            page.mouse.move(
                random.randint(100, 1800),
                random.randint(100, 1000)
            )
            time.sleep(random.uniform(0.1, 0.3))
        
        # 滚动
        page.evaluate(f"window.scrollTo(0, {random.randint(300, 800)})")
        time.sleep(random.uniform(1, 2))
        
        content = page.content()
        browser.close()
        
        return content

CAPTCHA

要自动解决CAPTCHA,请使用识别服务(2captcha、Anti-Captcha)或避免策略:

  • 降低请求频率到不触发CAPTCHA的水平
  • 使用声誉良好的干净住宅IP
  • 通过授权账户进行操作(它们的CAPTCHA阈值更高)
  • 分散负载到时间上(避免高峰时段)

监控和处理封锁

即使在最佳实践下,封锁也是不可避免的。快速发现和正确处理它们很重要。

封锁指标

信号 描述 行动
HTTP 429 请求过多 增加延迟,切换IP
HTTP 403 禁止(IP被封) 切换代理,检查指纹
CAPTCHA 需要验证 解决或降低活动
空响应 内容未加载 检查JavaScript、cookies
重定向到/blocked 明确封锁 完全更改策略

重试系统

import requests
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry

def create_session_with_retries():
    """
    带有自动重试和错误处理的会话
    """
    session = requests.Session()
    
    retry_strategy = Retry(
        total=5,
        backoff_factor=2,  # 2, 4, 8, 16, 32秒
        status_forcelist=[429, 500, 502, 503, 504],
        method_whitelist=["GET", "POST"]
    )
    
    adapter = HTTPAdapter(max_retries=retry_strategy)
    session.mount("http://", adapter)
    session.mount("https://", adapter)
    
    return session

def safe_request(url, session, max_attempts=3):
    """
    带有封锁处理的请求
    """
    for attempt in range(max_attempts):
        try:
            response = session.get(url, timeout=15)
            
            # 检查封锁
            if response.status_code == 403:
                print(f"IP被封。正在切换代理...")
                # 切换代理的逻辑
                continue
                
            elif response.status_code == 429:
                wait_time = int(response.headers.get('Retry-After', 60))
                print(f"达到速率限制。等待{wait_time}s...")
                time.sleep(wait_time)
                continue
                
            elif 'captcha' in response.text.lower():
                print("检测到CAPTCHA")
                # 解决CAPTCHA或跳过的逻辑
                return None
                
            return response
            
        except requests.exceptions.Timeout:
            print(f"尝试{attempt + 1}超时")
            time.sleep(5 * (attempt + 1))
            
        except requests.exceptions.ProxyError:
            print("代理错误。正在切换...")
            # 切换代理
            continue
            
    return None

日志记录和分析

跟踪指标以优化策略:

import logging
from collections import defaultdict
from datetime import datetime

class ScraperMetrics:
    def __init__(self):
        self.stats = {
            'total_requests': 0,
            'successful': 0,
            'rate_limited': 0,
            'blocked': 0,
            'captcha': 0,
            'errors': 0,
            'proxy_failures': defaultdict(int)
        }
        
    def log_request(self, status, proxy=None):
        self.stats['total_requests'] += 1
        
        if status == 200:
            self.stats['successful'] += 1
        elif status == 429:
            self.stats['rate_limited'] += 1
        elif status == 403:
            self.stats['blocked'] += 1
            if proxy:
                self.stats['proxy_failures'][proxy] += 1
                
    def get_success_rate(self):
        if self.stats['total_requests'] == 0:
            return 0
        return (self.stats['successful'] / self.stats['total_requests']) * 100
        
    def print_report(self):
        print(f"\n=== 爬虫报告 ===")
        print(f"总请求数:{self.stats['total_requests']}")
        print(f"成功率:{self.get_success_rate():.2f}%")
        print(f"速率限制:{self.stats['rate_limited']}")
        print(f"被封:{self.stats['blocked']}")
        print(f"CAPTCHA:{self.stats['captcha']}")
        
        if self.stats['proxy_failures']:
            print(f"\n问题代理:")
            for proxy, count in sorted(
                self.stats['proxy_failures'].items(), 
                key=lambda x: x[1], 
                reverse=True
            )[:5]:
                print(f"  {proxy}: {count}次失败")

# 使用
metrics = ScraperMetrics()

for url in urls:
    response = safe_request(url, session)
    if response:
        metrics.log_request(response.status_code, current_proxy)
    
metrics.print_report()

最佳指标:成功率超过95%是优秀的结果。80-95%是可接受的,但还有改进的空间。低于80%则需要重新审视策略:可能是过于激进的速率限制、糟糕的代理或指纹识别问题。

结论

保护...

```