2、Python网络库入门:urllib库的使用、requests库的安装与基本请求、response对象解析、异常处理机制
说到Python做网络通信,我最早接触的就是urllib。那时候刚入行,觉得这东西够用就行。后来项目越做越复杂,才发现urllib用起来真有点「手写算盘」的感觉。直到遇见了requests库——嗯,这才叫「现代工具」。
这一章,咱们就把这两个库掰开揉碎讲清楚。你想想看,桌面应用里最常干的事是什么?无非就是发个请求、拿个数据、处理个异常。搞明白这些,你的应用就能跟外面的世界对话了。
2.1 urllib库:Python自带的「老黄牛」
urllib是Python标准库的一部分,不需要额外安装。说白了,它就是Python官方给你准备的一套网络工具包。我个人习惯在写简单脚本或者做快速验证时用它,毕竟不用装依赖,开箱即用。
2.1.1 发送GET请求
先看个最简单的例子:
import urllib.request
# 发送GET请求
response = urllib.request.urlopen('https://api.github.com')
print(response.read().decode('utf-8'))
就这么几行,就能拿到GitHub API的返回数据。不过要注意,urlopen返回的是一个类文件对象,你得手动调用read()方法,还得自己处理编码。我在项目中遇到过一个问题——忘记解码,结果打印出来一堆乱码,排查了半天才发现是编码没指定。
2.1.2 发送POST请求
POST请求稍微麻烦一点,需要把数据编码一下:
import urllib.request
import urllib.parse
# 准备数据
data = urllib.parse.urlencode({'key': 'value'}).encode('utf-8')
# 发送POST请求
response = urllib.request.urlopen('https://httpbin.org/post', data=data)
print(response.read().decode('utf-8'))
看到没?你得先urlencode,再encode,两步操作。说实话,这设计确实有点啰嗦。但没办法,标准库嘛,追求的是通用性,不是易用性。
2.1.3 添加请求头
很多API需要你带上认证信息或者指定内容类型。urllib里得用Request对象:
import urllib.request
url = 'https://api.github.com/user'
headers = {
'User-Agent': 'Mozilla/5.0',
'Authorization': 'token YOUR_TOKEN'
}
req = urllib.request.Request(url, headers=headers)
response = urllib.request.urlopen(req)
print(response.read().decode('utf-8'))
这里有个坑——Request对象的参数顺序容易搞混。我曾经在代码里把headers传成了第二个参数,结果请求一直报错,查了半天文档才发现问题。
2.2 requests库:更优雅的选择
说实话,我真正推荐大家用的是requests库。它把urllib那些繁琐的操作都封装好了,用起来特别顺手。你想想看,发个请求还要手动编码、手动解码,这体验能好吗?
2.2.1 安装requests
安装很简单,一行命令搞定:
pip install requests
如果你用的是虚拟环境(我强烈建议你用),记得先激活环境再安装。我在刚开始带团队时,有个同事直接在系统环境里装了requests,结果跟其他项目冲突,折腾了一下午才解决。
2.2.2 发送GET请求
用requests发GET请求,代码简洁到令人感动:
import requests
response = requests.get('https://api.github.com')
print(response.text) # 自动解码,直接输出字符串
看到了吗?没有read(),没有decode(),一行代码搞定。这就是我为什么说requests是现代工具——它懂你真正想要什么。
带参数的GET请求也很简单:
import requests
params = {'q': 'python', 'sort': 'stars'}
response = requests.get('https://api.github.com/search/repositories', params=params)
print(response.url) # 自动拼接URL
print(response.json()) # 直接解析JSON
这里有个小技巧——response.json()方法会帮你把返回的JSON字符串解析成Python字典。我在做桌面应用的API集成时,几乎每次都用到这个方法。
2.2.3 发送POST请求
POST请求同样简洁:
import requests
# 发送JSON数据
data = {'key': 'value'}
response = requests.post('https://httpbin.org/post', json=data)
print(response.json())
# 发送表单数据
response = requests.post('https://httpbin.org/post', data={'key': 'value'})
print(response.json())
注意看,json参数和data参数的区别。传json时,requests会自动设置Content-Type为application/json,并且帮你做序列化。传data时,默认是表单格式。这个细节很重要,我在对接某个第三方API时,就因为传错了参数类型,对方一直返回400错误。
2.3 response对象解析
不管是urllib还是requests,拿到响应后都得解析。但requests的response对象明显更好用。
2.3.1 常用属性
| 属性 | 说明 | 示例 |
|---|---|---|
status_code |
HTTP状态码 | 200, 404, 500 |
text |
响应内容(字符串) | "{\"key\": \"value\"}" |
content |
响应内容(字节) | b'{"key": "value"}' |
json() |
解析为JSON对象 | {'key': 'value'} |
headers |
响应头 | {'Content-Type': 'application/json'} |
url |
最终请求的URL | https://api.github.com |
我个人最常用的是status_code和json()。先检查状态码,再解析数据,这是标准流程。
2.3.2 判断请求是否成功
requests提供了一个很方便的方法:
import requests
response = requests.get('https://api.github.com')
if response.ok: # 状态码小于400
print('请求成功')
data = response.json()
else:
print(f'请求失败,状态码:{response.status_code}')
response.ok这个属性,说白了就是帮你判断状态码是不是在200-399之间。比手动判断方便多了。
response.headers.get('Content-Type')。注意头信息是不区分大小写的,requests会自动帮你处理。
2.4 异常处理机制
网络通信最怕什么?怕不稳定。你想想看,用户的网络可能断掉,服务器可能挂掉,API可能限流。如果不做异常处理,你的桌面应用分分钟崩溃给你看。
2.4.1 urllib的异常处理
urllib的异常类型比较多,我列几个常见的:
import urllib.request
import urllib.error
try:
response = urllib.request.urlopen('https://api.github.com', timeout=5)
print(response.read().decode('utf-8'))
except urllib.error.URLError as e:
print(f'网络错误:{e.reason}')
except urllib.error.HTTPError as e:
print(f'HTTP错误:{e.code} - {e.reason}')
except Exception as e:
print(f'未知错误:{e}')
这里要注意,HTTPError是URLError的子类。所以如果你先捕获URLError,就捕获不到HTTPError了。正确的做法是先捕获子类,再捕获父类。我曾经在这个顺序上栽过跟头,调试了半天才发现异常被提前拦截了。
2.4.2 requests的异常处理
requests的异常处理更清晰一些:
import requests
from requests.exceptions import RequestException, Timeout, ConnectionError
try:
response = requests.get('https://api.github.com', timeout=5)
response.raise_for_status() # 如果状态码不是200,抛出HTTPError
print(response.json())
except Timeout:
print('请求超时,请检查网络')
except ConnectionError:
print('连接失败,服务器可能挂了')
except RequestException as e:
print(f'请求异常:{e}')
这里有个关键方法——raise_for_status()。它会把4xx和5xx的状态码转换成异常抛出。我个人习惯在每次请求后都调用这个方法,这样就不用手动判断状态码了。
2.4.3 重试机制
网络请求偶尔失败是正常的,关键是要有重试机制。我常用的做法是:
import requests
from time import sleep
def fetch_with_retry(url, max_retries=3, delay=1):
for attempt in range(max_retries):
try:
response = requests.get(url, timeout=5)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f'第{attempt + 1}次尝试失败:{e}')
if attempt < max_retries - 1:
sleep(delay * (attempt + 1)) # 递增等待时间
else:
raise # 最后一次失败,抛出异常
# 使用示例
try:
data = fetch_with_retry('https://api.github.com')
print(data)
except Exception as e:
print(f'最终失败:{e}')
这个模式我几乎在每个项目里都用。递增等待时间是个好习惯——第一次失败等1秒,第二次等2秒,第三次等3秒。这样既不会给服务器太大压力,也能提高成功率。
sleep,结果瞬间发了上百个请求,直接把对方的API打挂了。后来对方把我IP封了整整一天。所以记住——重试一定要加延迟,这是基本的网络礼仪。
好了,这一章的内容就到这里。urllib和requests,一个老成持重,一个现代优雅。我个人建议:日常开发用requests,特殊场景(比如不能装第三方库的环境)用urllib。下一章咱们聊聊更高级的话题——异步网络请求和WebSocket,到时候你会发现,网络通信的世界远比你想象的精彩。