Skip to content

7.1 常见反爬手段:IP 封禁、频率限制、User-Agent 检测

在你兴致勃勃地写完一个爬虫,准备大展身手时,网站可能会给你一个“惊喜”——突然返回 403 错误、验证码页面,甚至直接把你 IP 拉黑。这就是网站的反爬机制在起作用。常见的反爬手段主要有以下几种:

  • IP 封禁:短时间内来自同一 IP 的请求过多,会被认为是机器人,直接封禁。
  • 频率限制(Rate Limiting):即使没被封 IP,也可能被限速,比如每秒只能请求一次。
  • User-Agent 检测:很多网站会检查请求头中的 User-Agent,如果发现是 Python-requests 这种默认标识,就会拒绝服务。
  • JavaScript 挑战:有些网站会用 JS 动态生成关键参数或 token,纯静态请求拿不到有效数据。
  • Cookie/Session 验证:要求维持会话状态,否则无法访问。

这些机制的目的不是阻止所有爬虫,而是增加自动化抓取的成本,保护服务器资源和数据安全。

功能名称实例调用方法具体功能与注意事项
检测 User-Agentheaders={'User-Agent': 'Mozilla/5.0...'}必须模拟真实浏览器,否则可能被拦截
控制请求频率time.sleep(random.uniform(1, 3))随机延迟避免规律性,降低被识别风险
更换 IP 地址使用代理池(见 7.3 节)单一 IP 容易被封,需轮换

下面是一个简单的示例,展示如何设置合法的 User-Agent 并添加随机延迟:

python
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)

注意事项

  • 不要使用默认的 requests User-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秒…用于处理临时性错误,避免雪崩

来看一个带随机延迟的批量请求示例:

python
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)无效代理会拖慢速度,需预筛选

下面是一个使用代理池的示例(这里用免费代理举例,实际项目建议用付费稳定代理):

python
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-LanguageAccept-EncodingRefererSec-Fetch-* 系列字段等。如果你只改 User-Agent,其他字段还是默认值,依然容易露馅。

所谓“请求头随机化”,就是让每次请求的头部信息都略有不同,模拟不同用户、不同浏览器的行为。而“指纹混淆”则是更高级的概念,指让爬虫的网络特征尽可能接近真实浏览器。

功能名称实例调用方法具体功能与注意事项
随机 User-Agent从列表随机选必备项
随机 Accept-Language'zh-CN,zh;q=0.9,en;q=0.8'模拟不同地区用户
添加 Refererheaders['Referer'] = 'https://example.com/'表示从哪个页面跳转而来
模拟完整浏览器头包含 Sec-Fetch-Mode、DNT 等更难被识别

下面是一个构建随机请求头的实用函数:

python
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。
  • 过度复杂的头信息可能适得其反,保持简洁合理即可。

这一节讲解了如何通过随机化请求头来增强爬虫的“伪装能力”,让网站更难将你识别为机器人。