Python urllib 模块使用指南

1. 概述

urllib 是 Python 标准库中唯一内置的 HTTP 全链路请求工具集,从请求发送、异常捕获、URL 处理到 robots.txt 规则解析,一站式覆盖基础爬虫的核心需求,对入门学习者非常友好。

它主要包含 4 个模块:

  • urllib.request:发送 GET/POST 等 HTTP 请求的核心
  • urllib.error:统一捕获请求过程中的 URL/HTTP 异常
  • urllib.parse:处理 URL 拼接、编码、分割等操作的工具箱
  • urllib.robotparser:robots.txt 协议解析工具(入门后偶尔用到)

⚠️ 别再查 Python 2 的 urllib/urllib2 了!Python 3 已经彻底统一为 urllib 包,拆分出了更清晰的子模块。


2. 发送请求

2.1 基础请求:urlopen

urlopen 是最简单的请求入口,一行代码就能发起 GET 请求:

from urllib.request import urlopen

# 发起无参数的 GET 请求
response = urlopen('https://www.python.org')
# 读取响应字节流,转为 UTF-8 文本
print(response.read().decode('utf-8'))

除了 URL 外,urlopen 还支持几个关键参数:

参数1:data → 用于 POST 请求

POST 请求需要先把字典参数编码成字节流:

from urllib.parse import urlencode
from urllib.request import urlopen

# 构造字典参数
data_dict = {'word': 'hello urllib', 'author': 'germey'}
# 1. 用 urlencode 转为 URL 编码字符串
# 2. 再用 bytes() 转为 UTF-8 字节流
data_bytes = bytes(urlencode(data_dict), encoding='utf-8')

# 带 data 参数会自动转为 POST 请求
response = urlopen('https://httpbin.org/post', data=data_bytes)
print(response.read().decode('utf-8'))

参数2:timeout → 超时控制

防止网络波动导致程序卡死:

import socket
import urllib.error
from urllib.request import urlopen

try:
    # 设置 0.1 秒超时(故意设短测试异常)
    response = urlopen('https://httpbin.org/get', timeout=0.1)
except urllib.error.URLError as e:
    # 判断异常原因是否为超时
    if isinstance(e.reason, socket.timeout):
        print('⚠️ 请求超时,请检查网络或延长 timeout')

参数3:其他安全参数

  • context:自定义 SSL 验证规则(入门时可以默认跳过验证,但生产环境禁用)
  • cafile/capath:指定本地 CA 证书路径(适用于内网 HTTPS 站点)

2.2 进阶请求:Request

当需要自定义请求头(User-Agent、Referer 等)、指定请求方法时,直接用 urlopen 就不够灵活了,需要先构建 Request 对象:

from urllib.request import Request, urlopen

# 1. 基础构造
req = Request('https://python.org')
# 2. 动态添加请求头(伪装成浏览器很重要!)
req.add_header('User-Agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36')
# 3. 发起请求
response = urlopen(req)
print(response.status)  # 输出响应码 200

Request 完整构造方法(用的时候按需传参):

Request(
    url,
    data=None,       # POST 字节流
    headers={},      # 请求头字典,也可以用 add_header 动态加
    origin_req_host=None,  # 请求发起的主机(一般自动生成)
    unverifiable=False,    # 很少用到的参数,默认 False
    method=None      # 显式指定请求方法:GET/POST/PUT/DELETE 等
)

2.3 高级场景:OpenerDirector

urlopen 其实是 Python 内置的默认 OpenerDirector,如果需要处理身份验证、代理、Cookies 等复杂场景,就需要自定义 Opener

场景1:网站身份验证(Basic Auth)

比如测试站 https://ssr3.scrape.center/ 需要账号密码:

from urllib.request import (
    HTTPPasswordMgrWithDefaultRealm,
    HTTPBasicAuthHandler,
    build_opener
)

# 1. 创建密码管理器
password_mgr = HTTPPasswordMgrWithDefaultRealm()
# 2. 添加验证信息
password_mgr.add_password(
    realm=None,  # 一般网站没有指定,留空即可
    uri='https://ssr3.scrape.center/',
    user='admin',
    passwd='admin'
)
# 3. 创建验证 Handler
auth_handler = HTTPBasicAuthHandler(password_mgr)
# 4. 用 Handler 构建自定义 Opener
opener = build_opener(auth_handler)
# 5. 发起验证请求
response = opener.open('https://ssr3.scrape.center/')
print(response.read().decode('utf-8'))

场景2:代理 IP 设置

防止同一 IP 请求过多被封:

from urllib.request import ProxyHandler, build_opener

# 1. 配置代理字典(HTTP/HTTPS 分开写)
proxy_dict = {
    'http': 'http://127.0.0.1:8080',
    'https': 'https://127.0.0.1:8080'
}
# 2. 创建代理 Handler
proxy_handler = ProxyHandler(proxy_dict)
# 3. 构建 Opener
opener = build_opener(proxy_handler)
# 4. 发起代理请求
try:
    response = opener.open('https://www.baidu.com', timeout=3)
    print('✅ 代理连接成功')
except Exception as e:
    print(f'❌ 代理连接失败:{e}')

场景3:Cookies 处理

模拟登录后保持会话状态:

