如果您从事电商平台爬取、竞争对手价格监控或网站数据采集,那么您一定了解这个问题:网站会封禁IP地址、要求验证码或返回空白页面。封禁率(被封禁请求的百分比)可能达到70-90%,这使得爬取变得不可能。本文将介绍具体方法,帮助您将封禁率降低到5-10%并稳定地采集数据。
我们将探讨技术解决方案(代理轮换、HTTP头、指纹识别)以及行为模式(延迟、模拟用户操作)。所有方法都在爬取Wildberries、Ozon、Avito和国外平台时经过实践验证。
网站为何封禁爬虫:主要触发因素
在探讨防护方法之前,重要的是要理解网站如何识别自动化流量。现代反机器人系统(Cloudflare、Akamai、DataDome、Imperva)会分析每个请求的数十个参数。以下是主要的封禁触发因素:
网络层面的触发因素:
- 来自单个IP地址的请求过多(例如,每分钟100+请求)
- 来自已知数据中心IP段的IP(AWS、Google Cloud、Hetzner)
- 地理位置不匹配:来自俄罗斯的IP请求英文版网站
- IP地址缺少反向DNS记录
HTTP层面的触发因素:
- 缺少或错误的HTTP头(User-Agent、Accept-Language、Referer)
- 头部顺序与浏览器标准不同
- TLS/SSL版本与声明的浏览器不匹配
- 缺少cookie或使用不正确
浏览器层面的触发因素(JavaScript):
- 未执行JavaScript(如果使用简单的HTTP客户端)
- 浏览器指纹识别:Canvas、WebGL、AudioContext、已安装字体
- 缺少鼠标移动、滚动、点击
- 浏览器窗口大小(无头浏览器通常具有非标准尺寸)
- 存在自动化特征:navigator.webdriver、window.chrome属性
行为触发因素:
- 页面间导航过快(少于1秒)
- 请求间隔相同(例如,恰好每2秒)
- 顺序浏览页面(1、2、3、4...)无跳过
- 缺少典型用户操作:搜索、筛选、查看图片
例如,在爬取Wildberries时,典型错误是从单个IP每0.5秒发送请求。Cloudflare反机器人系统会立即识别模式并将IP封禁24小时。真实用户会花5-15秒查看商品卡片、滚动页面、点击图片。
代理轮换:如何正确更换IP地址
使用代理是降低封禁率的基本方法。但重要的不仅是购买代理,还要正确配置轮换。以下是经过验证的策略:
选择爬取代理类型
| 代理类型 | 封禁率 | 速度 | 使用场景 |
|---|---|---|---|
| 数据中心代理 | 高(40-60%) | 非常高 | 无防护的简单网站,大IP池的批量爬取 |
| 住宅代理 | 低(5-15%) | 中等 | 电商平台(Wildberries、Ozon)、带Cloudflare的网站、社交网络 |
| 移动代理 | 非常低(2-8%) | 低 | 具有激进防护的网站、移动应用程序 |
对于电商平台爬取(Wildberries、Ozon、Avito),推荐使用住宅代理——它们拥有真实家庭用户的IP,难以与普通流量区分。数据中心代理适用于防护较弱的网站或需要大数据量时的最高速度。
IP地址轮换策略
策略1:按时间轮换
每5-10分钟更换IP。这是最佳平衡:足够长以避免频繁更换引起怀疑,但足够频繁以避免在单个IP上累积请求历史。
示例: 爬取1000个商品的目录,请求间隔3秒,一个IP将活跃约100个请求,然后进行更换。
策略2:按请求数量轮换
每50-150个请求后更换IP。这有助于避免在单个地址上累积可疑活动。添加随机性:不是恰好100个请求,而是80到120个。
示例: 配置脚本,使其在随机数量的请求(80-120)后从池中轮换代理。
策略3:粘性会话(会话代理)
对于需要授权或使用购物车的网站,使用粘性会话——在会话期间(10-30分钟)固定IP。这允许保留cookie,并且不会在单个会话内更换IP时引起怀疑。
示例: 在Ozon上爬取个人账户时,使用一个IP进行登录和15分钟会话内的所有后续请求。
重要: 不要将同一IP用于不同任务。如果IP在爬取一个网站时被封禁,不要立即将其用于另一个网站——等待24-48小时。
代理池大小
最小池大小取决于爬取强度:
- 低强度(每天最多10,000个请求):10-20个代理
- 中等强度(每天10,000-100,000个请求):50-100个代理
- 高强度(每天超过100,000个请求):200+个代理或带自动轮换的住宅代理
对于每个请求轮换的住宅代理(轮换代理),池大小可以更小,因为提供商会自动从其数百万地址池中替换新IP。
User-Agent和HTTP头:模拟真实浏览器
即使使用良好的代理,如果HTTP头看起来可疑,您也可能被封禁。网站不仅分析User-Agent,还分析头部顺序、它们的值以及彼此之间的对应关系。
正确的User-Agent
不要对所有请求使用相同的User-Agent。创建一个流行浏览器列表并随机选择:
user_agents = [
# Windows上的Chrome
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
# macOS上的Chrome
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
# Windows上的Firefox
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0",
# macOS上的Safari
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1 Safari/605.1.15",
# Windows上的Edge
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0"
]
错误: 使用过时的浏览器版本(例如Chrome 80)——这会立即引起怀疑。每2-3个月更新一次User-Agent列表,在whatismybrowser.com上跟踪当前版本。
完整的HTTP头集合
现代浏览器发送15-20个头部。以下是模拟Chrome的最小必需集合:
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Accept-Language": "ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7",
"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",
"sec-ch-ua": '"Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"Windows"'
}
注意Sec-Fetch-*和sec-ch-ua-*头部——它们出现在新版Chrome中,缺少它们可能暴露自动化。
头部顺序很重要
浏览器以特定顺序发送头部。例如,Chrome总是将Host放在第一位,然后是Connection、User-Agent等。如果您使用Python的requests库,顺序可能是字母顺序,这会暴露自动化。
解决方案:使用正确格式化头部的库(Python的curl_cffi,Node.js的got)或无头浏览器(Puppeteer、Playwright、Selenium),它们像真实浏览器一样生成头部。
请求间延迟:最佳间隔
降低封禁率最简单但最有效的方法之一是请求间的正确延迟。真实用户无法每秒打开10个页面,因此过快的请求会立即导致封禁。
随机延迟而非固定延迟
不要使用固定延迟(例如,请求间恰好2秒)。反机器人系统很容易识别这种模式。使用随机间隔:
import random
import time
# 不要使用固定延迟
time.sleep(2) # ❌ 不好
# 使用随机间隔
delay = random.uniform(2.5, 5.5) # ✅ 好
time.sleep(delay)
不同网站的推荐间隔
| 网站类型 | 最小延迟 | 推荐延迟 | 示例 |
|---|---|---|---|
| 有防护的电商平台 | 3-5秒 | 5-10秒 | Wildberries、Ozon、Lamoda |
| 分类广告网站 | 2-4秒 | 4-8秒 | Avito、Yula、CIAN |
| 新闻网站 | 1-2秒 | 2-4秒 | RBC、Kommersant、Vedomosti |
| 无限制的API | 0.5-1秒 | 1-2秒 | 开放API、RSS源 |
基于服务器响应的自适应延迟
高级方法——根据服务器响应动态更改延迟:
base_delay = 3.0 # 基础延迟
delay_multiplier = 1.0
response = requests.get(url, headers=headers, proxies=proxies)
# 如果收到验证码或429——增加延迟
if response.status_code == 429 or 'captcha' in response.text.lower():
delay_multiplier *= 1.5
print(f"检测到防护,增加延迟至{base_delay * delay_multiplier}秒")
# 如果一切正常——可以稍微加速
elif response.status_code == 200:
delay_multiplier = max(1.0, delay_multiplier * 0.95)
time.sleep(random.uniform(base_delay * delay_multiplier, base_delay * delay_multiplier * 1.5))
这种方法允许在检测到防护时自动减速,在网站不显示攻击性时加速。
防指纹识别:Canvas、WebGL、字体
如果网站使用JavaScript进行检查,简单的HTTP头是不够的。现代反机器人系统基于数十个参数创建浏览器"指纹":Canvas、WebGL、已安装字体、时区、屏幕分辨率等。
指纹识别的主要参数
Canvas指纹识别
网站在Canvas中绘制不可见图像并读取它。不同的浏览器和操作系统以不同方式渲染图像,创建唯一指纹。无头浏览器通常生成相同的Canvas,这会暴露自动化。
WebGL指纹识别
类似于Canvas,但使用3D渲染。读取有关显卡、驱动程序、支持的扩展的信息。无头浏览器通常显示软件渲染(SwiftShader)而不是真实GPU。
已安装字体
JavaScript可以确定已安装字体列表。无头浏览器通常具有最小的系统字体集,这与安装了Microsoft Office、Adobe和其他程序的真实用户不同。
Navigator属性
navigator.webdriver、navigator.plugins、navigator.languages属性会暴露自动化。例如,在Selenium中navigator.webdriver === true,这会被反机器人系统立即检测到。
绕过指纹识别的工具
要绕过指纹识别,请使用专用工具:
- Undetected ChromeDriver(Python)——隐藏自动化迹象的修改版Selenium
- Puppeteer Stealth(Node.js)——替换指纹参数的Puppeteer插件
- 带stealth的Playwright——类似于Puppeteer,但对不同浏览器有更好的支持
- 反检测浏览器(Dolphin Anty、AdsPower、Multilogin)——对于不想编写代码的人,这些浏览器会自动替换指纹
在Python中使用undetected-chromedriver的示例:
import undetected_chromedriver as uc
# 创建具有防检测保护的浏览器
options = uc.ChromeOptions()
options.add_argument('--disable-blink-features=AutomationControlled')
driver = uc.Chrome(options=options)
driver.get('https://example.com')
# 检查navigator.webdriver === undefined
webdriver_status = driver.execute_script("return navigator.webdriver")
print(f"navigator.webdriver: {webdriver_status}") # 应该是None/undefined
Cookie和会话管理
许多网站使用cookie来跟踪用户行为。正确管理cookie有助于避免封禁并看起来像真实用户。
保存和重用cookie
不要在每个请求上创建新会话,而是保存cookie并重复使用它们。这模拟了返回网站的真实用户的行为:
import requests
import pickle
session = requests.Session()
# 首次访问——获取cookie
response = session.get('https://example.com')
# 将cookie保存到文件
with open('cookies.pkl', 'wb') as f:
pickle.dump(session.cookies, f)
# 稍后加载cookie
with open('cookies.pkl', 'rb') as f:
session.cookies.update(pickle.load(f))
# 现在请求看起来像来自返回的用户
response = session.get('https://example.com/catalog')
爬取前预热会话
不要立即从目标页面开始爬取。模拟真实用户行为:
- 打开网站主页
- 等待2-5秒
- 打开类别或部分页面
- 等待3-7秒
- 只有在此之后才开始爬取目标页面
这在cookie中创建活动历史并降低封禁概率。
处理会话cookie和令牌
一些网站在首次访问时生成唯一令牌,并在后续请求中检查它们。例如,Wildberries在x-requested-with头中使用令牌。始终从第一个响应中保存此类令牌并在后续请求中发送它们。
JavaScript渲染:何时需要
许多现代网站通过JavaScript加载内容。如果您使用简单的HTTP客户端(Python中的requests,Node.js中的axios),您将获得空白页面或占位符。在这种情况下,需要JavaScript渲染。
何时需要JavaScript渲染
- 网站使用React、Vue、Angular——内容在初始页面加载后加载
- 数据通过AJAX/Fetch请求加载
- 网站需要执行JavaScript来生成令牌或cookie
- 存在需要执行JS代码的反机器人保护(例如Cloudflare Challenge)
JavaScript渲染工具
| 工具 | 语言 | 速度 | 绕过防护 |
|---|---|---|---|
| Selenium | Python、Java、C# | 慢 | 中等(使用undetected-chromedriver) |
| Puppeteer | Node.js | 中等 | 好(使用puppeteer-extra-plugin-stealth) |
| Playwright | Python、Node.js、Java | 快 | 优秀 |
| Splash | HTTP API | 中等 | 弱 |
对于大多数任务,推荐使用Playwright——它比Selenium更快,更好地绕过防护,并且具有更方便的API。
替代方案:拦截API请求
通常可以避免JavaScript渲染,如果找到网站用于加载数据的API请求。打开DevTools(F12)→ Network选项卡→ XHR/Fetch过滤器,查看网站发送的请求。然后通过HTTP客户端直接重复这些请求。
示例:Wildberries通过API https://catalog.wb.ru/catalog/...加载商品数据。不是渲染整个页面,而是可以直接请求此API,这快10-20倍。
绕过验证码:自动化解决方案
即使使用正确的代理和头部,您也可能遇到验证码。有几种解决方法:
验证码类型和解决方法
reCAPTCHA v2(复选框"我不是机器人")
通过识别服务解决:2Captcha、Anti-Captcha、CapMonster。成本:每1000次解决$1-3。解决时间:10-30秒。
reCAPTCHA v3(不可见,基于评分)
更复杂。分析用户行为并给出0到1的评分。绕过:使用具有正确指纹的无头浏览器+模拟用户操作(鼠标移动、点击)。
hCaptcha
reCAPTCHA的类似物,在许多网站上使用。通过相同的识别服务解决。成本:每1000次解决$0.5-2。
Cloudflare Challenge
检查浏览器的JavaScript挑战。绕过:使用专用库(Python的cloudscraper,Node.js的cloudflare-scraper)或服务(FlareSolverr)。
集成验证码识别服务
在Python中集成2Captcha的示例:
from twocaptcha import TwoCaptcha
solver = TwoCaptcha('YOUR_API_KEY')
try:
# 解决reCAPTCHA v2
result = solver.recaptcha(
sitekey='6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-',
url='https://example.com'
)
# 获取解决令牌
captcha_token = result['code']
# 使用令牌提交表单
response = requests.post('https://example.com/submit', data={
'g-recaptcha-response': captcha_token
})
except Exception as e:
print(f"验证码解决错误: {e}")
重要: 解决验证码会使爬取速度减慢10-30倍并增加成本。仅在其他方法不起作用时使用。首先尝试改进代理、指纹和延迟。
速率限制:如何不超过网站限制
许多网站对请求数量有明确或隐含的限制。超过这些限制会导致临时或永久封禁IP。
确定网站限制
注意服务器响应中的HTTP头:
X-RateLimit-Limit——期间内的最大请求数X-RateLimit-Remaining——剩余请求数X-RateLimit-Reset——限制何时重置(Unix时间戳)Retry-After——多少秒后可以重试请求
如果您收到状态码429(请求过多),这意味着超过了限制。读取Retry-After头并在下一个请求之前等待指定时间。
实现速率限制器
创建请求速度控制机制:
import time
from collections import deque
class RateLimiter:
def __init__(self, max_requests, time_window):
self.max_requests = max_requests # 时间窗口内的最大请求数
self.time_window = time_window # 时间窗口(秒)
self.requests = deque()
def wait_if_needed(self):
now = time.time()
# 删除时间窗口外的旧请求
while self.requests and self.requests[0] < now - self.time_window:
self.requests.popleft()
# 如果达到限制——等待
if len(self.requests) >= self.max_requests:
sleep_time = self.time_window - (now - self.requests[0])
if sleep_time > 0:
print(f"达到速率限制,等待{sleep_time:.2f}秒")
time.sleep(sleep_time)
self.requests.popleft()
# 记录新请求
self.requests.append(time.time())
# 使用:每分钟最多60个请求
limiter = RateLimiter(max_requests=60, time_window=60)
for url in urls:
limiter.wait_if_needed()
response = requests.get(url)
监控指标:跟踪封禁率
要了解您的防护方法是否有效,需要监控关键指标。以下是应该跟踪的内容:
关键指标
封禁率(Ban Rate)
被封禁请求占总请求的百分比。计算方式:(被封禁请求数/总请求数)× 100%。目标:低于10%,理想情况下低于5%。
验证码率(Captcha Rate)
遇到验证码的请求百分比。高验证码率(>20%)表示需要改进指纹或代理质量。
成功率(Success Rate)
返回有效数据的请求百分比。应该高于90%。如果更低——检查页面结构是否改变或防护是否加强。
平均响应时间
帮助识别慢速代理或服务器过载。如果响应时间突然增加——可能是网站开始限制您的请求。
实现监控
简单的监控实现示例:
import time
from collections import defaultdict
class ScraperMetrics:
def __init__(self):
self.total_requests = 0
self.successful_requests = 0
self.blocked_requests = 0
self.captcha_requests = 0
self.response_times = []
self.status_codes = defaultdict(int)
def record_request(self, status_code, response_time, has_captcha=False, is_blocked=False):
self.total_requests += 1
self.status_codes[status_code] += 1
self.response_times.append(response_time)
if is_blocked:
self.blocked_requests += 1
elif has_captcha:
self.captcha_requests += 1
elif status_code == 200:
self.successful_requests += 1
def get_stats(self):
if self.total_requests == 0:
return "没有请求"
ban_rate = (self.blocked_requests / self.total_requests) * 100
captcha_rate = (self.captcha_requests / self.total_requests) * 100
success_rate = (self.successful_requests / self.total_requests) * 100
avg_response_time = sum(self.response_times) / len(self.response_times)
return f"""
总请求数: {self.total_requests}
成功率: {success_rate:.2f}%
封禁率: {ban_rate:.2f}%
验证码率: {captcha_rate:.2f}%
平均响应时间: {avg_response_time:.2f}秒
状态码分布: {dict(self.status_codes)}
"""
# 使用
metrics = ScraperMetrics()
for url in urls:
start_time = time.time()
response = requests.get(url)
response_time = time.time() - start_time
is_blocked = response.status_code in [403, 429]
has_captcha = 'captcha' in response.text.lower()
metrics.record_request(response.status_code, response_time, has_captcha, is_blocked)
print(metrics.get_stats())
爬取工具比较
选择正确的工具可以显著影响封禁率。以下是流行解决方案的比较:
| 工具 | 复杂度 | 封禁率 | 速度 | 最佳用途 |
|---|---|---|---|---|
| Requests + BeautifulSoup | 低 | 高(40-70%) | 非常快 | 简单静态网站,API |
| Scrapy | 中等 | 中等(20-40%) | 快 | 大规模爬取,需要结构化 |
| Selenium + undetected-chromedriver | 中等 | 低(10-20%) | 慢 | 需要JS的网站,复杂交互 |
| Playwright | 中等 | 非常低(5-15%) | 中等 | 现代SPA,强防护 |
| 商业API(ScraperAPI、Bright Data) | 非常低 | 非常低(2-10%) | 快 | 预算充足,需要稳定性 |
推荐组合
对于大多数任务,最佳方法是组合使用:
- 第一阶段: 使用浏览器DevTools分析网站,查找API端点
- 第二阶段: 如果找到API——使用requests + 住宅代理直接请求
- 第三阶段: 如果需要JS——使用Playwright + 代理轮换
- 第四阶段: 如果遇到验证码——集成2Captcha或类似服务
结论
降低网页爬取中的封禁率需要综合方法。没有单一的"银弹"——您需要结合多种技术:
关键要点:
- 使用高质量的住宅或移动代理,而不是数据中心代理
- 正确配置HTTP头,包括新的Sec-Fetch-*和sec-ch-ua-*
- 实现随机延迟(3-10秒),而不是固定间隔
- 使用Playwright或undetected-chromedriver进行JS渲染
- 在爬取前预热会话,模拟真实用户行为
- 监控指标(封禁率、验证码率、成功率)并调整策略
- 实现速率限制器以不超过网站限制
- 仅在必要时使用验证码解决服务
从简单方法开始(良好的代理+正确的头部+延迟),并在需要时逐步添加复杂性。在大多数情况下,这足以将封禁率降低到5-10%并稳定地采集数据。
记住,网站不断改进其防护,因此定期更新您的方法并监控指标变化非常重要。今天有效的方法明天可能需要调整。
重要提示: 始终遵守网站的robots.txt和使用条款。网页爬取应该合乎道德并遵守法律。如果网站明确禁止自动化数据收集,请寻找替代方案或联系网站所有者获取官方API访问权限。