← 返回总目录

Day 132 — 代理池搭建

爬虫与反爬对抗 260 行 3 个可运行脚本

Day 132 - 代理池搭建

阶段:Phase 7 - 进阶与性能优化 · 实战
主题:免费/付费代理获取、代理验证与可用性检测、代理池架构、高匿名代理池实战


一、概念解释

1.1 什么是代理(Proxy)

代理是一台位于爬虫与目标服务器之间的"中转服务器"。爬虫的请求先发给代理,代理再转发给目标网站;网站的响应也经由代理返回给爬虫。

没有代理:
  爬虫 ────────────────────────▶ 目标网站
        (真实 IP 直接暴露,容易被封)

使用代理:
  爬虫 ──▶ 代理服务器 ──▶ 目标网站
  爬虫 ◀─ 代理服务器 ◀─ 目标网站
        (目标网站只看到代理 IP)

为什么需要代理?
- 目标网站通常有 IP 频率限制:同一 IP 短时间内请求过多会被封禁或弹验证码。
- 使用代理后,请求分散到大量不同 IP 上,每个 IP 的请求频率都低于阈值。
- 封了一个代理 IP,换下一个即可,爬虫本体不受影响。

1.2 代理的分类

按协议分:

协议 适用场景 说明
HTTP 普通 HTTP 页面 只能代理 HTTP 请求
HTTPS (CONNECT) 加密网站(主流) 通过 CONNECT 隧道转发 TLS 流量
SOCKS4/5 通用 TCP/UDP 更底层,可代理任意流量,Python 用 PySocks 库

按匿名度分(关键!):

匿名级别 目标网站看到的 适用性
透明代理 你的真实 IP + 代理 IP ❌ 毫无用处,反而暴露自己
普通匿名 代理 IP,但知道你用了代理 ⚠️ 可用,但容易被针对
高匿名(Elite) 只看到代理 IP,不知道你用代理 ✅ 爬虫首选

判断方法:向检测网站(如 httpbin.org/headers)发请求,观察返回的 X-Forwarded-For / Via 头:
- 有 X-Forwarded-For: 你的真实IP → 透明代理
- 有 Via / Proxy-Connection 但无真实 IP → 普通匿名
- 什么都没有 → 高匿名

1.3 免费 vs 付费代理

维度 免费代理 付费代理
价格 0 元 几十~几百元/天
可用率 < 10%,随抓随失效 > 90%
稳定性 极差,几分钟内可能失效 有 SLA 保障
匿名度 参差不齐 可指定高匿名
适用场景 学习、练手 生产爬虫

免费代理来源:快代理、西刺(已停)、89IP、FreeProxyList 等网站的免费页面。免费代理的本质是"别人暴露在公网上的代理服务",随时可能关闭,所以免费代理必须配合验证机制才有价值。


二、原理解释

2.1 HTTP 代理的工作原理

HTTP 请求代理:
爬虫向代理发送的请求行直接就是完整目标 URL:

GET http://example.com/page HTTP/1.1     ← 注意:完整 URL
Host: example.com

代理看到完整 URL,替你向目标发起请求。

HTTPS 请求代理(CONNECT 隧道):

爬虫 ──▶ 代理:   CONNECT example.com:443 HTTP/1.1
代理 ──▶ 爬虫:   HTTP/1.1 200 Connection Established
(此后 TCP 连接变成透明隧道,爬虫在隧道内完成 TLS 握手)
爬虫 ◀═════════════ 加密数据 ═════════════▶ 目标:443

代理无法解密 HTTPS 内容,只能看到你在连哪个域名(SNI)。这就是为什么 requests 的 proxies 参数同时支持 http/https URL 走 HTTP 代理。

2.2 代理池的核心架构

代理池不是一个"池子数据结构",而是一个常驻服务,内部有四个逻辑模块:

flowchart LR A[抓取器 Fetcher<br>从免费网站抓代理] --> B[验证器 Validator<br>并发检测可用性] B -->|可用| C[存储器 Redis/内存<br>按分数排序] B -->|不可用| C C --> D[API 接口<br>随机返回高分数代理] D --> E[爬虫业务] C -.定时调度.-> A C -.定时降分.-> B
flowchart LR
    A[抓取器 Fetcher<br>从免费网站抓代理] --> B[验证器 Validator<br>并发检测可用性]
    B -->|可用| C[存储器 Redis/内存<br>按分数排序]
    B -->|不可用| C
    C --> D[API 接口<br>随机返回高分数代理]
    D --> E[爬虫业务]
    C -.定时调度.-> A
    C -.定时降分.-> B

四个模块职责:

  1. 抓取器(Fetcher):从多个免费代理网站爬取代理列表。来源越多越好,因为单来源可用率极低。
  2. 验证器(Validator):并发地对每个代理发起真实请求(访问可靠、响应快的检测站点),测量延迟、验证匿名度。验证失败的代理扣分,多次失败则移除。
  3. 存储器(Store):通常用 Redis 的 ZSet(有序集合),score 表示代理的"健康度"。
  4. API 接口(Provider):爬虫通过 GET /random 获取当前最优代理,或 GET /all 获取全量列表。

