第3章 HTTP协议与网络请求:数据采集的“敲门砖”

说实话,做数据采集这么多年,我见过太多人一上来就写爬虫,结果被反爬搞得焦头烂额。其实问题的根子,往往出在没搞懂HTTP协议上。这一章,咱们就把这块“敲门砖”彻底敲明白。

3.1 HTTP/HTTPS协议基础——你发的请求,服务器到底怎么理解?

HTTP协议,说白了就是客户端和服务器之间的一种“对话规则”。你发一个请求,服务器回一个响应,就这么简单。但这里面有几个关键点,我当年踩过不少坑。

3.1.1 请求与响应的基本结构

一个HTTP请求,由三部分组成:请求行、请求头、请求体。响应也一样:状态行、响应头、响应体。

组成部分 请求 响应
起始行 GET /api/data HTTP/1.1 HTTP/1.1 200 OK
头部 Host、User-Agent、Cookie等 Content-Type、Set-Cookie等
空行 分隔头部和体 分隔头部和体
体 POST提交的表单数据 HTML、JSON、图片等

嗯,这里要注意:GET请求没有请求体,所有参数都拼在URL里。POST请求的参数放在请求体里,相对安全一些。

3.1.2 状态码——服务器在跟你说什么?

我个人习惯,看到状态码先看百位数字:

  • 2xx:成功。200是标准成功,201是创建成功(POST常用)。
  • 3xx:重定向。301永久搬家,302临时跳转。我记得有一次爬一个网站,数据一直不对,后来发现是301重定向到了另一个URL。
  • 4xx:客户端错误。404最常见,403是禁止访问(反爬了),429是请求太频繁(限流了)。
  • 5xx:服务端错误。500内部错误,502网关超时。遇到5xx,我一般会等几秒重试。
避坑指南:我曾经遇到一个网站,返回200但内容却是“请开启JavaScript”。这说明服务器虽然响应了,但真正的数据需要通过JS渲染才能拿到。这时候光靠Requests库就不够了,得用Selenium或Playwright。

3.1.3 HTTPS——多了个“S”到底多了什么?

HTTPS = HTTP + SSL/TLS。说白了,就是在HTTP外面套了一层加密协议。你想想看,如果不用HTTPS,你提交的密码、Cookie都是明文传输的,随便一个中间人就能截获。

用Requests库请求HTTPS网站时,默认会验证SSL证书。如果遇到证书过期或自签名的网站,可以设置 verify=False,但会收到一个警告。我个人建议:生产环境不要关闭验证,开发调试时可以临时关闭。

import requests

# 关闭SSL验证(仅用于调试)
response = requests.get('https://example.com', verify=False)
# 或者使用自定义证书
response = requests.get('https://example.com', cert=('/path/to/cert.pem', '/path/to/key.pem'))

3.2 Requests库的GET与POST方法——最常用的两个武器

Requests库是我用过的Python HTTP库里最顺手的。它把底层的urllib封装得特别优雅,代码写起来像说话一样自然。

3.2.1 GET请求——拿数据

GET请求,说白了就是“给我看看”。参数直接拼在URL后面,用 ? 开头,& 分隔。

import requests

# 最简单的GET请求
response = requests.get('https://api.github.com/users/octocat')
print(response.status_code)  # 200
print(response.json())       # 自动解析JSON

# 带参数的GET请求
params = {'q': 'python', 'page': 1}
response = requests.get('https://api.github.com/search/repositories', params=params)
print(response.url)  # 自动拼接参数到URL

这里有个小技巧:params 参数会自动处理URL编码。比如中文、特殊字符,都不用你手动转义,Requests帮你搞定。

3.2.2 POST请求——提交数据

POST请求,就是“我给你点东西”。数据放在请求体里,常见的有两种格式:表单数据和JSON数据。

import requests

# 提交表单数据
form_data = {'username': 'admin', 'password': '123456'}
response = requests.post('https://httpbin.org/post', data=form_data)

# 提交JSON数据
json_data = {'name': '张三', 'age': 30}
response = requests.post('https://httpbin.org/post', json=json_data)
注意:data 和 json 参数不能混用。如果你传了 data,Requests会自动设置 Content-Type: application/x-www-form-urlencoded;如果传了 json,会自动设置 Content-Type: application/json。搞错了,服务器可能解析不了。

3.3 处理Cookies与Session——让服务器记住你

