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传成了第二个参数,结果请求一直报错,查了半天文档才发现问题。

注意:urllib默认不会自动处理重定向,也不会自动解码响应内容。如果你遇到返回数据不对的情况,先检查这两点。

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-Typeapplication/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_codejson()。先检查状态码,再解析数据,这是标准流程。

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}')

这里要注意,HTTPErrorURLError的子类。所以如果你先捕获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,到时候你会发现,网络通信的世界远比你想象的精彩。