HTTP协议基础:请求与响应、状态码详解、User-Agent伪装

做数据采集,说白了就是跟HTTP协议打交道。你写的爬虫每发一次请求,背后都是HTTP协议在干活。我刚开始学爬虫时,总觉得这东西太抽象,直到有一次被反爬搞得焦头烂额,才老老实实把协议啃了一遍。

嗯,这一节咱们就把HTTP协议拆开揉碎了讲。你理解透了,后面写爬虫会顺手很多。

一次完整的HTTP请求与响应

HTTP协议是客户端和服务器之间的对话。客户端发一个请求,服务器回一个响应。就这么简单。

但简单归简单,里面的门道不少。咱们先看一个典型的请求长什么样:

GET /api/products HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
Accept: application/json
Accept-Language: zh-CN,zh;q=0.9
Connection: keep-alive

请求由三部分组成:

  • 请求行:第一行,包含方法(GET)、路径(/api/products)、协议版本(HTTP/1.1)
  • 请求头:从第二行到空行之前,携带各种元信息
  • 请求体:空行之后的内容,GET请求通常没有

服务器收到请求后,会返回一个响应:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 342
Date: Mon, 15 Jan 2024 08:30:00 GMT
Set-Cookie: session_id=abc123; Path=/

{"products": [{"id": 1, "name": "商品A"}]}

响应也分三块:

  • 状态行:协议版本 + 状态码 + 状态描述
  • 响应头:服务器返回的元信息
  • 响应体:实际数据,可能是HTML、JSON、图片等
我的习惯:调试爬虫时,先用浏览器的开发者工具(F12)看一遍请求和响应。Network面板里能看到完整的请求头、响应头、状态码,比猜靠谱多了。

状态码详解:每个数字都有含义

状态码是服务器给你的反馈。我见过不少新手,看到200就高兴,看到404就慌。其实每个状态码都在告诉你一件事:接下来该怎么办。

状态码 含义 采集场景说明
200 请求成功 正常返回数据,直接解析响应体
301/302 重定向 需要跟随Location头,requests默认会自动处理
403 禁止访问 大概率被反爬了,检查User-Agent和Cookie
404 资源不存在 URL写错了,或者数据已被删除
429 请求过多 触发频率限制,必须加延时
500 服务器内部错误 服务器挂了,等会儿再试
503 服务不可用 服务器过载,建议退避重试
我曾经踩过的坑:有一次爬一个电商网站,所有请求都返回200,但响应体是空的。后来发现是服务器返回了200但内容被反爬机制拦截了。所以别只看状态码,还得检查响应体内容。

为什么会这样?因为有些网站会用200状态码返回一个错误页面,而不是直接返回403。你想想看,这样爬虫就很难判断到底有没有成功。

User-Agent伪装:爬虫的第一道护身符

User-Agent(简称UA)是请求头里最关键的字段之一。它告诉服务器:我是谁?我用的是什么浏览器?什么操作系统?

服务器通过UA来判断请求来源。如果你用Python的requests库发请求,默认的UA长这样:

python-requests/2.28.1

这等于直接告诉服务器:我是爬虫。很多网站看到这个UA就直接拒绝了。

所以,我们需要伪装UA。说白了,就是让服务器以为我们是真实的浏览器用户。

常见的UA伪装方式

  1. 固定UA:选一个常见的浏览器UA,一直用
  2. UA池:准备多个UA,每次随机切换
  3. 动态生成:根据浏览器版本、操作系统版本实时构造

我个人习惯用UA池。准备几十个常见的UA,每次请求随机选一个。这样既简单,又能有效降低被识别为爬虫的概率。

import random
import requests

# 常见的UA列表
user_agents = [
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
    'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/121.0',
    'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1'
]

headers = {
    'User-Agent': random.choice(user_agents)
}

response = requests.get('https://example.com', headers=headers)
避坑指南:UA不是万能的。有些网站还会检查其他请求头,比如Accept-Language、Referer、Sec-Fetch-*等。我建议你至少把常见的请求头都带上,模拟得越像浏览器越好。

核心流程:一次HTTP请求的完整生命周期

下面这张图展示了从爬虫发请求到拿到数据的完整流程。我把它画出来,方便你理解每一步发生了什么。

HTTP请求与响应完整流程 爬虫程序 构造请求头 发送HTTP请求 网络传输 服务器处理 返回HTTP响应 解析响应体 提取目标数据 判断状态码 错误处理/重试 关键点 UA伪装 + 状态码判断 成功

你看,整个流程其实不复杂。关键就两步:发请求、收响应。但每一步都有坑,尤其是UA伪装和状态码处理。

实战中的几个要点

说了这么多理论,咱们来点实际的。我在项目中总结了几条经验:

  • 别只用一种UA:固定UA容易被封,准备10-20个轮着用
  • 状态码200不代表成功:一定要检查响应体内容,看是不是你想要的数据
  • 403和429要区别对待:403可能是UA问题,429一定是频率问题
  • 重定向要跟:301/302别怕,requests默认会帮你处理

核心总结:HTTP协议是数据采集的基石。你不需要背下所有状态码,但一定要理解请求和响应的结构。UA伪装是爬虫的第一道防线,但别指望它解决所有问题。真正的反爬对抗,后面章节会慢慢展开。