做数据抓取久了,大家经常会碰到业务跑到一半突然卡壳的情况。其实,作为爬虫程序员,我们日常最怕两件事:
代理IP突然被封
和
代理管理乱成一锅粥
。前者会导致采集任务直接中断,而后者则会让项目的维护成本直线飙升。
追根溯源,根本原因只有一个:
缺乏高可用的代理调度框架
。如果没有统一的代理验证、存储和分发机制,代理失效时就无法快速切换。
今天不绕弯子,结合我平时写爬虫和折腾代理IP产品的经验,直接给大家拆解3个经过实战验证的代理调度框架。为了让大家能直接“抄作业”,文章中的上游供应和配置示例均以代理IP服务为例,并附带了核心的实战代码和避坑指南。
框架一:ProxyPool + API 代理(适合中大型项目)
如果你的项目目前已经有了自建代理池,但经常遇到可用IP突然耗尽或质量急剧下降的问题,这套架构非常对口。核心架构分工:
- ProxyPool 框架负责代理的验证、存储和分发。
- API代理作为上游IP来源,持续补充高质量IP到池中。
💻 核心配置代码示例
在 ProxyPool 项目克隆后,我们需要在 config/settings.py 中添加API代理作为代理来源。核心逻辑是配置好提取链接和合理的请求间隔。# config/settings.py 代理获取器配置示例
PROXY_GETTERS = [
{
'name': 'yiniu_api',
'type': 'api',
# 填入亿牛云控制台生成的 API 提取链接
# num: 每次提取数量,建议20-50
# type: 1表示高匿名代理
# pack: 套餐码,从控制台获取
'api_url': 'https://api.16yun.cn/proxy/get?num=20&type=1&pack=xxx&port=1&end=1&order=create_date&sort=asc',
# 【关键避坑点】提取间隔(秒)。
# API代理对提取频率有限制:包量套餐建议 interval >= 60,不限量套餐可设为 interval >= 30
'interval': 60,
# 权重,影响ProxyPool分发该渠道IP的优先级
'weight': 10,
},
]
# 代理验证配置(确保进入代理池的IP都是活的)
TESTER_CYCLE = 30 # 验证周期(秒)
TEST_TIMEOUT = 10 # 单个代理验证超时(秒)
TEST_URL = 'http://httpbin.org/ip' # 用于测试代理的目标网站,建议换成你实际要爬的业务域名
实战避坑要点: 除了上面代码里提到的API提取频率限制,还要特别注意IP白名单配置。API代理默认只允许在白名单IP环境下使用,如果你发现代码跑起来获取不到数据,第一时间去控制台的「IP白名单」页面,把你的服务器出口IP填进去。
框架二:轻量级轮询方案(适合中小规模敏捷开发)
并不是所有项目都需要上重型框架。对于单线程或十线程级别的并发量,追求简单可控才是王道。我们可以直接用 requests 或 httpx 结合自建队列来撸一个调度器。核心思路: 采用多线程加上按需提取IP的模式,代理池空了就去调API接口取,取回来的IP放入队列按顺序分配,用完归还或丢弃。
💻 核心实现代码示例
这里给出一个基于 httpx 和 queue 的轻量级代理调度类:import httpx
import threading
import queue
from typing import Optional
class ProxyScheduler:
def __init__(self, api_url: str, min_threshold: int = 5):
self.api_url = api_url
self.min_threshold = min_threshold
self._lock = threading.Lock()
self._proxies: queue.Queue = queue.Queue()
# 初始化时先去16yun拉取一批代理填充队列
self._refill()
def _refill(self):
"""核心动作:从API提取代理并入队"""
try:
# 请求API 获取代理 IP
resp = httpx.get(self.api_url, timeout=10)
data = resp.json()
if data.get('code') != 200:
print(f"API返回异常,可能是频率超限或未加白名单: {data.get('msg')}")
return
# 解析提取到的代理列表
proxy_list = data.get('data', [])
for p in proxy_list:
# 拼接成 HTTP 代理格式
proxy = f"http://{p['ip']}:{p['port']}"
self._proxies.put(proxy)
print(f"成功补充代理 {len(proxy_list)} 个,当前队列可用: {self._proxies.qsize()}")
except Exception as e:
print(f"代理提取网络请求失败: {e}")
def get_proxy(self, timeout: float = 30) -> Optional[str]:
"""工作线程调用此方法获取代理"""
# 如果队列里的代理数量低于阈值,触发加锁补充逻辑
if self._proxies.qsize() < self.min_threshold:
with self._lock:
if self._proxies.qsize() < self.min_threshold:
self._refill()
try:
return self._proxies.get(timeout=timeout)
except queue.Empty:
return None
# ------------- 业务调用演示 -------------
def worker_task(scheduler: ProxyScheduler, target_url: str):
proxy = scheduler.get_proxy()
if not proxy:
return
try:
# 使用获取到的代理发起业务请求
resp = httpx.get(target_url, proxy=proxy, timeout=15)
print(f"抓取成功: {resp.status_code}")
except Exception as e:
print(f"请求失败,代理可能已失效: {e}")
finally:
# 【重点】如果是每请求换IP策略,这里可以直接丢弃;
# 如果是批次复用策略,可以将其重新放回队列 scheduler._proxies.put(proxy)
pass
实战避坑要点: 要注意API代理的有效时间通常为1-4分钟。在上面的简单队列中,代理如果因为被目标网站封禁而失效,它会一直占用队列。建议在工程化时,给入队的代理打上时间戳,取出时判断 当前时间 - 提取时间 > 180秒 则直接丢弃。
框架三:Scrapy中间件方案(适合已有Scrapy项目升级)
对于很多成熟的 Scrapy 项目来说,核心诉求是:代码尽量不改,直接插上代理就能跑。这时候通过自定义 Downloader Middleware 快速接入爬虫代理(隧道代理模式)是最佳选择。核心机制: 隧道代理支持“动态转发”(每个请求自动换IP)。对于 Scrapy,我们只需要在中间件里构造认证头,每次发出 Request 时,隧道底层会自动帮我们切换出口 IP。
💻 核心配置与中间件代码示例
首先在 Scrapy 项目的 settings.py 中配置账号信息:# settings.py 配置项
# 16YUN爬虫代理(隧道)配置
PROXY_TUNNEL = 'http://proxy.16yun.cn:8080' # 隧道服务器地址
PROXY_USER = 'your_username' # 用户名
PROXY_PASS = 'your_password' # 密码
# 激活自定义的代理中间件
DOWNLOADER_MIDDLEWARES = {
'myproject.middlewares.YiniuProxyMiddleware': 543,
}
接着编写自定义的代理中间件逻辑:
# middlewares.py
import base64
class YiniuProxyMiddleware:
"""隧道代理专属中间件"""
def __init__(self, proxy_url, username, password):
self.proxy_url = proxy_url
self.username = username
self.password = password
@classmethod
def from_crawler(cls, crawler):
# 推荐使用 from_crawler 读取 settings 配置,避免将明文密码硬编码
return cls(
proxy_url = crawler.settings.get('PROXY_TUNNEL'),
username = crawler.settings.get('PROXY_USER'),
password = crawler.settings.get('PROXY_PASS'),
)
def process_request(self, request, spider):
# 1. 构造 Basic Auth 代理认证信息
user_credentials = f'{self.username}:{self.password}'
encoded_credentials = base64.b64encode(user_credentials.encode()).decode()
# 2. 将隧道代理地址注入到请求的 meta 中
request.meta['proxy'] = self.proxy_url
# 3. 将认证信息放入请求头
request.headers['Proxy-Authorization'] = f'Basic {encoded_credentials}'
# 4. 【致命暗坑修复】强制关闭 Keep-Alive
# Scrapy 默认启用 Keep-Alive,复用 TCP 连接会导致隧道代理出口 IP 不变!
# 必须显式设置为 Close,强制每次请求新建连接,从而触发隧道动态切换 IP
request.headers['Connection'] = 'Close'
实战避坑要点: 这段代码里的 request.headers['Connection'] = 'Close' 是重中之重!如果你抓取的网站是 HTTPS 且使用了隧道代理,如果不加这行代码,你会发现即便隧道后台配置了每次请求切换IP,你的爬虫依然是用同一个IP在跑,分分钟被目标网站识别并封禁。
总结
结合项目实际情况选架构:- 重型分布式爬虫:上 ProxyPool + API 代理。
- 轻量化敏捷开发:自己手搓请求队列 + API 代理。
- Scrapy 框架老将:直接写中间件 + 隧道代理(爬虫代理),改造成本最低。