一、为什么要学习 urllib
在 Python 爬虫开发中,urllib 是一个非常重要的基础库。
在标准库中,urllib 并不是单一功能模块,而是一组与 URL 处理相关的工具。入门阶段最常用的两个部分是:
urllib.request:负责发送请求urllib.parse:负责处理 URL 和请求参数
补充:urllib 常见模块还包括:
urllib.error:处理请求异常urllib.robotparser:解析 robots 协议
本篇主要聚焦最常用的 request 和 parse。
它的特点是:
- Python 内置,无需安装
- 可以直接发送 HTTP 请求
- 能够帮助我们理解“爬虫的底层原理”
虽然在实际开发中,我们更多会使用更高级的库(如 requests),但 urllib 的学习可以帮助我们理解:
浏览器是如何和服务器进行通信的, 爬虫的请求是如何构造的 , 因此,urllib 更适合作为“入门底层原理”的工具。
补充:urllib 在爬虫体系中的位置
在整个爬虫技术栈中,可以把 urllib 理解为:
- 最底层的请求工具(原生)
- 帮助理解 HTTP 请求的本质
对比关系可以这样理解:
- urllib:底层原理学习
- requests:工程化开发
- Scrapy:框架化爬虫
学习 urllib 的意义不在于“多用”,而在于“理解”。
二、最简单的请求方式
我们先来看一个最简单的爬虫程序:
# 导入 urllib.request 模块,用于发送 HTTP 请求
import urllib.request
# 定义要访问的目标网址
url = 'http://www.baidu.com'
# 向目标网址发送请求,并获取服务器返回的响应对象
response = urllib.request.urlopen(url)
# 读取响应内容,并按 utf-8 编码解码为字符串
content = response.read().decode('utf-8')
# 输出网页源码内容
print(content)
代码说明:
urlopen():向服务器发送请求response:服务器返回的数据对象read():读取返回的数据(字节)decode():将字节转换为字符串
补充说明:
并不是所有网页都使用 utf-8 编码。
如果页面编码不同,直接使用 decode('utf-8') 可能会报错。
入门阶段可以先这样写,后续会学习如何判断网页真实编码。
运行之后,你会看到网页的 HTML 源码。
另外,urlopen() 返回的并不是普通字符串,而是一个 HTTPResponse 对象。后续我们读取状态码、响应头、最终 URL,都是基于这个对象完成的。
注意:这里获取到的是“网页源码”,而不是浏览器渲染后的页面。
补充:请求执行流程(非常重要)
这个简单程序背后,其实发生了完整的 HTTP 请求流程:
- 客户端(你的程序)向服务器发送请求
- 服务器接收请求并处理
- 返回响应(HTML / JSON 等)
- 客户端接收并解析数据
可以理解为:
爬虫的核心 = 发送请求 + 解析响应
后续所有技术,都是围绕这两步展开。
三、Response 对象详解
response 是服务器返回的结果对象,我们可以通过它获取很多信息。
常用方法:
response.read() # 读取全部内容
response.readline() # 读取一行
response.readlines() # 读取多行
response.getcode() # 状态码
response.geturl() # 当前访问的 URL
response.getheaders() # 响应头
常见 HTTP 状态码:
- 200:请求成功
- 301 / 302:重定向
- 403:禁止访问(常见反爬)
- 404:页面不存在
- 500:服务器错误
在爬虫开发中,最常见的问题就是 403(被网站拒绝)
常见异常处理
在实际开发中,请求经常会失败,因此需要进行异常处理:
# 导入 urllib.request 模块,用于发送请求
import urllib.request
# 导入 urllib.error 模块,用于处理请求异常
import urllib.error
# 定义要访问的网址
url = 'http://www.baidu.com'
# 尝试执行请求代码
try:
# 发送请求并获取响应对象
response = urllib.request.urlopen(url)
# 读取响应内容并解码后输出
print(response.read().decode('utf-8'))
# 捕获 HTTP 状态码异常,例如 403、404
except urllib.error.HTTPError as e:
# 输出 HTTP 错误状态码
print('HTTPError:', e.code)
# 捕获 URL 请求异常,例如网络错误、域名错误
except urllib.error.URLError as e:
# 输出具体的异常原因
print('URLError:', e.reason)
说明:
HTTPError:服务器返回了响应,但状态码异常(如 403、404)URLError:请求未成功发出(如网络问题、域名错误)
补充:响应头中常见信息
可以通过 response.getheaders() 查看完整响应头,例如:
print(response.getheaders())
常见字段包括:
- Content-Type:返回数据类型(HTML / JSON)
- Server:服务器类型
- Set-Cookie:服务器设置的 Cookie
这些信息在后续分析接口和登录状态时非常重要。
实战建议
在真实项目中,建议:
- 对关键请求增加重试机制
- 打印日志方便排查问题
例如:
for i in range(3):
try:
response = urllib.request.urlopen(url)
break
except Exception:
print('重试中...', i)
四、下载网页、图片和文件
urllib 提供了一个更简单的方法用于下载资源:
# 导入 urllib.request 模块,用于下载网络资源
import urllib.request
# 定义要下载的图片地址
url = 'https://www.baidu.com/img/bd_logo1.png'
# 下载图片并保存为本地文件 logo.png
urllib.request.urlretrieve(url, 'logo.png')
说明:
- 第一个参数:资源地址
- 第二个参数:保存的文件名
这个方法不仅可以下载图片,还可以下载:
- HTML 页面
- 视频
- 文件(如 PDF)
注意事项:
- 有些网站需要请求头,否则下载失败
- 大文件下载建议使用流式处理(后续会讲)
补充说明:
urlretrieve() 适合入门演示,但在实际项目中,很多开发者更倾向于使用 urlopen() 或 requests 手动读取响应并保存文件,这样更加灵活,也更方便控制请求头和异常处理。
补充:使用 urlopen 手动下载(更灵活)
import urllib.request
url = 'https://www.baidu.com/img/bd_logo1.png'
# 发送请求
response = urllib.request.urlopen(url)
# 读取二进制数据
content = response.read()
# 写入文件
with open('logo.png', 'wb') as f:
f.write(content)
优点:
- 可以控制请求头
- 可以处理异常
- 更适合真实项目
五、为什么要定制请求对象
在真实爬虫中,直接使用 urlopen() 往往会失败,例如:
返回 403(被拒绝访问)
原因是:
服务器会判断请求是否来自“真实浏览器”。
因此我们需要使用 Request 对象来自定义请求。
# 导入 urllib.request 模块,用于构造请求和发送请求
import urllib.request
# 定义要访问的网址
url = 'http://www.baidu.com'
# 定义请求头字典
headers = {
# 添加 User-Agent,请求时伪装成浏览器
'User-Agent': 'Mozilla/5.0'
}
# 根据 URL 和请求头构造 Request 对象
request = urllib.request.Request(url=url, headers=headers)
# 发送自定义请求对象,并获取响应结果
response = urllib.request.urlopen(request)
# 读取响应内容并解码后输出
print(response.read().decode('utf-8'))
核心作用:
自定义请求头(headers)
模拟浏览器访问
补充:
除了 User-Agent,常见的请求头还包括:
RefererCookieAcceptAccept-Language
在很多网站中,仅设置 User-Agent 仍可能被识别,后续需要组合多个请求头。
补充:403 的本质原因
403 Forbidden 本质上是:
服务器判断你不是“正常用户”
常见检测方式:
- User-Agent
- Referer
- Cookie
- 请求频率
因此,仅添加 UA 有时还不够,需要组合策略。
六、User-Agent 基础介绍
什么是 User-Agent?
User-Agent 是 HTTP 请求头的一部分,用于标识客户端信息,例如:
- 浏览器类型
- 操作系统
- 设备信息
例如:
Mozilla/5.0 (Windows NT 10.0; Win64; x64)
为什么网站会检测 UA?
网站通过 UA 判断:
- 是否是浏览器访问
- 是否是爬虫程序
如果没有 UA,很多网站会直接返回:
403 Forbidden
如何伪装浏览器?
我们只需要在请求头中加入 User-Agent:
# 定义请求头字典
headers = {
# 在请求头中加入 User-Agent,伪装成浏览器
'User-Agent': 'Mozilla/5.0'
}
扩展:
后续我们还会学习:
- Cookie(保持登录状态)
- Proxy(代理 IP)
- Headers 伪装策略
这些都是“反爬对抗”的核心技术。
常见浏览器 UA 示例
Chrome:
Mozilla/5.0 (Windows NT 10.0; Win64; x64)
Safari:
Mozilla/5.0 (Macintosh; Intel Mac OS X)
Mobile:
Mozilla/5.0 (iPhone; CPU iPhone OS)
可以根据目标网站选择更合适的 UA。
七、补充:GET 与 POST 参数的基本处理
在使用 urllib 发送请求时,除了最基本的 urlopen() 和 Request(),还需要理解参数是如何传递的。
1. GET 请求参数
GET 请求通常会把参数直接拼接到 URL 后面。
例如,我们在搜索引擎中搜索关键词时,常见的 URL 形式如下:
https://www.baidu.com/s?wd=python
如果参数中包含中文或特殊字符,就需要先进行编码。
方式一:使用 quote() 处理单个参数
# 导入 urllib.parse 模块,用于处理 URL 编码
import urllib.parse
# 定义搜索关键词
keyword = '周杰伦'
# 对中文关键词进行编码,并拼接成完整 URL
url = 'https://www.baidu.com/s?wd=' + urllib.parse.quote(keyword)
# 输出编码后的 URL
print(url)
这种方式适合参数较少、结构比较简单的情况。
方式二:使用 urlencode() 处理多个参数
# 导入 urllib.parse 模块,用于处理查询参数
import urllib.parse
# 定义多个查询参数
params = {
# 搜索关键词参数
'wd': '周杰伦',
# 示例参数:性别
'sex': '男',
# 示例参数:地区
'location': '中国台湾省'
}
# 将字典参数编码为查询字符串
query_string = urllib.parse.urlencode(params)
# 将查询字符串拼接到 URL 后面
url = 'https://www.baidu.com/s?' + query_string
# 输出最终生成的 URL
print(url)
实际开发中,更推荐使用 urlencode(),因为它更适合处理多个参数,也更接近真实项目中的请求写法。
2. POST 请求参数
POST 请求和 GET 的主要区别在于:
- GET:参数拼接在 URL 后面
- POST:参数放在请求体中
在 urllib 中,POST 参数除了要进行 urlencode() 处理,还必须进一步转换为字节数据。
# 导入 urllib.request 模块,用于构造和发送请求
import urllib.request
# 导入 urllib.parse 模块,用于对 POST 参数进行编码
import urllib.parse
# 定义目标接口地址
url = 'https://fanyi.baidu.com/sug'
# 定义请求头字典
headers = {
# 添加 User-Agent,请求时伪装成浏览器
'User-Agent': 'Mozilla/5.0'
}
# 定义要提交的 POST 表单数据
data = {
# 提交的关键词参数
'kw': 'spider'
}
# 先将字典编码为查询字符串,再转换为字节数据
encoded_data = urllib.parse.urlencode(data).encode('utf-8')
# 构造携带 POST 数据的请求对象
request = urllib.request.Request(url=url, headers=headers, data=encoded_data)
# 发送 POST 请求并获取响应对象
response = urllib.request.urlopen(request)
# 读取响应内容并按 utf-8 解码为字符串
content = response.read().decode('utf-8')
# 输出服务器返回的内容
print(content)
这里需要重点记住两点:
- GET 参数通常不需要再调用
encode(),因为它们最终是 URL 字符串的一部分 - POST 参数在放入
Request()的data参数前,必须先转成字节类型
3. GET 和 POST 的简单对比
可以先用一句话记住它们的区别:
- GET 更常用于获取数据
- POST 更常用于提交数据
在后续的接口请求、登录请求、翻译请求中,POST 会非常常见。
补充:如何判断请求类型
可以通过浏览器开发者工具判断:
- 打开 Network 面板
- 点击请求
- 查看 Method 字段
如果是:
- GET → 参数在 URL
- POST → 参数在 Request Payload
八、Ajax 请求、Handler 与代理补充
前面介绍的是最基础的页面请求。实际抓取时,经常会遇到接口请求、Cookie 请求、代理请求等情况。
1. Ajax 请求
很多网站页面中的数据并不是直接写在 HTML 中,而是通过接口异步加载。浏览器中看到的数据,可能来自 Network 面板中的 XHR 或 Fetch 请求。
这类请求一般先在浏览器中找到接口地址,然后用 urllib 直接请求接口。
from urllib import request, parse
base_url = 'https://example.com/api/list?'
params = {
'page': 1,
'pageSize': 10
}
url = base_url + parse.urlencode(params)
headers = {
'User-Agent': 'Mozilla/5.0',
'Referer': 'https://example.com/list'
}
req = request.Request(url=url, headers=headers)
response = request.urlopen(req, timeout=10)
content = response.read().decode('utf-8')
print(content)
Ajax 接口通常返回 JSON 数据,后面可以继续使用 json.loads() 转成 Python 字典。
2. Handler 的作用
urlopen() 适合简单请求,如果需要统一处理 Cookie、代理、认证等逻辑,就需要使用 Handler。
可以把 Handler 理解为 urllib 的请求处理器。
from urllib import request
handler = request.HTTPHandler()
opener = request.build_opener(handler)
req = request.Request(
url='https://www.baidu.com',
headers={'User-Agent': 'Mozilla/5.0'}
)
response = opener.open(req)
print(response.read().decode('utf-8'))
后续如果要加代理、Cookie,也是在 build_opener() 中组合不同 Handler。
3. 使用代理
代理的作用是让请求通过另一台服务器转发。测试代理是否可用时,不要一开始就访问复杂网站,可以先访问能返回 IP 的测试接口。
from urllib import request
proxy_handler = request.ProxyHandler({
'http': 'http://127.0.0.1:7890',
'https': 'http://127.0.0.1:7890'
})
opener = request.build_opener(proxy_handler)
req = request.Request(
url='https://httpbin.org/ip',
headers={'User-Agent': 'Mozilla/5.0'}
)
response = opener.open(req, timeout=10)
print(response.read().decode('utf-8'))
如果要使用多个代理,可以先把代理地址放到列表中,再通过循环或随机选择的方式取出。
import random
from urllib import request
proxy_list = [
'http://127.0.0.1:7890',
'http://127.0.0.1:7891'
]
proxy = random.choice(proxy_list)
proxy_handler = request.ProxyHandler({
'http': proxy,
'https': proxy
})
实际使用代理时要注意两点:
- 代理不可用是很常见的,需要加超时和异常处理。
- 不要因为使用了代理就高频请求,频率过高仍然可能影响目标网站。
九、调试建议
如果代码运行失败,可以优先检查以下问题:
- URL 是否可以在浏览器中正常打开
- 是否因为缺少请求头被网站拒绝(如 403)
- 是否是编码问题导致解码失败
- 是否是网络问题或代理/VPN影响了请求
建议刚开始先使用结构简单、限制较少的网站进行测试,例如:
十、本篇总结
本篇我们系统学习了 urllib 的核心内容:
- 如何发送请求(urlopen / Request)
- Response 对象的结构
- 如何处理异常
- 如何下载资源
- 如何添加请求头
- GET / POST 参数处理
- Ajax 接口请求、Handler 与代理的基本使用
同时理解了一个非常重要的概念:
爬虫 = 请求 + 解析
urllib 虽然使用起来不如 requests 简洁,但它帮助我们理解了:
- HTTP 请求的本质
- 浏览器与服务器的交互方式
为后续学习 requests、Selenium、Scrapy 打下基础。