二、HTTP基础:请求方法、状态码、请求头、响应体、Cookie与Session机制

HTTP(HyperText Transfer Protocol)是Web数据交互的通用语言,也是API爬取与接口开发必须掌握的第一性原理。无论是使用requests库发起一个GET请求,还是设计一个RESTful风格的POST接口,底层都遵循同一套报文格式与状态语义。本章将系统拆解HTTP协议的核心组成:请求方法、状态码、请求头、响应体,以及用于维持用户状态的Cookie与Session机制。理解这些基础,是后续编写健壮爬虫、处理反爬策略、设计高可用接口的基石。

客户端 (爬虫/浏览器) requests/urllib 服务端 (API/Web应用) Flask/Django/Node HTTP Request 请求行 + 请求头 + 请求体 HTTP Response 状态行 + 响应头 + 响应体 状态管理 Cookie (客户端存储) Session (服务端存储) GET / POST / PUT / DELETE / PATCH / OPTIONS / HEAD

上图展示了HTTP请求-响应模型的核心链路。客户端(爬虫或浏览器)构造请求报文,通过TCP连接发送至服务端;服务端解析请求后,执行相应逻辑,并返回包含状态码和数据的响应报文。Cookie与Session则是在这个无状态协议之上,通过额外的头部字段和服务端存储机制,为Web应用提供“记忆”能力。下面我们将逐一深入这些核心模块。

2.1 请求方法:语义化操作的本质

核心原理

HTTP请求方法(HTTP Method)定义了客户端希望服务端对资源执行的操作类型。在RESTful API设计中,方法名本身就是一种“动词”语义,与URL中的“名词”资源形成完整表达。最常用的方法包括:GET(获取资源)、POST(创建资源)、PUT(整体更新资源)、PATCH(局部更新资源)、DELETE(删除资源)。此外还有HEAD(仅获取响应头)、OPTIONS(查询服务器支持的HTTP方法)等辅助方法。

从爬虫角度看,GET请求通常用于获取页面数据,参数拼接在URL查询字符串中;POST请求则常用于提交表单、登录认证或触发特定操作,参数放在请求体中。理解方法语义,有助于我们判断目标接口的行为,并选择合适的爬取策略。

判断方法:如何选择正确的请求方式

  • 幂等性判断:GET、PUT、DELETE是幂等的(多次执行结果一致),POST、PATCH不保证幂等。若爬虫需要重试,优先考虑幂等方法。
  • 参数位置:GET参数在URL中,有长度限制(通常2KB-8KB);POST参数在Body中,可传输更大数据量,且支持二进制文件上传。
  • 安全性:GET参数会暴露在浏览器历史、服务器日志中,不适合传输敏感信息;POST相对更安全,但HTTPS才是真正的加密保障。

案例拆解:登录接口的请求方法选择

假设我们要爬取一个需要登录的电商网站。初始现象是:直接访问用户中心URL返回302重定向到登录页。通过浏览器开发者工具(F12)抓包,发现登录表单提交的URL为 /api/login,方法为POST,请求体格式为 {"username":"...", "password":"..."}。分析判断:登录操作会创建服务端会话,属于非幂等操作,必须使用POST。实际操作中,我们使用requests库构造POST请求,并携带必要的请求头(如User-Agent、Referer),成功获取到包含Set-Cookie的响应。结果对比:使用GET方式尝试登录,服务器返回405 Method Not Allowed;使用POST后,返回200并携带会话Cookie。复盘经验:在爬虫开发中,遇到表单提交或状态变更操作,应优先尝试POST方法,并仔细检查请求体格式是否与服务器期望一致。

经验提醒:在爬虫中模拟请求时,不要盲目使用GET。很多API对方法有严格校验,返回405或403时,应检查方法是否匹配。同时,注意部分框架支持方法覆盖(如X-HTTP-Method-Override头),但这不是标准做法。

2.2 状态码:服务器响应的数字密码

核心原理

HTTP状态码(Status Code)是三位数字,用于指示请求的处理结果。它们分为五大类:1xx(信息性响应)、2xx(成功)、3xx(重定向)、4xx(客户端错误)、5xx(服务端错误)。爬虫开发者必须熟记常见状态码的含义,以便快速定位问题。

状态码英文名称含义爬虫常见场景
200OK请求成功正常获取数据
301Moved Permanently永久重定向网站更换域名,需更新URL
302Found临时重定向未登录跳转登录页,需处理会话
403Forbidden禁止访问IP被封、缺少认证信息、UA被识别
404Not Found资源不存在URL拼写错误、接口已下线
429Too Many Requests请求过多触发限流,需降低频率或使用代理
500Internal Server Error服务器内部错误服务器代码异常,可稍后重试
502Bad Gateway网关错误反向代理配置问题,或上游服务宕机

判断方法:状态码的优先级处理

在编写爬虫时,不应仅检查状态码是否为200。正确的处理逻辑是:首先判断状态码类别,2xx视为成功;3xx需要检查Location头决定是否跟随重定向(requests库默认跟随);4xx表示请求本身有问题,应停止重试并检查参数;5xx表示服务器问题,可以尝试延迟重试。此外,有些API即使返回200,响应体中的业务状态码也可能表示失败(如 {"code": 1, "msg": "error"}),需要双重校验。

风险提示:不要对4xx错误进行无脑重试,这可能会加重服务器负担,并导致IP被更严厉地封锁。对于403错误,应优先检查请求头(如User-Agent、Referer)和Cookie是否完整。

