3、HTTP状态码:从1xx到5xx,每一类都藏着什么秘密?
状态码这东西,说白了就是服务器给你回的一句「暗号」。做数据采集这几年,我见过太多新手一看到4xx、5xx就慌了神。其实没必要,状态码就是服务器在告诉你:你刚才那请求,我处理得怎么样。
我个人习惯把状态码分成五类来记。1xx是「还在处理」,2xx是「搞定了」,3xx是「你换个地儿」,4xx是「你搞错了」,5xx是「我搞砸了」。嗯,就这么简单。
核心原则:状态码是服务器对请求的「第一反馈」。采集程序必须根据状态码决定下一步动作——重试、跳转、还是直接报错。
1xx:信息性状态码——服务器说「我收到了,还在处理」
1xx 状态码在实际采集工作中其实很少见到。为什么?因为大多数 HTTP 客户端库(比如 Python 的 requests、Node 的 axios)都帮你处理好了,不会暴露给上层。
但有个场景我遇到过——做文件上传采集时,服务器返回了 100 Continue。意思是:你尽管发,我准备好了。这时候客户端才会继续发送请求体。
| 状态码 | 含义 | 采集场景 |
|---|---|---|
| 100 Continue | 服务器已收到请求头,客户端可以继续发送请求体 | 大文件上传前的「握手」 |
| 101 Switching Protocols | 服务器同意切换协议(如升级到 WebSocket) | 实时数据流采集 |
| 102 Processing | 服务器正在处理,但还没完成(WebDAV) | 极少见,可忽略 |
我的建议:采集程序里不用专门处理 1xx。底层库已经帮你消化了。你只需要关注 2xx 及以上的状态码。
2xx:成功状态码——「搞定了,这是你要的数据」
2xx 是采集程序最喜欢的数字。看到 2xx,意味着请求被服务器正常处理了。但同样是 2xx,不同的码含义差别很大。
200 OK——最熟悉的陌生人
200 是最常见的成功状态码。表示请求成功,响应体里就是你要的数据。我采集过的网站,90% 以上的成功请求返回的都是 200。
# Python 示例:处理 200 响应
import requests
resp = requests.get('https://api.example.com/data')
if resp.status_code == 200:
data = resp.json() # 直接解析
print(f"采集到 {len(data)} 条记录")
else:
print(f"非预期状态码: {resp.status_code}")
201 Created——「你创建的东西,我存好了」
201 通常出现在 POST 请求之后。比如你向 API 提交了一条新数据,服务器创建成功,返回 201,同时在响应头 Location 里告诉你新资源的地址。
我记得有一次对接某个电商平台的商品上传接口,对方文档写的是返回 200,结果实际返回 201。排查了半天,才发现是文档没更新。嗯,这种事很常见。
204 No Content——「处理完了,但没啥可给你的」
204 有点特殊。服务器成功处理了请求,但响应体是空的。常见于 DELETE 操作,或者某些更新接口。
采集时遇到 204,别慌。这不是报错,只是服务器告诉你:事情办完了,但没有数据返回。你的程序应该继续,而不是抛出异常。
避坑指南:我曾经写过一个采集脚本,遇到 204 就直接报错退出。结果跑了三天,数据少了一半。后来才发现,那个接口在数据为空时返回 204,而不是 200。从那以后,我所有的采集程序都会单独处理 204——不解析响应体,但标记为「成功」。
3xx:重定向状态码——「你要的东西不在这儿,去那边找」
3xx 系列在采集工作中非常常见。说白了就是服务器告诉你:资源地址变了,你重新发个请求吧。
301 Moved Permanently——永久搬家
301 表示资源已经永久迁移到了新地址。采集程序遇到 301,应该更新记录的 URL,以后都用新地址。
我遇到过最典型的案例:某个政府数据网站改版,所有数据接口从 old.gov.cn/api 迁移到了 new.gov.cn/api。返回的全是 301。如果程序不处理重定向,第二天就全挂了。
302 Found——临时转移
302 表示资源临时在别处。下次请求还是用原地址。很多网站用 302 做临时跳转,比如登录后的页面跳转。
采集时要注意:requests 库默认会跟随重定向(allow_redirects=True)。但有时候你需要手动控制——比如想看看跳转前的 URL 是什么。
# 手动控制重定向
resp = requests.get('http://example.com/old-path', allow_redirects=False)
if resp.status_code in (301, 302):
new_url = resp.headers['Location']
print(f"跳转到: {new_url}")
# 决定是否跟随
304 Not Modified——「没变,用缓存吧」
304 是个好东西。它表示资源没有更新,客户端可以直接用本地缓存。在采集场景下,合理利用 304 可以大幅减少请求量。
怎么用?发送请求时带上 If-Modified-Since 或 If-None-Match 头。如果资源没变,服务器返回 304,响应体为空。你的程序就不用重复解析了。
采集优化技巧:对于更新频率低的网站(比如新闻归档、历史数据),加上条件请求头。遇到 304 直接跳过,能省下 80% 的带宽和解析时间。
4xx:客户端错误——「你发的东西有问题」
4xx 是采集程序最常遇到的「非预期」状态码。看到 4xx,第一反应应该是:检查我的请求哪里错了。
| 状态码 | 含义 | 常见原因 | 采集对策 |
|---|---|---|---|
| 400 Bad Request | 请求格式错误 | 参数缺失、JSON 格式不对 | 检查请求体/参数 |
| 401 Unauthorized | 未认证 | 缺少 Token 或 Token 过期 | 刷新认证信息 |
| 403 Forbidden | 禁止访问 | IP 被封、权限不足 | 更换代理/检查权限 |
| 404 Not Found | 资源不存在 | URL 拼错、资源已删除 | 检查 URL/跳过 |
400 Bad Request——「你发的啥玩意儿?」
400 通常意味着请求语法有问题。比如 POST 请求的 JSON 格式不对,或者缺少必填参数。
我调试时遇到 400,第一件事就是打印出完整的请求头和请求体,跟 API 文档逐行比对。90% 的情况是字段名写错了。
401 vs 403——「没登录」和「没权限」
这两个容易混淆。401 是「你是谁?」,403 是「你是谁也不行」。
- 401:没带 Token,或者 Token 过期了。重新登录获取新 Token 就行。
- 403:服务器认识你,但你不配。比如普通用户想访问管理员接口,或者 IP 被拉黑了。
采集时遇到 403,我通常会先检查 User-Agent 和 Cookie。很多网站的反爬策略就是通过 403 来拒绝非浏览器请求。
404 Not Found——「找不着」
404 是最常见的「错误」状态码。但严格来说,它不算错误——服务器正常处理了请求,只是没找到资源。
采集时遇到 404,我的处理逻辑是:记录日志,跳过这条 URL,继续采集下一条。不要因为一个 404 就中断整个任务。
我曾经踩过的坑:某个数据源有 10 万条记录,其中 3% 的链接已经失效。第一次跑脚本时,遇到 404 就抛异常退出,结果只采集了 2000 条。后来改成「记录 + 跳过」,一天就跑完了全部有效数据。
5xx:服务器错误——「服务器自己炸了」
5xx 表示服务器端出了问题。这不是你请求的问题,是服务器自己的锅。采集程序遇到 5xx,正确的做法是等待后重试,而不是放弃。
| 状态码 | 含义 | 采集对策 |
|---|---|---|
| 500 Internal Server Error | 服务器内部错误 | 等待 1-5 秒后重试,最多 3 次 |
| 502 Bad Gateway | 网关/代理出错 | 通常短暂,等待后重试 |
| 503 Service Unavailable | 服务暂时不可用 | 检查响应头 Retry-After,按指示等待 |
500 Internal Server Error——「服务器代码崩了」
500 是最通用的服务器错误。可能是数据库挂了,也可能是代码有 bug。采集时遇到 500,先别急着骂服务器——等几秒再试一次。
502 Bad Gateway——「上游挂了」
502 通常出现在有反向代理的场景。比如 Nginx 后面的应用服务器挂了,Nginx 就会返回 502。
我记得有一次采集某个大型电商平台,每到晚上 8 点就频繁出现 502。后来发现是对方服务器在高峰期扛不住了。我的对策是:遇到 502 就暂停 10 秒再重试,效果很好。
503 Service Unavailable——「别打了,我扛不住了」
503 是服务器主动告诉你:我现在太忙/正在维护,你过会儿再来。有些服务器会在响应头里加 Retry-After 字段,告诉你等多少秒。
# 处理 503 并遵循 Retry-After
import time
import requests
resp = requests.get('https://api.example.com/data')
if resp.status_code == 503:
retry_after = int(resp.headers.get('Retry-After', 5))
print(f"服务器忙,等待 {retry_after} 秒...")
time.sleep(retry_after)
# 重新请求
我的经验:5xx 错误大多数是暂时的。写采集程序时,一定要加重试机制。我一般用「指数退避」策略:第一次等 1 秒,第二次等 2 秒,第三次等 4 秒,最多重试 5 次。超过 5 次还报 5xx,那可能是对方服务器真的挂了,记录下来人工处理。
小结:状态码是服务器的「语言」
做数据采集,本质上就是在跟服务器对话。状态码就是服务器回应你的每一句话。听懂这些话,你的采集程序才能做出正确的反应。
记住这个口诀:2xx 直接拿数据,3xx 跟着走,4xx 查自己,5xx 等重试。