Python Requests库入门实战演示
前言
requests是 Python 生态里最有名的第三方 HTTP 库,它把标准库urllib那些绕来绕去的地方——手工编码参数、手工解码字节、手工管理连接——统统包成了带默认值的函数。写一行requests.get(url),参数、编码、连接复用都替你处理了。
但它有个前提必须先说清楚:requests不是标准库。import requests之前必须先pip install requests,装不上就没法用。这和urllib(随 Python 一起发布、零依赖)是完全不同的定位。另外,Python 2.7 已于2020 年 1 月 1 日停止维护,requests也早已放弃对它的支持;按 PyPI 上的元数据,最新版requests声明requires_python为>=3.10,所以在更老的 Python 3 上pip会自动挑一个兼容的旧版本装上。
本文只做入门:安装、get/post、params/data/headers三个最常用的关键字参数,以及Response对象上那五个属性。会话保持、流式下载、重试这些放到进阶文章里讲。
一、安装与第一个请求
pip install requests如果机器上有多个 Python,用python -m pip install requests更保险,能保证装到当前解释器对应的环境里。装好后最简单的验证方式是发一个请求:
# 适用于 Python 3.8+(requests 本体请用 pip 安装)
import requests
r = requests.get("https://example.com", timeout=10)
print(r.status_code) # 200
print(type(r)) # <class 'requests.models.Response'>官方的接口文档里写得很直白:所有功能都通过 7 个模块级方法暴露(request、get、head、post、put、patch、delete),它们全部返回Response对象。也就是说你只要学会读懂一个Response,七个方法就都会用了。
timeout建议永远显式传。不传时 requests 会一直等下去,服务端不响应就卡死;这一点和urllib的默认行为一样,但 requests 的文档反复强调过。
二、get 与 params
requests.get()的签名是:
requests.get(url, params=None, **kwargs)params接受字典、元组列表或 bytes,requests 会替你编码并拼到问号后面。过程中不需要你碰urlencode。
# 适用于 Python 3.8+
import requests
params = {"q": "python requests", "page": 1}
r = requests.get("https://example.com/search", params=params, timeout=10)
print(r.url) # 实际发出的完整 URL,参数已被编码拼好
print(r.status_code)
print(r.text[:200]) # 文本形式的响应体r.url是个被低估的属性:它显示的是真正发出去的URL,编码成什么样、有没有被重定向,一看便知。调试参数编码问题时,先看它。
字典里如果某个值是None,requests 会把这个键整个丢掉,不会传key=None过去;值如果是列表,默认会展开成多个同名参数(等价于urllib里的doseq=True)。
三、post:data 与 json 的区别
requests.post()的签名是:
requests.post(url, data=None, json=None, **kwargs)data和json是两套不同的请求体编码方式,用错了服务端多半解析不出来:
| 参数 | 请求体内容 | 自动设置的Content-Type | 适用场景 |
|---|
data=dict | 表单编码k=v&k2=v2 | application/x-www-form-urlencoded | 传统表单提交 |
json=dict | JSON 文本 | application/json | 现代 REST 接口 |
data=str | 原样发送字符串 | 不自动设置,需自己加头 | 特殊格式的裸文本 |
# 适用于 Python 3.8+
import requests
# 表单风格
r1 = requests.post(
"https://example.com/api/login",
data={"username": "alice", "password": "secret"},
timeout=10,
)
print(r1.status_code)
# JSON 接口风格
r2 = requests.post(
"https://example.com/api/items",
json={"name": "book", "price": 42},
timeout=10,
)
print(r2.status_code)注意json=会顺带把请求头设成application/json,而data=传字典时设的是application/x-www-form-urlencoded。手工传data=一个字符串时,requests 不知道格式,不会帮你设置Content-Type,需要自己在headers里补。
四、headers:身份与内容协商
headers是这七个方法共有的关键字参数,传一个字典即可。爬取公开页面时,最有用的两个头是User-Agent和Accept-Language。请如实标识自己:
# 适用于 Python 3.8+
import requests
headers = {
"User-Agent": "page-reader/1.0 (contact: me@example.com)",
"Accept-Language": "zh-CN,zh;q=0.9",
}
r = requests.get("https://example.com/", headers=headers, timeout=10)
print(r.status_code)
print(r.headers.get("Content-Type"))自定义头会与 requests 自带的默认头合并;你提供的同名键会覆盖默认值。这一点比urllib方便,urllib里默认User-Agent是Python-urllib/3.x,要改还得动 opener。
顺便提一句合规底线:真实可识别的 UA、请求之间time.sleep()限速、先看 robots.txt、只抓公开页面、不绕登录墙与验证码。这些是把requests当工具用,而不是当武器用的分界线。
五、Response 对象:五个必须认识的东西
Response上的属性很多,入门阶段先掌握下面五个,日常用法基本都在里面。
# 适用于 Python 3.8+
import requests
r = requests.get("https://example.com/", timeout=10)
print(r.status_code) # int,例如 200 / 404
print(r.ok) # status_code < 400 时为 True
print(r.headers["Content-Type"])
print(r.encoding) # 用于解码 r.text 的编码名,可能是 None
print(len(r.content)) # bytes,响应体原始字节
r.raise_for_status() # 4xx/5xx 时抛 requests.HTTPErrorstatus_code:整数状态码。判断成功不要只看== 200,用r.ok(它判断的是status_code < 400)或raise_for_status()更合适。text与content:text是str,content是bytes。要下载图片、压缩包这类二进制内容,必须用content。encoding:决定text怎么解码。requests 会先看 HTTP 头的Content-Type里有没有 charset;官方的说法是,如果Response.encoding是None,它会用charset_normalizer之类的工具去猜。如果你知道真实编码,就在访问text之前手动赋值,例如r.encoding = "utf-8",猜错的概率会低很多。json():把响应体按 JSON 解析成 Python 对象(字典、列表等)。解析失败会抛requests.exceptions.JSONDecodeError。
# 适用于 Python 3.8+
import requests
r = requests.get("https://example.com/api/item/1", timeout=10)
r.encoding = "utf-8" # 明确指定,避免猜测
data = r.json() # 解析失败会抛 JSONDecodeError
print(type(data))
print(data.get("name") if isinstance(data, dict) else len(data))还有一个容易忽略的r.history:它是一个Response列表,记录这次请求经过的重定向链,从最早到最新排列。排查「为什么拿到的页面和我请求的不一样」时,先看它是不是非空。
常见坑点
1. 忘了pip install就 import。
❌import requests直接写在脚本开头,换台机器就ModuleNotFoundError。 ✅ 先python -m pip install requests;交付脚本时在说明里写明这项依赖,或改用标准库urllib。
2. 不传 timeout。
❌requests.get(url)—— 服务端不响应就无限等。 ✅requests.get(url, timeout=10)。需要更细的控制可以传(连接超时, 读取超时)元组。
3. 用text拿二进制内容。
❌open("a.png", "wb").write(r.text)——str不是bytes,写文件会失败。 ✅open("a.png", "wb").write(r.content)。
4. 拿到响应就访问text,之后再改encoding。
❌print(r.text)然后再r.encoding = "utf-8"—— 第一次访问时已经按猜的编码解好了。官方文档明确要求:要在访问text之前设置encoding。 ✅ 先r.encoding = "utf-8",再r.text。
5. POST 该传 JSON 时传了data=字典。
❌requests.post(url, data={"a": 1})打 JSON 接口 —— 服务端按表单解析,直接 400。 ✅requests.post(url, json={"a": 1}),Content-Type会自动变成application/json。
6. 把status_code == 200当作唯一成功判据。
❌if r.status_code == 200:—— 201、204 这些同样表示成功。 ✅ 用r.ok或r.raise_for_status()。
7. 用r.json()解析非 JSON 响应。
❌ 对 HTML 页面调r.json()—— 抛requests.exceptions.JSONDecodeError。 ✅ 先用r.headers.get("Content-Type")判断是不是 JSON,或把调用放进try/except。
8. 把r.text当字节长度统计。
❌len(r.text)—— 字符数不等于字节数,中文一个字在这里算 1。 ✅ 要字节数用len(r.content)。
总结
| 想做的事 | 写法 |
|---|
| 发 GET | requests.get(url, params={...}, headers={...}, timeout=10) |
| 发 POST 表单 | requests.post(url, data={...}) |
| 发 POST JSON | requests.post(url, json={...}) |
| 取文本 | 先设r.encoding,再读r.text |
| 取字节 | r.content |
| 解析 JSON | r.json()(失败抛JSONDecodeError) |
| 判断成功 | r.ok或r.raise_for_status() |
| 看真实 URL | r.url、r.history |
requests上手快,是因为默认值选得好;但默认值也会掩盖行为,比如编码猜错、超时无限、text与content混用。把这些默认值搞明白,才算真的入了门。