2. HTTP 协议与请求:请求方法、状态码、请求头与响应体解析

做数据采集,说白了就是跟 HTTP 打交道。你写的爬虫、调用的 API、甚至浏览器打开网页,底层都在走同一套协议。我刚开始接触数据采集时,总觉得 HTTP 是网络工程师的事,直到有一次抓取一个电商网站,对方返回了 403,我愣是排查了半天才发现是请求头少了一个 Referer。嗯,从那以后我再也不敢轻视 HTTP 了。

2.1 HTTP 协议的本质:你问我答

HTTP 是一种请求-响应协议。客户端发一个请求,服务端回一个响应。就这么简单。但简单背后藏着不少细节,咱们一个一个拆开看。

核心要点:HTTP 是无状态的。每次请求都是独立的,服务端不会记住你上次干了什么。这也是为什么后来有了 Cookie、Session 这些东西来弥补。

2.2 请求方法:你想对资源做什么?

HTTP 定义了多种请求方法,每种方法代表一种操作意图。数据采集中最常用的就几个,我列个表给你看。

方法 含义 数据采集中的用途 是否携带请求体
GET 获取资源 抓取网页、获取列表数据 否
POST 提交数据 登录、搜索、提交表单 是
PUT 更新资源 修改数据(较少用) 是
DELETE 删除资源 删除操作(极少用) 通常否
HEAD 获取响应头 检查链接有效性、获取文件大小 否

我个人习惯,写爬虫时 90% 的情况只用 GET 和 POST。GET 用来拿页面,POST 用来模拟登录或提交搜索条件。你想想看,如果对方网站是 RESTful 风格的 API,可能还会用到 PUT 和 DELETE,但普通网页抓取基本碰不到。

小技巧:用 HEAD 方法可以快速检查一个 URL 是否有效,而不需要下载整个页面内容。我在做大规模 URL 验证时,经常先用 HEAD 探路,能省不少带宽和时间。

2.3 状态码:服务器在告诉你什么?

每次请求,服务器都会返回一个三位数的状态码。这玩意儿就像交通信号灯,告诉你请求是顺利通过了,还是出了什么幺蛾子。

我把它分成五类,你记牢了,排查问题会快很多。

分类 范围 含义 常见状态码
1xx 100-199 信息性响应 101(切换协议)
2xx 200-299 成功 200(OK)、201(Created)
3xx 300-399 重定向 301(永久)、302(临时)、304(未修改)
4xx 400-499 客户端错误 403(禁止)、404(未找到)、429(请求过多)
5xx 500-599 服务端错误 500(内部错误)、502(网关错误)、503(服务不可用)

数据采集时,你最常碰到的几个状态码:

  • 200:一切正常,放心解析。
  • 301/302:页面搬家了。你的爬虫需要跟着跳转,否则拿不到真实内容。
  • 304:内容没变,可以用缓存。我一般会利用这个做增量抓取。
  • 403:被拒绝了。大概率是请求头不对,或者 IP 被封了。
  • 404:页面不存在。可能是链接失效了。
  • 429:请求太频繁,被限流了。这时候需要加延时。
  • 500/502/503:服务器自己出问题了。等会儿再试。

注意:我曾经遇到过某个网站,明明页面正常,但返回的状态码是 302。原来它把所有请求都先重定向到一个中间页做统计,然后再跳回目标页。如果你的爬虫不处理重定向,就会一直拿到空数据。所以,写代码时一定要设置 allow_redirects=True(Requests 库默认就是 True)。

2.4 请求头:你的身份和意图

请求头是客户端发给服务器的元信息。服务器通过请求头来判断你是谁、想干什么、能接受什么格式的数据。

数据采集时,请求头是反爬虫的第一道防线。很多网站一看请求头不对,直接给你 403。我整理了一份常用请求头清单,你照着配基本不会出错。

