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 3 已经把 Python 2 里混乱的 urllib / urllib2 彻底统一成了现在的 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 请求,需要先把参数字典编码成字节串再传入 data

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

# 准备要提交的参数
data_dict = {'word': 'hello urllib', 'author': 'germey'}
# 用 urlencode 转换成 URL 编码的字符串,再编码成 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:
    # 故意设置一个极短的超时时间来测试异常
    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. 用 Request 构造请求
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,      # 很少用到,默认即可
    method=None              # 显式指定请求方法:GET / POST / PUT / DELETE 等
)

2.3 高级玩法:OpenerDirector

urlopen 背后其实是 Python 帮我们准备好的一个默认 Opener。如果要处理身份认证、代理、Cookies 等复杂场景,就需要自己动手构建 Opener 了。

场景1:网站需要 Basic Auth 认证

比如某些测试站点需要输入用户名和密码才能访问:

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

# 1. 创建密码管理器
password_mgr = HTTPPasswordMgrWithDefaultRealm()
# 2. 添加认证信息
password_mgr.add_password(
    realm=None,   # 一般网站没有指定 realm,留空即可
    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)
opener.open('https://www.baidu.com')

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)
print('\n✅ Cookies 已保存到 baidu_cookies.txt')

3. exception-handling

爬虫在运行过程中难免遇到网络故障、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 Not Found、500 Internal Server Error
    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 拆成多个组成部分:

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

urlspliturlparse 很相似,只是不会单独拆出 params(现代网站已经很少使用这一部分)。

4.2 拼接 URL:urlunparse / urlunsplit / urljoin

  • urlunparseurlunsplit 是上面拆分函数的反向操作,可以把元组/列表拼回完整的 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 处理相对链接
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 / unquote

中文和特殊符号放在 URL 里必须经过编码,才能被服务器正确识别:

from urllib.parse import urlencode, quote, unquote

# urlencode:把字典转换成 URL 查询字符串
params = {'keyword': 'Python爬虫', 'page': 1, 'size': 10}
base_url = 'https://www.example.com/search?'
full_url = base_url + urlencode(params)
print(full_url)  # 中文会被自动编码

# quote / unquote:单独处理字符串的编码和解码
chinese = 'Python入门到放弃'
encoded = quote(chinese)
decoded = unquote(encoded)
print(f'编码后:{encoded}')
print(f'解码后:{decoded}')

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

  1. 一定要设置 User-Agent
    默认的 urllib User-Agent 会直接暴露你的爬虫身份,大概率遇到 403,务必伪装成浏览器。

  2. 合理设置 timeout
    一般建议 3~10 秒,并且配合异常捕获,避免程序长时间无响应。

  3. 显式处理编码
    将响应字节流转成字符串时,最好先根据响应头的 Content-Type 来确定编码,直接写死 utf-8 有时会碰到乱码。

  4. 遵守 robots.txt
    入门时可以暂时不处理,但如果要写公开爬虫或大规模采集,一定要先通过 robotparser 检查是否允许访问。

  5. 复杂需求及时换库
    urllib 虽然内置,但语法相对繁琐。当遇到 Cookie 池、代理池、异步并发等需求时,更推荐使用 requests(同步)或 aiohttp(异步)。


总结

urllib 是 Python 爬虫入门的“敲门砖”。掌握它,你就能理解 HTTP 请求的基本流程:构造请求 → 发送 → 接收响应 → 处理异常。一旦把这些基础打牢,以后再学习其他第三方库就会特别顺畅。