大量请求的防封锁技术与工具
帐户和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)
选择代理类型
对于社交媒体和具有强大保护的平台的大规模操作,使用住宅代理。它们看起来像普通的家庭连接,较少被列入黑名单。数据中心适合于保护较少的资源,速度更重要。
浏览器指纹识别和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
Referer和Origin头显示用户的来源。缺少或不正确的值是红旗。
# 正确的顺序:主页 → 类别 → 商品
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%则需要重新审视策略:可能是过于激进的速率限制、糟糕的代理或指纹识别问题。
结论
保护...