3. HTTP 状态码:从 1xx 到 5xx 的爬虫生存指南

状态码这东西,说白了就是服务器给你的一个「暗号」。它告诉你请求是死是活,下一步该干嘛。我刚开始写爬虫那会儿,看到 200 就开心,看到 404 就头疼,看到 5xx 直接懵圈。后来踩坑多了才明白——每个状态码背后都藏着一段故事。

今天咱们就把 1xx 到 5xx 撸一遍。重点聊聊爬虫里最常见的那些码,以及遇到它们时该怎么处理。

核心观点:状态码不是用来「怕」的,是用来「用」的。你越懂它,爬虫就越稳。

HTTP 状态码在爬虫中的处理全景 爬虫请求 → 状态码 1xx 信息 2xx 成功 3xx 重定向 4xx 客户端错 5xx 服务端错 爬虫处理策略 忽略/跳过 (1xx, 部分3xx) 正常解析 (2xx) 跟踪跳转 (301, 302) 重试/降级/放弃 (4xx, 5xx) 不同状态码对应不同处理策略, 盲目重试或直接放弃都是坑

3.1 1xx:信息性状态码——基本用不上

1xx 是「信息类」状态码。说白了就是服务器跟你说:「嘿,我收到你的请求了,正在处理,别急。」

常见的就两个:

  • 100 Continue:客户端可以继续发送请求体。一般出现在 POST 大文件时,先发个头探探路。
  • 101 Switching Protocols:服务器同意切换协议,比如从 HTTP 切到 WebSocket。

爬虫里基本碰不到 1xx。为什么?因为大多数 HTTP 库(比如 requests、httpx)都帮你自动处理了。你根本看不到它。我个人写爬虫这么多年,只在调试 WebSocket 连接时见过一次 101。

小提示:如果你用底层库(比如 socket 自己拼 HTTP 请求),才需要关心 100 Continue。日常爬虫开发,直接忽略 1xx 就好。

3.2 2xx:成功状态码——爬虫的「绿灯」

2xx 意味着请求成功了。这是爬虫最喜欢的数字。

状态码 含义 爬虫场景
200 OK 请求成功,响应体包含资源 最常见的状态码,直接解析页面内容
201 Created 资源创建成功 POST 提交数据后,确认创建成功
204 No Content 请求成功,但响应体为空 DELETE 操作后常见,无需解析
206 Partial Content 返回部分内容(断点续传) 大文件下载、视频流抓取时用到

爬虫里 200 是主力。但注意——200 不代表数据是对的。我遇到过好几次,服务器返回 200,但页面里是个验证码弹窗或者「请登录」的提示。所以拿到 200 后,还得检查响应体内容是否真的有效。

避坑:别迷信 200。有些网站反爬手段就是「先给你 200,再给你个假页面」。我习惯在解析前加一层内容校验,比如检查页面标题或特定关键词是否存在。

3.3 3xx:重定向状态码——跟着走,但要长个心眼

3xx 表示资源位置变了,需要客户端重新请求。爬虫里最常见的就是 301 和 302。

  • 301 Moved Permanently:资源永久迁移。以后都走新地址。
  • 302 Found:临时跳转。下次可能还用旧地址。
  • 303 See Other:用 GET 请求去另一个 URL 拿结果。
  • 307 Temporary Redirect:临时跳转,但请求方法不变。
  • 308 Permanent Redirect:永久跳转,请求方法不变。

大多数 HTTP 库默认会跟随重定向。但这里有个坑——重定向次数限制。有些网站会搞重定向循环,让你无限跳转下去。我遇到过最离谱的一次,一个 URL 跳了 20 多次才到目标页面。

我的习惯:设置最大重定向次数(比如 5 次),超过就放弃。同时记录最终 URL,方便后续直接访问。

import requests

session = requests.Session()
# 设置最大重定向次数为 5
session.max_redirects = 5