HTTP协议本身是无状态的。什么意思?就是服务器每次收到请求,都不知道你是谁。那怎么让服务器记住你呢?靠Cookie和Session。

3.3.1 Cookies——服务器留在你身上的“小纸条”

服务器通过 Set-Cookie 响应头,把一个小纸条贴在你身上。你下次请求时,带上这个纸条,服务器就知道你是谁了。

import requests

# 手动设置Cookie
cookies = {'session_id': 'abc123'}
response = requests.get('https://httpbin.org/cookies', cookies=cookies)

# 从响应中获取Cookie
response = requests.get('https://httpbin.org/cookies/set?name=value')
print(response.cookies)  # <RequestsCookieJar>

3.3.2 Session对象——自动管理Cookie

手动管理Cookie太麻烦了。用 requests.Session(),它会自动帮你保存和发送Cookie。

import requests

# 创建Session对象
session = requests.Session()

# 第一次请求,服务器设置Cookie
session.get('https://httpbin.org/cookies/set?session_id=xyz')

# 第二次请求,自动带上Cookie
response = session.get('https://httpbin.org/cookies')
print(response.json())  # 可以看到session_id=xyz

我个人习惯:只要是需要登录的网站,一律用Session。它还能保持TCP连接,减少握手开销,性能更好。

3.4 设置请求头与代理——伪装与绕行

做数据采集,最怕被反爬。设置合理的请求头和使用代理,是绕过反爬的基本功。

3.4.1 请求头——让请求看起来像真人

服务器会检查请求头来判断你是不是爬虫。最常见的检查项是 User-Agent,也就是“你用什么浏览器访问的”。

import requests

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
    'Referer': 'https://www.google.com/',
    'Accept-Language': 'zh-CN,zh;q=0.9',
}
response = requests.get('https://httpbin.org/headers', headers=headers)
经验之谈:我建议准备一个User-Agent池,每次请求随机切换。有些网站还会检查 Accept-Encoding、Accept 等头,最好都模拟成浏览器的样子。

3.4.2 代理——换个IP再来

同一个IP访问太频繁,肯定会被封。用代理IP,可以分散请求来源。

import requests

# 使用HTTP代理
proxies = {
    'http': 'http://127.0.0.1:8080',
    'https': 'http://127.0.0.1:8080',
}
response = requests.get('https://httpbin.org/ip', proxies=proxies)

# 使用SOCKS5代理(需要安装requests[socks])
proxies = {
    'http': 'socks5://127.0.0.1:1080',
    'https': 'socks5://127.0.0.1:1080',
}
response = requests.get('https://httpbin.org/ip', proxies=proxies)
避坑指南:我曾经用了一个免费代理池,结果请求全失败了。后来发现那些代理要么超时,要么返回错误数据。建议:付费代理比免费代理靠谱得多,至少要有IP存活率检测。

3.5 本章知识体系

下面这张图,把本章的核心知识点串起来了。你想想看,从协议基础到实际编码,再到反爬对抗,其实是一条线。

HTTP协议与网络请求知识体系 HTTP协议与网络请求知识体系 HTTP/HTTPS协议基础 请求与响应结构 状态码含义 HTTPS加密原理 Requests库实战 GET与POST方法 Cookies与Session 请求头与代理 核心:理解协议 → 掌握工具 → 应对反爬

3.6 实战小案例:模拟登录一个简单网站

光说不练假把式。咱们用Session + 请求头 + POST,模拟登录一个测试网站。

import requests

# 1. 创建Session
session = requests.Session()

# 2. 设置请求头,模拟浏览器
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
    'Referer': 'https://httpbin.org/forms/post',
}
session.headers.update(headers)

# 3. 先GET一下登录页面,获取Cookie
session.get('https://httpbin.org/cookies/set?session_id=test123')

# 4. POST提交登录数据
login_data = {'username': 'admin', 'password': 'secret'}
response = session.post('https://httpbin.org/post', data=login_data)

# 5. 检查结果
print('状态码:', response.status_code)
print('响应内容:', response.json())
print('当前Cookie:', session.cookies.get_dict())

你看,整个过程就是:建立会话 → 伪装浏览器 → 获取Cookie → 提交数据。这套模式,能应对80%的简单网站。

小提示:实际项目中,很多网站登录后返回的Cookie有时效性。我一般会写一个 is_login() 函数,定期检查Cookie是否过期,过期就重新登录。