3. HTTP 状态码:从 1xx 到 5xx 的爬虫生存指南
状态码这东西,说白了就是服务器给你的一个「暗号」。它告诉你请求是死是活,下一步该干嘛。我刚开始写爬虫那会儿,看到 200 就开心,看到 404 就头疼,看到 5xx 直接懵圈。后来踩坑多了才明白——每个状态码背后都藏着一段故事。
今天咱们就把 1xx 到 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 次,结果把服务器搞得更崩溃了。正确的做法是:
- 先等几秒再重试
- 每次重试间隔递增(指数退避)
- 设置最大重试次数(3-5 次足够)
- 如果持续 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,其他码不管了」。结果一上线就崩,排查起来特别痛苦。
最后一句:状态码处理不是「锦上添花」,而是「雪中送炭」。一个健壮的爬虫,一定是从容面对各种状态码的爬虫。