3. 发送第一个GET请求:用requests库实战一把
上一章我们聊完了HTTP协议的基本骨架。说实话,光看报文格式确实有点干。我当年啃协议的时候,也是觉得这东西不亲手敲两行代码,心里不踏实。所以这一章,咱们直接上手,用Python的requests库发一个真正的GET请求。
你可能会想:不就是发个请求吗,有什么难的?
嗯,真上手了你会发现——难的不是发出去,而是怎么把回应里的数据完整、准确地取出来。这中间有很多坑,我在项目里踩过不止一回。
3.1 环境准备:别小看这一步
开始之前,确认你的Python环境里已经装好了requests库。它不是标准库,需要单独安装。
pip install requests
装完之后,打开Python交互环境或者新建一个.py文件。我个人习惯在项目里单独建一个http_demo.py,方便反复测试。
HTTP_PROXY和HTTPS_PROXY自动读取代理设置,不用在代码里硬编码。
3.2 发送第一个GET请求
requests库的设计哲学就是「让HTTP变得人性化」。发一个GET请求,核心只需要两样东西:目标URL和requests.get()方法。
来看一个最基本的例子。我选了一个公开的、不需要认证的API做演示:
import requests
url = "https://httpbin.org/get"
response = requests.get(url)
print(response.status_code)
print(response.text)
这段代码干了什么?
- 第1行:把requests库导进来。记住,大小写敏感。
- 第3行:定义目标URL。httpbin.org是一个专门用来测试HTTP请求的免费服务,咱们课程后面还会经常用到它。
- 第4行:真正发起GET请求。这行代码执行的时候,requests库会帮我们构建请求报文、处理TCP连接、发送数据、接收响应。所有这些底层细节都被封装了。
- 第6-7行:打印出状态码和响应正文。
跑完之后,你会在控制台看到类似这样的输出:
200
{
"args": {},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Host": "httpbin.org",
"User-Agent": "python-requests/2.31.0"
},
"origin": "你的公网IP",
"url": "https://httpbin.org/get"
}
状态码200,说明请求成功。响应正文是一段JSON格式的数据,包含了请求的详细信息。为什么会有这些信息?因为httpbin.org把我们的请求原样反射回来了,这就是它的工作方式。
3.3 理解返回值:Response对象拆解
很多人拿到response之后,直接就response.text一把梭。但我在项目中发现,这样做很容易出问题——特别是当服务端返回的不是文本,或者编码不对的时候。
咱们把response对象里最常用的几个属性和方法整理一下:
| 属性/方法 | 返回类型 | 说明 |
|---|---|---|
response.status_code |
int | HTTP状态码,200表示成功,404表示未找到 |
response.headers |
CaseInsensitiveDict | 响应头,不区分大小写,可以直接用response.headers['Content-Type'] |
response.text |
str | 响应体的文本形式,requests会根据响应头自动解码 |
response.content |
bytes | 响应体的原始字节数据,下载图片或二进制文件时必用 |
response.json() |
dict/list | 把响应体当作JSON解析,如果格式不对会抛异常 |
response.encoding |
str | 当前使用的编码,可以手动修改 |
response.ok |
bool | 状态码小于400时为True,一个快速判断成功与否的捷径 |
response.url |
str | 最终请求的URL(可能会跟踪重定向) |
response.text然后手动json.loads()。结果服务端返回的Content-Type里没写编码,requests默认用了ISO-8859-1,导致中文全部乱码。后来改用response.json(),问题一次性解决——因为json()方法会优先使用字节流解码,不受响应头编码字段的误导。
3.4 实操:解析不同格式的返回内容
现实世界中的API返回格式千奇百怪。咱们用三个例子,把最常见的三种情况都过一遍。
3.4.1 解析JSON格式
这是最普遍的情况。直接调用response.json()是最优解:
import requests
url = "https://httpbin.org/json"
response = requests.get(url)
data = response.json() # 返回Python字典
print(data['slideshow']['title'])
print(data['slideshow']['author'])
为什么推荐用json()而不是text?因为json()内部做了两件事:第一,它检查Content-Type是否包含application/json;第二,它会用字节流解码,绕开编码猜测的bug。说白了,更安全。
3.4.2 获取二进制内容(图片、文件等)
我在项目里做过一个爬取验证码图片的功能,当时就踩了坑——用text去拿图片数据,结果全乱码了。正确的做法是用response.content:
import requests
url = "https://httpbin.org/image/png"
response = requests.get(url)
with open("captcha.png", "wb") as f:
f.write(response.content) # content返回的是bytes
print(f"图片大小: {len(response.content)} bytes")
print(f"Content-Type: {response.headers['Content-Type']}")
你看,只需要把response.content写入文件就可以了。注意用wb模式打开文件,不然会出类型错误。
3.4.3 处理非标准编码的文本
有些老旧系统,返回的响应头里编码字段是错的或者干脆没有。这时候就需要手动干预:
import requests
url = "https://httpbin.org/encoding/utf8"
response = requests.get(url)
# 如果发现中文乱码,检查编码
print(f"自动检测的编码: {response.encoding}")
# 手动指定编码
response.encoding = "utf-8"
print(response.text)
response.apparent_encoding查看requests根据响应体字节流自动推测出来的编码,然后跟自己预期的编码做对比。如果两者不一致,以apparent_encoding为准往往更靠谱。
3.5 本章知识图谱:一次GET请求的完整旅程
说了这么多,我们用一张图把整个流程串起来。从你敲下requests.get()那一刻开始,到拿到解析好的数据为止,中间经历了哪些环节?
3.6 实战中的小技巧和避坑点
到这里,你已经掌握了发送GET请求的核心操作。但实际项目中,总会遇到一些意外情况。我把这些年攒下来的经验总结成几条,希望对你有帮助。
第一,不要迷信response.ok。
我见过不少同事直接写if response.ok: ...。ok的判定标准是状态码小于400。但你想想,302重定向也是小于400的,可是302并不代表业务成功。所以我的做法是:判断200就老老实实写response.status_code == 200,或者用raise_for_status()显式抛出异常。
第二,超时设置必须有。
不发请求的时候你觉得网络很好。一到生产环境,有些上游服务慢得像乌龟。如果你没设超时,一个请求可能卡住几十秒甚至几分钟,导致线程池耗尽。requests.get(url, timeout=5)这个参数一定要加上。
第三,用Session对象复用连接。
如果你要连续发多个请求到同一个服务器(比如分页拉取数据),别每次都新建连接。用requests.Session()来保持长连接,效率提升非常明显。这个话题我们在后面章节会专门讲。
第四,关注重定向行为。
requests默认会跟随重定向(比如301/302),但有时候你不想跟——比如你要拿到第一个URL返回的Set-Cookie。这时可以设置allow_redirects=False,自己控制重定向逻辑。
好了,关于GET请求的基础操作,咱们就聊到这里。你打开编辑器试一下,哪怕只是打印一个response.text。亲手敲一遍,比看十遍都管用。