3. 发送第一个GET请求:用requests库实战一把

上一章我们聊完了HTTP协议的基本骨架。说实话,光看报文格式确实有点干。我当年啃协议的时候,也是觉得这东西不亲手敲两行代码,心里不踏实。所以这一章,咱们直接上手,用Python的requests库发一个真正的GET请求。

你可能会想:不就是发个请求吗,有什么难的?

嗯,真上手了你会发现——难的不是发出去,而是怎么把回应里的数据完整、准确地取出来。这中间有很多坑,我在项目里踩过不止一回。

3.1 环境准备:别小看这一步

开始之前,确认你的Python环境里已经装好了requests库。它不是标准库,需要单独安装。

pip install requests

装完之后,打开Python交互环境或者新建一个.py文件。我个人习惯在项目里单独建一个http_demo.py,方便反复测试。

小提示:如果你在公司内网环境,可能需要配置代理。requests支持通过环境变量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把我们的请求原样反射回来了,这就是它的工作方式。

重点记忆:response对象是整个交互的入口。它里面封装了状态码、响应头、响应体、编码方式等所有关键信息。

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(可能会跟踪重定向)
避坑指南:我曾经在解析一个JSON接口时直接用了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()那一刻开始,到拿到解析好的数据为止,中间经历了哪些环节?

一次GET请求的完整旅程 准备URL requests.get() TCP连接与发送 接收响应 status_code检查 解析响应体 关键说明 • 准备URL:包含协议、主机、路径、查询参数 • requests.get():构建请求报文,自动处理请求头 • TCP连接与发送:三次握手后发送HTTP请求行和头部 • 接收响应:接收状态行、响应头、响应体 • 状态码检查:判断是否成功(200-299)或异常 • 解析响应体:根据Content-Type决定用text/content/json()

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。亲手敲一遍,比看十遍都管用。