2、HTTP协议基础:请求与响应、状态码、请求头与响应头、Cookie与Session
做数据采集,说白了就是跟HTTP协议打交道。你写的爬虫,本质上就是一个HTTP客户端。服务器不认识你是谁,它只认协议。所以,搞懂HTTP协议,是爬虫工程师的基本功。
我个人习惯把HTTP协议比作「快递服务」。你(客户端)填一张单子(请求),快递员送到仓库(服务器),仓库给你回一张回执(响应)。就这么简单。但细节里全是坑,咱们一个一个说。
2.1 请求与响应:一次完整的对话
一次HTTP交互,就两件事:你问,它答。
请求(Request):客户端发给服务器的消息。包含三部分:
- 请求行:方法 + URL + 协议版本。比如
GET /index.html HTTP/1.1 - 请求头:一堆键值对,告诉服务器你的「身份」和「偏好」
- 请求体:POST请求时携带的数据,比如表单内容
响应(Response):服务器返回给客户端的消息。也分三部分:
- 状态行:协议版本 + 状态码 + 状态描述。比如
HTTP/1.1 200 OK - 响应头:服务器告诉你它的「脾气」和「附加信息」
- 响应体:真正的内容,HTML、JSON、图片二进制流都在这里
核心要点:爬虫拿到的数据,99%都在响应体里。但请求头和响应头里的信息,往往决定了你能不能拿到数据。
我在项目中遇到过一件事:一个爬虫明明URL写对了,但就是返回空数据。查了半天,发现是请求头里少了一个 Accept-Language 字段。服务器根据这个字段判断用户语言偏好,没传就直接返回默认页面。嗯,这种坑踩过一次就记住了。
2.2 状态码:服务器的「暗号」
状态码是服务器给你的「暗号」。三位数字,告诉你请求的结果。你想想看,如果服务器每次都用中文回复「成功」「失败」「找不到」,那解析起来多麻烦?用数字多干脆。
| 状态码 | 含义 | 爬虫中常见场景 |
|---|---|---|
| 200 | 请求成功 | 正常拿到数据 |
| 301/302 | 重定向 | URL变了,需要跟进新地址 |
| 403 | 禁止访问 | 被反爬了,需要加headers或代理 |
| 404 | 资源不存在 | URL写错了,或者页面被删了 |
| 429 | 请求过多 | 触发频率限制,需要加延时 |
| 500 | 服务器内部错误 | 服务器崩了,等会儿再试 |
| 503 | 服务不可用 | 服务器过载,需要重试策略 |
避坑指南:我曾经写过一个爬虫,只判断了200状态码。结果某天数据全没了,排查发现服务器返回了302重定向,但我的代码没处理,直接跳过了。从那以后,我所有爬虫都会处理301/302,并且对非200状态码做日志记录。
为什么会这样?因为很多网站为了防爬,会故意返回302让你跳转到验证页面。你不处理,就永远拿不到真实数据。
2.3 请求头与响应头:藏在「信封」上的信息
请求头和响应头,就像快递单上的寄件人和收件人信息。看似不起眼,但缺了哪一项都可能出问题。
常用请求头
User-Agent:告诉服务器你是什么浏览器。我建议至少准备5-10个不同的UA轮换使用Referer:告诉服务器你从哪个页面跳过来的。很多图片站和视频站会校验这个字段Cookie:携带登录状态或会话信息。后面会细说Accept:告诉服务器你希望接收什么格式的数据。比如application/jsonAuthorization:携带Token或API密钥,用于接口鉴权
常用响应头
Set-Cookie:服务器让你保存的Cookie,下次请求要带上Content-Type:响应体的格式。比如text/html、application/jsonContent-Length:响应体的大小,单位字节Cache-Control:缓存策略。比如no-cache表示不要缓存Location:重定向时的新地址
小技巧:我个人习惯在爬虫里先手动复制浏览器的完整请求头,然后逐步精简。你会发现有些字段是必须的,有些是可有可无的。比如 Accept-Encoding 如果设置不当,服务器返回压缩数据,你忘了解压就会拿到乱码。
举个例子,你写爬虫请求一个API接口:
import requests
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Referer': 'https://example.com/data-page',
'Accept': 'application/json, text/plain, */*',
'Cookie': 'session_id=abc123; user_token=xyz789'
}
response = requests.get('https://api.example.com/data', headers=headers)
print(response.json())
你看,就这么几行代码,但少了任何一个header,都可能返回403或者空数据。我在项目中遇到过最离谱的一次,是服务器校验 Accept-Language 字段,没传就返回英文版页面,导致解析规则全废了。
2.4 Cookie与Session:记住你是谁
HTTP协议本身是「无状态」的。什么意思?就是服务器记不住你。你第一次请求和第二次请求,在服务器看来是两个人。那怎么解决?靠Cookie和Session。
Session:存在服务器端。你登录后,服务器给你分配一个Session ID,存着你的登录状态、购物车信息等。
Cookie:存在客户端(浏览器)。服务器通过 Set-Cookie 响应头,让你把Session ID存下来。下次请求时,你带上这个Cookie,服务器就知道「哦,是你啊」。
核心要点:爬虫要维持登录状态,本质上就是维护好Cookie。你拿到一次登录后的Cookie,只要不失效,就可以一直用。
为什么会这样?因为很多网站的反爬机制,就是通过Session来检测的。比如你短时间内用同一个Session ID请求了1000次,服务器直接封你。所以,爬虫里管理Cookie是个技术活。
爬虫中Cookie的三种处理方式
- 手动复制:浏览器登录后,从开发者工具里复制Cookie字符串,直接写死在代码里。简单粗暴,但有效期短
- 自动管理:用
requests.Session()自动处理Cookie。第一次登录后,后续请求自动携带 - 持久化存储:把Cookie存到文件或数据库里,下次启动爬虫时加载。适合长期运行的爬虫
我个人习惯用第二种,配合第一种做兜底。举个例子:
import requests
# 创建一个Session对象,自动管理Cookie
session = requests.Session()
# 第一步:登录,获取Cookie
login_data = {'username': 'admin', 'password': '123456'}
login_resp = session.post('https://example.com/login', data=login_data)
# 第二步:用同一个Session请求需要登录的页面
data_resp = session.get('https://example.com/my-data')
print(data_resp.text)
你看,用 requests.Session() 之后,你根本不用手动处理Cookie。它会自动从响应头里提取 Set-Cookie,然后在后续请求中自动带上。嗯,这里要注意:如果服务器返回了多个 Set-Cookie,Session对象也会帮你全部处理好。
避坑指南:我曾经遇到过一个网站,它的Cookie里有个 expires 字段,过期时间很短。我的爬虫跑着跑着就突然返回登录页面了。排查发现是Cookie过期了。解决方案是:在爬虫里加一个心跳请求,每隔几分钟访问一次首页,刷新Cookie的有效期。
另外,有些网站会把Session ID放在URL参数里,而不是Cookie里。这种叫「URL重写」。遇到这种情况,你需要从响应内容或重定向URL里提取Session ID,然后拼接到后续请求中。虽然少见,但碰到了别慌。
好了,HTTP协议基础就这些。说白了就是:请求怎么写,响应怎么读,状态码怎么处理,头信息怎么配,Cookie怎么管。把这些搞明白,你的爬虫就成功了一半。下一章咱们聊聊更具体的——如何用Python的requests库把这些理论落地。