try:
    resp = session.get('http://example.com/redirect-loop')
    print(f'最终状态码: {resp.status_code}')
    print(f'最终 URL: {resp.url}')
except requests.exceptions.TooManyRedirects:
    print('重定向次数过多,放弃请求')

3.4 4xx:客户端错误——先检查自己

4xx 表示你这边出了问题。服务器说:「你的请求有问题,我不处理。」

状态码 含义 爬虫处理策略
400 Bad Request 请求格式错误 检查参数、请求体格式
401 Unauthorized 未认证 检查 Cookie、Token 或认证头
403 Forbidden 禁止访问 被反爬了,换 IP 或 User-Agent
404 Not Found 资源不存在 URL 失效,记录并跳过
405 Method Not Allowed 请求方法不对 改用正确的 HTTP 方法
429 Too Many Requests 请求频率过高 限速、加延时、换代理

4xx 里爬虫最怕的是 403 和 429。403 说明你被识别了,429 说明你太快了。

我曾经写过一个爬虫,跑得好好的,突然全变 403。排查了半天,发现是 User-Agent 太老,被网站新加的反爬规则拦截了。换了个最新的浏览器 UA,问题解决。

处理 429 的正确姿势:别硬冲。等几秒再重试,或者用指数退避策略。我一般第一次等 5 秒,第二次 10 秒,第三次 20 秒,最多重试 3 次。

3.5 5xx:服务端错误——不是你的锅,但你要会处理

5xx 表示服务器出问题了。这时候别慌,问题不在你这边。

  • 500 Internal Server Error:服务器内部错误。原因不明,可能是代码 bug 或配置问题。
  • 502 Bad Gateway:网关或代理服务器收到无效响应。
  • 503 Service Unavailable:服务器暂时过载或维护中。
  • 504 Gateway Timeout:网关超时,上游服务器没响应。

5xx 的处理策略和 4xx 完全不同。4xx 你得改自己的代码,5xx 你只需要——重试。

但重试也有讲究。我见过有人遇到 503 就疯狂重试,每秒发 10 次,结果把服务器搞得更崩溃了。正确的做法是:

  1. 先等几秒再重试
  2. 每次重试间隔递增(指数退避)
  3. 设置最大重试次数(3-5 次足够)
  4. 如果持续 5xx,记录日志并跳过
import time
import requests

def fetch_with_retry(url, max_retries=3):
    for attempt in range(max_retries):
        try:
            resp = requests.get(url, timeout=10)
            if resp.status_code < 500:
                return resp
            # 5xx 错误,等待后重试
            wait = 2 ** attempt  # 指数退避:1, 2, 4 秒
            print(f'收到 {resp.status_code},等待 {wait} 秒后重试')
            time.sleep(wait)
        except requests.exceptions.RequestException as e:
            print(f'请求异常: {e}')
            time.sleep(2)
    return None

注意:别把 5xx 和 4xx 混在一起重试。403 重试 100 次也没用,只会浪费带宽。我习惯先判断状态码范围,再决定是重试还是放弃。

3.6 爬虫状态码处理总纲

说了这么多,总结一下我的处理原则:

状态码范围 处理策略 示例
1xx 忽略,库自动处理 100 Continue
2xx 正常解析,但校验内容有效性 200 OK
3xx 跟随跳转,限制最大次数 301, 302
4xx 检查请求,修改后重试或放弃 403, 429
5xx 指数退避重试,超限后跳过 500, 503

嗯,状态码这东西,说白了就是你和服务器之间的「暗号」。读懂了,爬虫就稳了。读不懂,那就是无头苍蝇乱撞。

我个人建议,写爬虫的时候一定要把状态码处理逻辑写清楚。别偷懒。我早期吃过不少亏,就是因为「反正大部分是 200,其他码不管了」。结果一上线就崩,排查起来特别痛苦。

最后一句:状态码处理不是「锦上添花」,而是「雪中送炭」。一个健壮的爬虫,一定是从容面对各种状态码的爬虫。