请求头 作用 数据采集中的建议值
User-Agent 标识客户端类型 模拟真实浏览器,如 Chrome、Safari
Referer 来源页面 模拟从上一页跳转过来
Cookie 携带会话信息 登录后获取,保持会话
Accept 可接受的响应格式 text/html, application/json, */*
Accept-Language 语言偏好 zh-CN,zh;q=0.9
Authorization 身份认证 Bearer token 或 Basic 认证

我个人习惯,写爬虫前先用浏览器开发者工具(F12)看一遍正常请求的请求头,然后照搬过来。尤其是 User-Agent 和 Referer,这两个最容易出问题。

避坑指南:我曾经因为 User-Agent 太老被网站识别为爬虫。后来我写了个 User-Agent 池,每次请求随机换一个,效果好了很多。另外,有些网站会检查 Accept-Encoding,如果你设置了 gzip,记得在代码里处理解压,否则拿到的是乱码。

2.5 响应体:你真正想要的数据

响应体就是服务器返回给你的实际内容。数据采集的目标,就是从响应体里提取出你需要的信息。

响应体的格式通常有这几种:

  • HTML:网页结构,用解析库(如 BeautifulSoup、lxml)提取。
  • JSON:API 接口返回,直接解析成字典或列表。
  • XML:老式接口或 RSS 订阅,用 XML 解析器处理。
  • 二进制:图片、视频、文件,直接保存成文件。

怎么判断响应体是什么格式?看响应头里的 Content-Type 字段。比如:

  • text/html → HTML
  • application/json → JSON
  • image/png → 图片

下面是一个完整的 HTTP 请求-响应流程的示意图,帮你把整个流程串起来。

HTTP 请求-响应流程 HTTP 请求-响应流程 客户端(爬虫) 服务端 ① 发送请求 ② 返回响应 请求结构 请求行:GET /api/data HTTP/1.1 请求头: User-Agent: Mozilla/5.0 ... Accept: application/json Cookie: session=abc123 请求体:{"key": "value"}(POST 时) 响应结构 状态行:HTTP/1.1 200 OK 响应头: Content-Type: application/json Set-Cookie: token=xyz Content-Length: 1024 响应体:{"data": [...]} 💡 数据采集的核心:构造正确的请求 → 解析响应体 请求头决定能否拿到数据,状态码决定请求是否成功

2.6 实战:用 Python 发起一个完整的 HTTP 请求

理论说完了,咱们来点实际的。下面是一个用 Python 的 requests 库发起 GET 请求的完整示例。

import requests

# 构造请求头
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
    "Accept": "text/html,application/json,*/*",
    "Accept-Language": "zh-CN,zh;q=0.9",
    "Referer": "https://www.example.com/"
}

# 发送 GET 请求
url = "https://api.example.com/data"
response = requests.get(url, headers=headers, timeout=10)

# 检查状态码
if response.status_code == 200:
    print("请求成功!")
    # 判断响应格式
    content_type = response.headers.get("Content-Type", "")
    if "application/json" in content_type:
        data = response.json()  # 解析 JSON
        print(data)
    elif "text/html" in content_type:
        html = response.text  # 获取 HTML 文本
        print(html[:200])  # 打印前 200 个字符
    else:
        # 二进制内容,保存文件
        with open("output.bin", "wb") as f:
            f.write(response.content)
elif response.status_code == 403:
    print("被拒绝了,检查请求头或 IP 是否被封")
elif response.status_code == 429:
    print("请求太频繁,需要加延时")
else:
    print(f"请求失败,状态码:{response.status_code}")

个人经验:我写爬虫时,习惯把请求头和状态码检查封装成一个函数。这样每次请求都自动带上标准头,还能统一处理异常。另外,timeout 参数一定要设,否则遇到慢响应,你的爬虫会卡死在那里。

2.7 常见问题与排查思路

数据采集时,HTTP 层面的问题占了很大比例。我总结了几种常见场景和排查方法。

现象 可能原因 排查方法
返回 403 请求头不全或 IP 被封 检查 User-Agent、Referer;换代理 IP
返回 302 但没拿到数据 未处理重定向 设置 allow_redirects=True
返回 200 但内容是空 数据通过 JavaScript 加载 检查页面是否用了 AJAX;改用 Selenium
返回 429 请求频率过高 增加延时,使用随机间隔
返回 500 服务器内部错误 稍后重试,或联系对方