4. Python 网络请求库:urllib 与 requests 实战

网络请求是数据采集的第一步。说白了,就是让你的 Python 程序像浏览器一样去访问网页、拿回数据。Python 里最常用的两个库就是 urllib(内置)和 requests(第三方)。我个人习惯是:简单测试用 urllib,正式项目必用 requests。为什么?往下看你就明白了。

本章核心脉络:urllib 基础 → requests 进阶 → Session 会话管理 → 代理与超时控制。这四个环节串起来,你就能应对 90% 的采集场景。

Python 网络请求知识体系 urllib 内置库 requests 第三方库 Session 会话 urlopen / Request get / post / 高级封装 Cookie 持久化 代理设置 超时控制 重试机制 从 urllib 入门 → 用 requests 提效 → 靠 Session 维持状态 → 用代理和超时保稳定

4.1 urllib 库基础使用

urllib 是 Python 自带的网络请求库,不需要额外安装。你想想看,一个连 pip 都不需要就能用的库,多方便。但它的 API 设计比较「原始」,用起来没那么顺手。

核心模块:

  • urllib.request — 发送请求、获取响应
  • urllib.parse — URL 解析与编码
  • urllib.error — 异常处理

来个最简单的例子:

import urllib.request

# 发送 GET 请求
response = urllib.request.urlopen('https://httpbin.org/get')
print(response.status)        # 200
print(response.read().decode('utf-8'))

嗯,这里要注意:urlopen() 返回的是一个类文件对象,需要用 .read() 读取内容,而且返回的是字节流,得手动 decode。我在项目中遇到过不少新手直接打印 response.read(),结果看到一堆乱码就慌了。

带参数的请求:

import urllib.request
import urllib.parse

# 构造查询参数
params = urllib.parse.urlencode({'q': '数据采集', 'page': 1})
url = f'https://httpbin.org/get?{params}'

req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
response = urllib.request.urlopen(req)
print(response.read().decode())

我的经验:urllib 的 Request 对象可以让你自定义请求头。很多网站会检查 User-Agent,不加的话容易被拦截。我曾经爬一个论坛,用默认的 Python-urllib 头,直接返回 403,加上浏览器 UA 就正常了。

4.2 requests 库安装与使用

说实话,urllib 用起来有点啰嗦。每次都要手动处理编码、构造请求头、处理异常……所以业界更推荐 requests 库。它把底层细节都封装好了,API 设计得非常人性化。

安装:

pip install requests

一行命令搞定。来看看它有多简洁:

import requests

# GET 请求
resp = requests.get('https://httpbin.org/get')
print(resp.status_code)   # 200
print(resp.text)          # 自动解码

# POST 请求
data = {'username': 'admin', 'password': '123456'}
resp = requests.post('https://httpbin.org/post', data=data)
print(resp.json())        # 自动解析 JSON

你发现没?resp.text 直接返回字符串,resp.json() 直接返回字典。urllib 要写好几行的事,requests 一行搞定。

常用方法对比:

功能 urllib 写法 requests 写法
GET 请求 urlopen(url) requests.get(url)
POST 请求 urlopen(url, data=bytes) requests.post(url, data=dict)
添加请求头 Request(url, headers=dict) requests.get(url, headers=dict)
解析 JSON json.loads(resp.read()) resp.json()
异常处理 urllib.error.URLError requests.exceptions.RequestException

我的建议:除非你是在受限环境(比如某些服务器不允许装第三方库),否则直接用 requests。它节省的时间值得那一次 pip install。

4.3 Session 与会话管理

很多网站需要登录后才能采集数据。你想想看,每次请求都重新登录,多麻烦。这时候就需要 Session 来维持会话状态。

Session 的核心作用就是 自动保存 Cookie,并在后续请求中自动携带。说白了,它模拟了浏览器的「记住我」功能。

import requests

# 创建 Session 对象
session = requests.Session()

# 第一次请求:登录
login_data = {'username': 'test', 'password': 'test123'}
resp = session.post('https://httpbin.org/post', data=login_data)
print('登录成功')

# 第二次请求:自动携带 Cookie
resp = session.get('https://httpbin.org/cookies')
print(resp.text)  # 可以看到登录后的 Cookie 信息

我在项目中遇到过这样一个场景:爬一个需要登录的电商网站,第一次用 requests.get() 直接请求,返回登录页面。后来改用 Session,先登录再请求,数据就正常返回了。你想想看,Session 帮你省了多少事。

注意:Session 对象默认会保持长连接,用完记得调用 session.close() 释放资源。或者用 with 上下文管理器自动管理。

with requests.Session() as session:
    session.post('https://httpbin.org/post', data={'key': 'value'})
    resp = session.get('https://httpbin.org/get')
    # 自动关闭

4.4 代理设置与超时控制

数据采集时,你可能会遇到两个问题:IP 被封 和 请求卡死。代理和超时就是解决这两个问题的利器。

代理设置:

import requests

# 设置代理
proxies = {
    'http': 'http://127.0.0.1:8080',
    'https': 'http://127.0.0.1:8080'
}

resp = requests.get('https://httpbin.org/ip', proxies=proxies)
print(resp.text)  # 显示代理 IP

我曾经爬一个反爬很严的网站,同一个 IP 请求 50 次就被封了。后来用代理池,每次请求换一个 IP,再也没被封过。嗯,这里要注意:免费代理往往不稳定,建议用付费代理或自建代理池。

超时控制:

import requests

try:
    # 设置连接超时 3 秒,读取超时 5 秒
    resp = requests.get('https://httpbin.org/delay/10', timeout=(3, 5))
    print(resp.text)
except requests.exceptions.Timeout:
    print('请求超时,请检查网络或目标服务器')

你想想看,如果不设超时,遇到一个响应慢的服务器,你的程序可能卡在那里几分钟甚至更久。我见过一个采集任务,因为没设超时,一个请求卡了 10 分钟,整个任务链都断了。

代理 + 超时 + Session 组合使用:

import requests

session = requests.Session()
session.proxies = {
    'http': 'http://127.0.0.1:8080',
    'https': 'http://127.0.0.1:8080'
}

try:
    resp = session.get(
        'https://httpbin.org/delay/3',
        timeout=(5, 10),
        headers={'User-Agent': 'Mozilla/5.0'}
    )
    print(resp.status_code)
except requests.exceptions.RequestException as e:
    print(f'请求失败: {e}')
finally:
    session.close()

我的经验:生产环境中,建议把代理、超时、重试机制封装成一个工具函数。这样每个采集任务都能复用,不用重复写这些配置代码。

4.5 避坑指南

做数据采集这几年,我踩过不少坑。分享几个最常见的:

  • 忘记设置 User-Agent:很多网站会拦截非浏览器请求。我建议至少设置一个常见的浏览器 UA。
  • 不处理重定向:requests 默认会跟随重定向,但 urllib 需要手动设置。如果你不想跟随,可以设置 allow_redirects=False。
  • 忽略 SSL 证书验证:测试时可以设置 verify=False,但生产环境建议用 verify='/path/to/cert.pem' 指定证书。
  • 不关闭 Session:Session 对象会占用连接资源,用完一定要关闭。

我曾经:有一次爬一个 HTTPS 网站,忘了关 SSL 验证,结果在测试环境跑得好好的,部署到生产环境就报 SSL 错误。排查了半天才发现是生产环境的 Python 版本没有更新 CA 证书。从那以后,我都在代码里显式指定证书路径。