花拾录
← 返回知识库

高频抓取触发限流:抓了 250 个之后整个任务中断

编程语言导入2026/09/220 阅读0 评论

写采集脚本的人,大多经历过那个转折点:前几分钟一切顺利,突然之间目标域名的连接全部失败,整个任务中断。这篇文章讲的是限流的机制,以及为什么"能跑通"和"能长期稳定跑"是两回事。

现象

连续抓取历史数据时,抓了大约 250 个对象之后,该域名的连接直接失败,整个数据任务中断。换到密集请求的接口,也出现三种典型症状:

Connection reset by peer
空返回(返回体为空)
HTTP 502 Bad Gateway

更让人困惑的是:加了延迟重试,有时也没用。 重试几次之后依然失败,任务还是断在那里。

根因

这是站点按 IP 限流 / 风控导致的。同一 IP 在短时间内高频连续请求,会触发临时封禁——不是你的请求写错了,而是对方在保护自己。

但让问题变得更难排查的是第二个原因:很多脚本对"限流"这条错误刻意静默。 常见写法是:

try:
    resp = fetch(url)
except Exception as e:
    if "rate limit" in str(e).lower():
        pass          # 限流?静默忽略
    else:
        log.error(e)  # 只在非限流错误时才打日志

于是限流发生时没有任何日志痕迹,你看到的只是"连接突然全断了",根本不知道是从哪一条开始被限流的。

解决

从防御和观测两方面同时下手:

import time, random, requests

def fetch_with_backoff(url, max_retries=2):
    for attempt in range(max_retries + 1):
        try:
            resp = requests.get(url, timeout=10)
            if resp.status_code == 429 or resp.status_code == 502:
                wait = (2 ** attempt) + random.uniform(0, 0.5)
                log.warning(f"限流/异常 {resp.status_code}, 退避 {wait:.1f}s: {url}")
                time.sleep(wait)          # 指数退避
                continue
            return resp
        except requests.ConnectionError as e:
            log.warning(f"连接失败: {e}")  # 限流也要留痕
            time.sleep(1 + attempt)
    return None

几个要点:

  • 请求间加节流,比如每条之间 time.sleep(0.3~0.5),别让请求打得那么密;
  • 对限流做 1~2 次指数退避重试(2 秒、4 秒这样递增),别用固定间隔猛捶;
  • 每 25 条就落盘一次断点。这一点特别关键——不要每 100 条才存一次,否则任务被超时杀掉时,那 100 条里没存的部分全丢;
  • 限流至少要打 WARN 并计数,绝不能静默。
for i, item in enumerate(items):
    result = fetch_with_backoff(item["url"])
    if result is None:
        rate_limit_count += 1
    buffer.append(parse(result))
    if i % 25 == 0:              # 每 25 条落盘
        save_checkpoint(buffer, i)
        time.sleep(0.3)

延伸与预防

根治限流的办法有两个方向:错峰和本地缓存。

  • 错峰:把密集抓取放到每天的低峰期(比如凌晨)执行,避开站点的风控高发时段;
  • 预抓取 + 本地缓存:每天低峰期把需要的数据一次性抓下来存成本地文件,白天任务全部读本地缓存。这样既大幅减少请求量,又让白天的任务不再受限于外部接口。

另外,关于"换用更稳的数据源"——如果你的采集目标有多个可用来源,把请求分散到不同来源上,本身就是最有效的限流缓解。但前提是你能确认它们返回的是同一份数据。

还有一点关于"观测"的经验:限流往往有前兆,只是你平时没留意。比如响应时间开始变长、偶发一两个空返回、开始零星出现 502——这些都是"配额快满了"的信号。如果脚本把这些都当噪声忽略,你就只会看到"突然全断"的那一刻。把 429/502/连接重置单独计数并打日志,就能在撞墙之前主动降速。

一句话总结:对"会限流的外部接口",节流、退避、断点、日志,缺一不可。 四样里少任何一样,你都会在某个时刻付出重跑的代价。

评论(0)

  • 还没有评论,来抢沙发~

相关文章