2.3 请求头与响应体:元数据与数据的载体

核心原理

请求头(Request Headers)是客户端发送给服务端的元数据,包含关于请求本身、客户端环境以及实体内容的信息。响应头(Response Headers)则携带服务端的元数据,如内容类型、缓存策略、Set-Cookie指令等。响应体(Response Body)是实际传输的数据,可以是HTML、JSON、图片二进制流等。

对于爬虫而言,最关键的请求头包括:User-Agent(标识客户端类型)、Referer(来源页面)、Cookie(会话凭证)、Authorization(Bearer 或Basic认证)、Content-Type(请求体格式)。响应头中,Content-Type决定了解析方式,Set-Cookie用于会话管理,Cache-Control影响缓存策略。

操作流程:构造请求头与解析响应体

  1. 抓包分析:使用浏览器F12或Charles等工具,查看目标请求的完整请求头。
  2. 最小化复制:在requests中,不必复制所有头字段,但User-Agent、Referer、Cookie通常是必需的。
  3. 设置Content-Type:如果发送POST请求且Body为JSON,需设置 headers={"Content-Type": "application/json"}。
  4. 解析响应体:根据响应头中的Content-Type选择解析方式。JSON用 response.json(),HTML用 response.text + BeautifulSoup,二进制用 response.content。
import requests

url = "https://api.example.com/user/info"
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
    "Referer": "https://example.com/dashboard",
    "Cookie": "sessionid=abc123; csrftoken=xyz789",
    "Accept": "application/json"
}
resp = requests.get(url, headers=headers, timeout=10)
print(resp.status_code)
print(resp.headers.get("Content-Type"))
data = resp.json()  # 假设返回JSON

常见误区:忽略响应头中的关键信息

很多爬虫初学者只关注响应体,却忽略了响应头。例如,Set-Cookie头是会话维持的关键;X-RateLimit-Remaining头可以告知剩余请求配额;Content-Encoding: gzip表示响应体被压缩,requests库会自动解压,但若使用其他HTTP库需手动处理。此外,某些API会在响应头中返回签名或时间戳,用于下次请求的签名计算。

2.4 Cookie与Session机制:无状态协议的状态管理

核心原理

HTTP协议本身是无状态的,即服务器无法区分两个请求是否来自同一用户。Cookie与Session机制就是为了解决这个问题而设计的。Cookie是存储在客户端(浏览器或爬虫)的小型文本数据,由服务器通过Set-Cookie头下发,之后客户端每次请求都会自动携带。Cookie中通常保存会话标识(Session ID)、用户偏好设置等。Session是存储在服务端的数据结构,通过唯一的Session ID与特定用户关联。服务器收到请求后,根据Cookie中的Session ID查找对应的Session数据,从而识别用户身份。

在爬虫中,处理Cookie与Session有三种常见方式:手动管理(从响应头提取Cookie并手动添加到后续请求)、requests.Session对象(自动处理Cookie的存储与发送)、维持登录态(先POST登录获取Cookie,再使用该Cookie访问受保护资源)。

操作流程:使用requests.Session维持会话

import requests

# 创建Session对象,自动管理Cookie
session = requests.Session()

# 步骤1:访问登录页获取初始Cookie(如CSRF )
login_page = session.get("https://example.com/login")
print("初始Cookies:", session.cookies.get_dict())

# 步骤2:提取CSRF (假设在HTML中)
csrf_ = "从页面中解析"

# 步骤3:POST登录,携带和凭证
login_data = {
    "username": "your_username",
    "password": "your_password",
    "csrfmiddlewaretoken": csrf_
}
login_resp = session.post("https://example.com/api/login", data=login_data)
print("登录状态码:", login_resp.status_code)

# 步骤4:使用同一Session访问受保护资源
profile_resp = session.get("https://example.com/api/profile")
print(profile_resp.json())

案例拆解:处理Session过期与并发会话

背景:某数据平台要求登录后才能在30分钟内下载报表。初始现象:爬虫运行10分钟后,突然开始返回401 Unauthorized。分析日志发现,服务端返回的响应头中包含 Set-Cookie: sessionid=deadbeef; Expires=Thu, 01 Jan 1970 00:00:00 GMT,表明Session已失效。关键输入数据:请求头中的Cookie值、响应状态码变化时间点。判断过程:Session有效期是30分钟,但爬虫在10分钟后就失效,说明可能是服务端主动踢下线(如单点登录限制),或者IP变动导致Session绑定失败。实际操作:检查登录时的IP地址,发现爬虫使用了代理池,IP频繁切换,而服务端将Session与IP绑定。解决方案:在Session有效期内固定使用同一代理IP,或改用认证(如JWT)替代Cookie。结果对比:固定IP后,Session稳定维持30分钟,爬取任务顺利完成。复盘经验:对于绑定IP的Session,必须确保出口IP一致;对于长期运行的爬虫,应设计自动重新登录的机制。

总结复盘:Cookie与Session是爬虫与反爬博弈的核心战场。理解其生命周期(创建、传输、校验、销毁)是编写稳定爬虫的前提。建议使用requests.Session管理Cookie,并定期检查响应头中的Set-Cookie字段,以便及时发现会话状态变化。同时,注意Cookie的域名和路径属性,确保在正确的请求中携带。
风险提示:不要将Cookie硬编码在代码中,尤其是生产环境。Cookie会过期,且可能包含敏感信息。应通过登录流程动态获取,并妥善保管,避免泄露。对于涉及用户隐私的API,务必遵守相关法律法规和网站robots协议。