7.1 常见反爬手段:IP 封禁、频率限制、User-Agent 检测
在你兴致勃勃地写完一个爬虫,准备大展身手时,网站可能会给你一个“惊喜”——突然返回 403 错误、验证码页面,甚至直接把你 IP 拉黑。这就是网站的反爬机制在起作用。常见的反爬手段主要有以下几种:
- IP 封禁:短时间内来自同一 IP 的请求过多,会被认为是机器人,直接封禁。
- 频率限制(Rate Limiting):即使没被封 IP,也可能被限速,比如每秒只能请求一次。
- User-Agent 检测:很多网站会检查请求头中的
User-Agent,如果发现是 Python-requests 这种默认标识,就会拒绝服务。 - JavaScript 挑战:有些网站会用 JS 动态生成关键参数或 token,纯静态请求拿不到有效数据。
- Cookie/Session 验证:要求维持会话状态,否则无法访问。
这些机制的目的不是阻止所有爬虫,而是增加自动化抓取的成本,保护服务器资源和数据安全。
| 功能名称 | 实例调用方法 | 具体功能与注意事项 |
|---|---|---|
| 检测 User-Agent | headers={'User-Agent': 'Mozilla/5.0...'} | 必须模拟真实浏览器,否则可能被拦截 |
| 控制请求频率 | time.sleep(random.uniform(1, 3)) | 随机延迟避免规律性,降低被识别风险 |
| 更换 IP 地址 | 使用代理池(见 7.3 节) | 单一 IP 容易被封,需轮换 |
下面是一个简单的示例,展示如何设置合法的 User-Agent 并添加随机延迟:
import requests
import time
import random
# 定义一个常见的浏览器 User-Agent 列表
user_agents = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36'
]
# 随机选择一个 User-Agent
headers = {
'User-Agent': random.choice(user_agents)
}
try:
# 发送 GET 请求,携带模拟的浏览器头
response = requests.get('https://httpbin.org/user-agent', headers=headers, timeout=10)
# 检查响应状态码是否为 200(成功)
if response.status_code == 200:
print("成功获取响应,User-Agent 未被拦截")
print("返回内容:", response.json()) # httpbin 会返回你发送的 User-Agent
else:
print(f"请求失败,状态码: {response.status_code}")
except requests.exceptions.Timeout:
print("请求超时,请检查网络或目标服务器")
except requests.exceptions.RequestException as e:
print(f"请求发生错误: {e}")
# 添加随机延迟,模拟人类操作间隔
delay = random.uniform(1, 3) # 随机等待 1 到 3 秒
print(f"等待 {delay:.2f} 秒后再进行下一次请求...")
time.sleep(delay)注意事项:
- 不要使用默认的
requestsUser-Agent(如python-requests/2.25.1),这等于主动告诉网站“我是爬虫”。 - 即使设置了 User-Agent,高频请求仍可能触发风控,必须配合延迟。
- 返回的响应内容可能包含反爬提示(如“请开启 JavaScript”),需结合其他技术(如 Selenium)处理。
这一节讲了常见的反爬手段及其原理,理解这些是制定应对策略的前提。知道敌人怎么出招,你才能见招拆招。
7.2 设置请求间隔与随机延迟
光有 User-Agent 还不够,如果你像机关枪一样疯狂发请求,网站照样能把你认出来。这时候就需要“装人”——人类浏览网页是有节奏的,不会一秒点十次。所以我们得给爬虫加上请求间隔,最好是随机延迟,让它看起来更自然。
Python 的 time.sleep() 函数可以暂停程序执行,而 random.uniform(a, b) 能生成 a 到 b 之间的随机浮点数,两者结合就能实现随机等待。
| 功能名称 | 实例调用方法 | 具体功能与注意事项 |
|---|---|---|
| 固定延迟 | time.sleep(2) | 简单但容易被识别为机器行为 |
| 随机延迟 | time.sleep(random.uniform(1, 3)) | 推荐方式,模拟人类操作节奏 |
| 指数退避重试 | 首次失败等1秒,再失败等2秒、4秒… | 用于处理临时性错误,避免雪崩 |
来看一个带随机延迟的批量请求示例:
import requests
import time
import random
# 目标 URL 列表(假设我们要爬多个页面)
urls = [
'https://httpbin.org/delay/1',
'https://httpbin.org/delay/1',
'https://httpbin.org/delay/1'
]
# 模拟浏览器的 User-Agent
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
}
for i, url in enumerate(urls, 1):
try:
print(f"正在请求第 {i} 个页面: {url}")
# 发送请求
response = requests.get(url, headers=headers, timeout=10)
if response.status_code == 200:
print(f"✅ 第 {i} 页请求成功")
else:
print(f"❌ 第 {i} 页请求失败,状态码: {response.status_code}")
except requests.exceptions.Timeout:
print(f"⏰ 第 {i} 页请求超时")
except requests.exceptions.ConnectionError:
print(f"🔌 第 {i} 页连接失败")
except Exception as e:
print(f"💥 第 {i} 页发生未知错误: {e}")
# 在每次请求后添加随机延迟(1 到 3 秒之间)
if i < len(urls): # 最后一次不用等
delay = random.uniform(1, 3)
print(f"⏳ 等待 {delay:.2f} 秒...")
time.sleep(delay)
print("所有请求完成!")注意事项:
- 延迟时间要根据目标网站的敏感程度调整。新闻站可能宽松,电商或金融网站则非常严格。
- 不要在循环内部忘记加延迟,否则前功尽弃。
- 如果遇到 429(Too Many Requests)状态码,说明你已经被限流,应立即停止并延长延迟。
这一节强调了通过随机延迟模拟人类行为的重要性,这是绕过频率限制最基础也最有效的手段之一。
7.3 使用代理 IP 池轮换访问
当你发现即使加了延迟和 User-Agent,IP 还是被封了,那就该祭出“替身术”——代理 IP。代理 IP 就像马甲,让你的请求从不同地址发出,网站就难以追踪到你的真实身份。
你可以购买商业代理服务(如快代理、芝麻代理),也可以使用免费代理(但稳定性差)。关键是把多个代理 IP 组成一个“池”,每次请求随机选一个。
| 功能名称 | 实例调用方法 | 具体功能与注意事项 |
|---|---|---|
| 使用单个代理 | proxies={'http': 'http://ip:port'} | 简单测试可用,但易失效 |
| 轮换代理池 | 从列表中随机选取代理 | 提高成功率,避免单点失效 |
| 验证代理有效性 | 先用代理请求测试页(如 httpbin.org/ip) | 无效代理会拖慢速度,需预筛选 |
下面是一个使用代理池的示例(这里用免费代理举例,实际项目建议用付费稳定代理):
import requests
import random
import time
# 代理 IP 池(示例,实际应从可靠来源获取)
proxy_pool = [
{'http': 'http://103.151.246.38:10001'},
{'http': 'http://47.243.178.234:8080'},
{'http': 'http://114.132.128.106:80'}
]
# 测试目标
test_url = 'https://httpbin.org/ip'
# 模拟浏览器头
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
# 尝试最多 3 次,每次换一个代理
for attempt in range(3):
# 随机选择一个代理
proxy = random.choice(proxy_pool)
print(f"尝试第 {attempt + 1} 次,使用代理: {proxy['http']}")
try:
# 发送带代理的请求
response = requests.get(
test_url,
headers=headers,
proxies=proxy,
timeout=10
)
if response.status_code == 200:
origin_ip = response.json().get('origin')
print(f"✅ 成功!返回 IP: {origin_ip}")
break # 成功就退出
else:
print(f"❌ 状态码异常: {response.status_code}")
except requests.exceptions.ProxyError:
print("🚫 代理错误,可能已失效")
except requests.exceptions.Timeout:
print("⏰ 请求超时")
except Exception as e:
print(f"💥 其他错误: {e}")
# 失败后稍等再试
time.sleep(1)
else:
print("⚠️ 所有代理尝试均失败,请检查代理池有效性")注意事项:
- 免费代理大多不稳定,响应慢或根本不可用,建议用于学习,生产环境用付费代理。
- 有些网站会检测代理 IP(如数据中心 IP),优先使用“住宅代理”(Residential Proxy)。
- 代理请求也要加 User-Agent 和延迟,三者配合效果最佳。
这一节介绍了代理 IP 池的使用方法,它是突破 IP 封禁的核心手段,尤其适合大规模爬取任务。
7.4 请求头随机化与指纹混淆
除了 User-Agent,现代网站还会检查更多请求头字段来判断是否为机器人,比如 Accept-Language、Accept-Encoding、Referer、Sec-Fetch-* 系列字段等。如果你只改 User-Agent,其他字段还是默认值,依然容易露馅。
所谓“请求头随机化”,就是让每次请求的头部信息都略有不同,模拟不同用户、不同浏览器的行为。而“指纹混淆”则是更高级的概念,指让爬虫的网络特征尽可能接近真实浏览器。
| 功能名称 | 实例调用方法 | 具体功能与注意事项 |
|---|---|---|
| 随机 User-Agent | 从列表随机选 | 必备项 |
| 随机 Accept-Language | 如 'zh-CN,zh;q=0.9,en;q=0.8' | 模拟不同地区用户 |
| 添加 Referer | headers['Referer'] = 'https://example.com/' | 表示从哪个页面跳转而来 |
| 模拟完整浏览器头 | 包含 Sec-Fetch-Mode、DNT 等 | 更难被识别 |
下面是一个构建随机请求头的实用函数:
import random
def get_random_headers():
"""
生成一个随机的、模拟真实浏览器的请求头字典
"""
user_agents = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36'
]
accept_languages = [
'zh-CN,zh;q=0.9,en;q=0.8',
'en-US,en;q=0.9,zh;q=0.8',
'zh-TW,zh;q=0.9,en;q=0.8'
]
# 随机选择各项
headers = {
'User-Agent': random.choice(user_agents),
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language': random.choice(accept_languages),
'Accept-Encoding': 'gzip, deflate, br',
'Connection': 'keep-alive',
'Upgrade-Insecure-Requests': '1',
'Sec-Fetch-Dest': 'document',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-Site': 'none',
'DNT': '1', # Do Not Track
}
return headers
# 使用示例
import requests
url = 'https://httpbin.org/headers'
headers = get_random_headers()
try:
response = requests.get(url, headers=headers, timeout=10)
if response.status_code == 200:
print("✅ 请求成功,返回的请求头如下:")
print(response.json()['headers'])
else:
print(f"❌ 请求失败,状态码: {response.status_code}")
except Exception as e:
print(f"💥 请求出错: {e}")注意事项:
- 不是所有网站都检查这么多字段,但对于高防护站点(如电商、招聘平台),完整模拟很有必要。
Referer字段要根据实际跳转路径设置,比如从列表页进入详情页,Referer 应是列表页 URL。- 过度复杂的头信息可能适得其反,保持简洁合理即可。
这一节讲解了如何通过随机化请求头来增强爬虫的“伪装能力”,让网站更难将你识别为机器人。