from http.cookiejar import CookieJar, MozillaCookieJar
from urllib.request import HTTPCookieProcessor, build_opener

# --- 情况1:内存中保存 Cookies(临时会话)
cookie_jar = CookieJar()
cookie_handler = HTTPCookieProcessor(cookie_jar)
opener = build_opener(cookie_handler)
# 第一次请求(比如登录后),Cookies 会自动存入 cookie_jar
opener.open('https://www.baidu.com')
# 打印内存中的 Cookies
print('内存中的 Cookies:')
for cookie in cookie_jar:
    print(f'  {cookie.name}: {cookie.value}')

# --- 情况2:保存 Cookies 到本地文件(持久化会话)
# 用 MozillaCookieJar 兼容浏览器的 cookies.txt 格式
local_cookie = MozillaCookieJar('baidu_cookies.txt')
local_handler = HTTPCookieProcessor(local_cookie)
local_opener = build_opener(local_handler)
# 发起请求并保存
local_opener.open('https://www.baidu.com')
local_cookie.save(ignore_discard=True, ignore_expires=True)  # 忽略过期和临时 Cookie
print('\n✅ 已保存到 baidu_cookies.txt')

3. 异常处理

爬虫运行过程中难免遇到网络错误、404/500 等问题,必须用异常捕获增强程序健壮性

urllib 主要提供两个异常类,且 HTTPErrorURLError 的子类,捕获时先抓子类再抓父类

from urllib.request import urlopen
from urllib.error import HTTPError, URLError

try:
    response = urlopen('https://httpbin.org/status/404', timeout=3)
except HTTPError as e:
    # HTTP 错误:比如 404 未找到、500 服务器错误
    print(f'❌ HTTP错误:状态码 {e.code},原因 {e.reason}')
    # 可以获取响应头
    # print(e.headers)
except URLError as e:
    # URL 错误:比如域名不存在、网络不通、超时
    print(f'❌ URL错误:{e.reason}')
else:
    # 无异常时执行
    print('✅ 请求成功')

4. URL 解析

爬虫经常需要拼接、分割或编码 URL,urllib.parse 模块完全能搞定:

4.1 分割 URL:urlparse/urlsplit

把完整 URL 拆成 6/5 个部分(区别是 urlparse 会拆出 params,现代网站很少用):

from urllib.parse import urlparse

url = 'https://www.baidu.com/index.html;user?id=5&name=test#comment'
result = urlparse(url)
print(result)
# 输出:ParseResult(scheme='https', netloc='www.baidu.com', path='/index.html', params='user', query='id=5&name=test', fragment='comment')

# 直接用属性访问对应部分
print(f'协议:{result.scheme}')
print(f'域名:{result.netloc}')
print(f'路径:{result.path}')

4.2 拼接 URL:urlunparse/urlunsplit/urljoin

  • urlunparse/urlunsplit:对应反向操作,把列表/元组拼成完整 URL
  • urljoin:更常用的「相对路径转绝对路径」工具
from urllib.parse import urlunparse, urljoin

# --- urlunparse
data = ['https', 'www.baidu.com', '/index.html', '', 'id=6', 'new_comment']
print(urlunparse(data))  # 输出:https://www.baidu.com/index.html?id=6#new_comment

# --- urljoin(处理 HTML 中的相对链接非常有用)
base_url = 'https://www.example.com/blog/'
rel_url1 = 'article1.html'
rel_url2 = '../about.html'
print(urljoin(base_url, rel_url1))  # https://www.example.com/blog/article1.html
print(urljoin(base_url, rel_url2))  # https://www.example.com/about.html

4.3 编码 URL 参数/中文字符:urlencode/quote

  • urlencode:把字典参数转为 URL 编码的查询字符串
  • quote/unquote:单独编码/解码中文字符或特殊符号
from urllib.parse import urlencode, quote, unquote

# --- urlencode
params = {'keyword': 'Python爬虫', 'page': 1, 'size': 10}
base_url = 'https://www.example.com/search?'
full_url = base_url + urlencode(params)
print(full_url)  # keyword 会自动编码

# --- quote/unquote
chinese = 'Python入门到放弃'
encoded = quote(chinese)
decoded = unquote(encoded)
print(f'编码后:{encoded}')
print(f'解码后:{decoded}')

5. 最佳实践(避坑指南)

  1. 必须设置 User-Agent:默认的 urllib User-Agent 会被大多数网站直接识别为爬虫,大概率返回 403 Forbidden
  2. 合理设置 timeout:一般设置 3-10 秒,配合异常捕获
  3. 显式处理编码:响应字节流转文本时,最好先判断响应头的 Content-Type 编码(虽然入门可以直接用 utf-8 试)
  4. 遵守 robots.txt:入门时可以暂时不管,但写公开爬虫或批量爬取时,一定要先检查 can_fetch
  5. 复杂需求换库:urllib 虽然内置,但语法较繁琐,有 Cookie 池、代理池、异步需求的话,建议用 requests(同步)或 aiohttp(异步)

总结

urllib 是 Python 爬虫入门的「敲门砖」,通过它能理解 HTTP 请求的基本流程(构造请求→发送→接收响应→异常处理)。熟练掌握后,再转用第三方库会非常轻松!