一、为什么「Token 爬虫」是块硬骨头普通的游客态爬虫(直接抓公开页面)已经很少见了。如今稍微有点价值的站点,几乎都走前后端分离 + Token 鉴权:请求不带Authorization: Bearer <token>直接 401;Token 有有效期(30min / 2h / 24h 不等),过期就采集中断;接口往往带签名校验或时间戳防重放,简单改个参数就被识破;同一 Token 高频请求会被标记为「机器行为」,连带封 IP。更麻烦的是,当你想用分布式把采集速度拉满时,这些麻烦会成倍放大:多个 Worker 各自刷新 Token 会互相挤掉、分散的 IP 各自为战、限流策略一旦不统一就被批量封。本文给你一套工程化、可落地的打法:Token 统一管理 + 分布式任务队列 + 反爬对抗三板斧 + 高质量代理 IP 池。代码基于httpx(async) +Redis+pydantic,复制即可跑。二、Token 机制与刷新策略:别让 Worker 各自为政分布式爬虫最致命的坑,就是每个 Worker 自己管 Token。结果就是 A 刷新了,B 还拿着旧 Token 请求 → 401 重试 → 又触发刷新 → 服务端以为被盗号,直接封号。正确姿势:集中式 Token 管理,所有 Worker 从同一个「Token 服务」取用。为什么这样做:asyncio.Lock+ 双重检查,保证「同一时刻只有一个协程去刷新」,其余协程复用结果。分布式多进程场景下,则把 Token 缓存到 Redis(见第四节),用SET NX抢刷新权。三、反爬三板斧:UA、频率、IP服务端识别爬虫,无非三招。我们挨个拆解应对:
反爬手段 | 识别逻辑 | 优雅应对 |
UA 指纹 | 固定/缺失 User-Agent、缺失常见请求头 | 维护 UA 池 + 补全Accept/Referer等头 |
请求频率 | 单位时间请求数超阈值、无随机间隔 | 令牌桶限速 + 随机抖动(jitter) |
IP 信誉 | 同一 IP 高频访问、数据中心 IP 段 | 高质量代理 IP 池(关键,见第五节) |
四、分布式采集架构:Redis 当任务总线核心思路:任务入队(Redis List/Stream)→ 多个 Worker 抢任务 → 结果落库。Worker 之间只通过 Redis 通信,彼此无状态,想横向扩到 100 台机器只需加进程。依赖:pip install httpx redis pydantic五、代理 IP 池实战:把亿牛云接进分布式前面三板斧里,IP 是最关键也最难自建的一环。把代理服务做成统一 getter 函数,Worker 无感知背后是哪家的池子——哪天换服务商只改一处。接入亿牛云时的三个工程要点:鉴权方式统一:亿牛云支持账密 / 子授权 Token 两种方式。分布式场景下推荐子授权 Token——每个 Worker 用不同子账号,万一某个出口 IP 被临时风控,不会连累整池。失败熔断:代理偶尔抽风是正常的。getter 里加重试 + 超时,连续 N 次失败则短暂降级(放慢采集),别让异常把 Worker 打挂。IP 复用策略:对「同一站点」适当复用出口 IP(避免每个请求都换 IP 反而触发「IP 跳跃」风控),对「不同站点」分散出口——这套策略在 getter 里用 LRU 缓存即可实现。六、限速与抖动:像人一样请求分布式不是「无脑并发」。哪怕有代理池,单 IP 的请求节奏依然要像人:令牌桶限速:全局 QPS 上限,例如每 IP 每秒 ≤ 2 次;随机抖动:sleep(uniform(0.5, 2.0)),消除「机器般精确」的间隔;指数退避:遇到 429 / 503,先等 1s,再 2s、4s…… 别硬刚。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import asyncio
import random
import time
class TokenBucket:
"""简单令牌桶:保证全局速率可控。"""
def __init__(self, rate: float, capacity: float) -> None:
self._rate = rate
self._capacity = capacity
self._tokens = capacity
self._last = time.monotonic()
async def acquire(self) -> None:
while True:
now = time.monotonic()
self._tokens = min(self._capacity, self._tokens + (now - self._last) * self._rate)
self._last = now
if self._tokens >= 1:
self._tokens -= 1
await asyncio.sleep(random.uniform(0, 0.3))
return
await asyncio.sleep(0.1)
七、工程化最佳实践(上线前 checklist)配置外置:Redis 地址、代理鉴权、账号密码全走环境变量 /pydantic-settings,绝不硬编码进仓库。可观测:每次请求记录url / proxy / status / cost_ms到日志,封禁率异常立刻告警。幂等消费:任务队列用 Redis Stream + 消费者组,Worker 崩溃重启不丢任务、不重复采。优雅退出:SIGTERM时停止拉新任务、处理完在途请求再退出,别直接 kill 导致 Token 状态混乱。代理质量监控:定期统计各出口 IP 的成功率,低质量 IP 自动剔除——这也是选亿牛云这类带质量保障 SLA 服务商的价值所在。八、总结Token 爬虫的「优雅」,本质是把混乱收敛成几个清晰的职责边界:Token 集中管理,杜绝刷新内卷;任务用 Redis 解耦,Worker 无状态横向扩展;反爬三板斧(UA / 频率 / IP)层层设防,其中 IP 这一层交给专业代理服务限速 + 抖动 + 退避,让请求节奏像人。按这套骨架搭起来,你就能从「单机裸爬三天两头挂」,进化到「百 Worker 稳定运行、封禁率可控」的生产级采集系统。代码都已给出,缺的只是把example.com换成你的目标站、把代理 getter 换成你的服务商的提取 API。