分数机制(为什么用它?):
- 新抓的代理初始分 50(半信半疑)
- 每次验证成功 +10,封顶 100 → 进入"可用"队列
- 每次验证失败 -10,降到 0 → 移除
- 这样能容忍偶发抖动(一次超时不至于立刻淘汰),又能及时清理持续失效的代理

2.3 并发验证为什么用协程而不是线程

验证 1000 个代理 = 1000 次网络 IO(每次 1~5 秒)。
- 串行:1000 × 3s ≈ 50 分钟,不可接受
- 线程池(100):可行,但线程开销大、上限低
- 协程(asyncio + aiohttp):单线程内上千并发连接,IO 等待时切换任务,验证 1000 个代理只需 10~30 秒 ✅

代理验证是典型的 IO 密集型 + 高并发短连接场景,是协程的最佳用武之地。


三、定义与使用方法(API 速查)

3.1 requests 使用代理

python
import requests

proxies = {
    "http": "http://112.34.5.6:8080",
    "https": "http://112.34.5.6:8080",   # https 流量也可走 http 代理(CONNECT 隧道)
}
# 带认证的代理
proxies_auth = {
    "http": "http://user:pass@112.34.5.6:8080",
}
resp = requests.get("https://httpbin.org/ip", proxies=proxies, timeout=10)

3.2 aiohttp 使用代理

python
import aiohttp, asyncio

async def main():
    async with aiohttp.ClientSession() as session:
        async with session.get(
            "https://httpbin.org/ip",
            proxy="http://112.34.5.6:8080",
        ) as resp:
            print(await resp.json())

asyncio.run(main())

3.3 Scrapy 使用代理(中间件方式)

python
# middlewares.py
class ProxyMiddleware:
    def process_request(self, request, spider):
        request.meta["proxy"] = "http://112.34.5.6:8080"

3.4 常用方法参数速查

场景 方法/参数 说明
requests 代理 proxies={"http": ..., "https": ...} 字典,key 是协议
requests 超时 timeout=(3, 5) (连接超时, 读取超时),验证代理必设
aiohttp 代理 session.get(url, proxy=...) 直接传 proxy 参数
aiohttp 超时 aiohttp.ClientTimeout(total=8) 总超时
验证失败重试 tenacity / 手写 for 循环 代理失效是常态,业务代码必须重试换代理
环境变量代理 HTTP_PROXY / HTTPS_PROXY requests 默认读取,注意本地调试时的干扰

⚠️ 避坑:
1. timeout 不设或设太大 → 验证阶段会被死代理拖死
2. 免费代理大多只支持 HTTP,访问 HTTPS 站点时失败不一定是代理问题,先确认协议
3. requests 的 proxies 字典 key 是 你访问的目标协议,不是代理本身的协议


四、图解

4.1 代理池生命周期总览

        ┌─────────────────────────────────────────────┐
        │              定时调度(每 10 分钟)            │
        └──────────────────┬──────────────────────────┘
                           ▼
   ┌──────────┐     ┌────────────┐     ┌────────────────┐
   │ Fetcher  │────▶│  Validator │────▶│  Redis ZSet    │
   │ 抓免费代理 │     │ aiohttp并发 │     │  score: 0~100  │
   └──────────┘     │  逐个验证   │     └───────┬────────┘
                    └────────────┘             │
                           ▲                   ▼
                           │            ┌─────────────┐
                    失败扣分续验证        │  API Server  │
                           │            │  GET /random │
                    ┌──────┴──────┐      └──────┬──────┘
                    │ score=0 移除 │             ▼
                    └─────────────┘        爬虫拿代理干活

4.2 分数流转状态机

  抓取入库          验证成功         验证失败
 ┌───────┐  +10   ┌───────┐  -10   ┌───────┐
 │  50   │──────▶ │  100  │◀────── │  30   │
 └───────┘        └───────┘         └───────┘
    │                                   │
    │            连续失败分数归零          ▼
    └──────────────▶ 0 ◀──────────── 删除代理

五、实战代码案例

完整的高匿名代理池实现见 code/03-proxy-pool.py,包含:
- 多来源免费代理抓取
- asyncio 并发验证(含匿名度检测)
- 内存 ZSet 式分数管理
- 随机取用 API + 业务侧自动换代理重试

核心使用方式:

python
pool = ProxyPool()
pool.fetch_all()          # 抓取
await pool.validate_all() # 并发验证
proxy = pool.get_proxy()  # 取最优代理

六、思考题

  1. 为什么免费代理的"可用率"这么低?从这些代理的来源(机房误配置、开放转发)角度分析。
  2. 验证代理时应该用哪个网站做检测目标?如果用你真正要爬的目标网站做验证,有什么好处和风险?
  3. 代理池的分数机制里,"一次失败扣 10 分而不是直接删除"的设计是为了解决什么问题?
  4. 如果目标网站不仅封 IP,还通过 TLS 指纹(JA3)识别并封锁 requests/curl 的流量,单纯换代理还有用吗?有什么思路?
  5. 如何评估"代理池越大越好"这个说法?代理数量与爬虫吞吐量之间的最优关系是什么?

参考阅读:
- requests proxies 文档:https://requests.readthedocs.io/en/latest/user/advanced/#proxies
- aiohttp proxy 支持:https://docs.aiohttp.org/en/stable/client_advanced.html#proxy-support