Python下载图片遇到403错误怎么设置User-Agent解决
详细讲解在Python中使用urllib与requests下载图片时遭遇HTTP 403 Forbidden错误的根本原因,手把手教你如何正确配置User-Agent请求头,以及遭遇图片防盗链Referer、Token鉴权时的深度排查方法。
Python-urllib/3.x 或 python-requests/2.x),直接触发了目标服务器网关的反爬拦截规则。最直接有效的解决办法是在发起请求时自定义 headers 字典,将 User-Agent 显式指定为主流桌面浏览器的标识字符串。本文将分别以 requests 库和 Python 标准库 urllib 为例,给出正确设置 User-Agent 并安全落盘保存图片的可运行完整代码,并深入辨析遭遇防盗链与鉴权时的排查路径。
一、为什么 Python 默认下载图片会触发 403 错误?
HTTP 403 Forbidden 是 HTTP 协议中的标准状态码,表示“服务器已经理解客户端发起的请求,但拒绝执行或授权访问该资源”。当我们在网页浏览器中能够正常点击查看一张图片,而同一张图片在 Python 脚本中调用 urllib.request.urlopen() 或 requests.get() 时却返回 403 错误,根本症结就在于两者的 HTTP 请求头(Request Headers) 存在显著差异。
在不主动声明 headers 的情况下:
- Python 原生标准库
urllib默认发送的 User-Agent 字符串形如:Python-urllib/3.10(具体版本号随 Python 环境而定); - 第三方库
requests默认发送的 User-Agent 字符串形如:python-requests/2.31.0。
现代网站的 Web 应用防火墙(WAF)、反向代理服务器(如 Nginx、OpenResty)或云厂商内容分发网络(CDN),普遍预置了基础的反爬虫与防盗链规则。一旦检测到入站请求中的 User-Agent 包含 Python、urllib、requests、curl 等关键字,或者该字段完全缺失,安全网关就会将其直接归类为非正常人类访问的自动化抓取脚本,并在网络层直接阻断,回传 HTTP 403 页面。
由此可见,解决此类问题的核心手段,就是在代码层面接管请求头构造,将 User-Agent 替换为主流合规浏览器的签名,使网络请求在服务器视角下看起来与常规浏览器访问完全一致。
二、使用 requests 库设置 User-Agent 下载图片(工程首选)
在实际生产和脚本开发中,推荐优先使用第三方库 requests。它不仅语法清晰易懂,而且原生支持 stream=True(流式响应传输),非常适合大文件和高分辨率图片的内存友好型下载。
2.1 核心步骤与实现逻辑
- 定义请求头字典:准备一个标准的桌面浏览器
User-Agent字符串并封装在字典中; - 开启流式请求:调用
requests.get(url, headers=headers, stream=True, timeout=15)发起连接; - 严格检查状态码:确认
response.status_code == 200,杜绝将 403 拒绝页面或 404 页面误写入图片; - 分块写入本地文件:以
'wb'(二进制写入)模式创建文件,利用response.iter_content()逐块落盘。
2.2 完整代码示例
以下是一段经过生产验证的标准图片下载函数。代码统一了变量命名,并加入了防御性异常捕获:
import os
import requests
def download_image_with_requests(image_url: str, save_path: str) -> bool:
"""
使用 requests 库配置 User-Agent 下载图片并安全存盘
:param image_url: 图片的网络 URL 目标地址
:param save_path: 本地保存的目标完整路径(如 ./download/test.jpg)
:return: 下载并保存成功返回 True,失败返回 False
"""
# 1. 模拟真实 Chrome 浏览器的 User-Agent
headers = {
'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'
}
try:
# 2. 发起流式网络请求,设置超时时间防止挂起
with requests.get(image_url, headers=headers, stream=True, timeout=15) as response:
# 3. 校验服务端状态码,只有 200 才能继续处理
if response.status_code != 200:
print(f"[下载失败] HTTP 状态码异常: {response.status_code}, URL: {image_url}")
return False
# 确保目标上级目录存在
parent_dir = os.path.dirname(save_path)
if parent_dir and not os.path.exists(parent_dir):
os.makedirs(parent_dir, exist_ok=True)
# 4. 以二进制只写模式 ('wb') 分块落盘,推荐块大小为 8192 字节
with open(save_path, 'wb') as f:
for chunk in response.iter_content(chunk_size=8192):
if chunk:
f.write(chunk)
# 5. 校验本地文件是否存在且非空
if os.path.exists(save_path) and os.path.getsize(save_path) > 0:
print(f"[下载成功] 图片已成功保存至: {save_path} ({os.path.getsize(save_path)} 字节)")
return True
else:
print(f"[下载失败] 保存的文件为空: {save_path}")
return False
except requests.exceptions.RequestException as err:
print(f"[网络异常] 请求发生错误: {err}")
return False
# 示例调用
if __name__ == '__main__':
target_url = "https://images.unsplash.com/photo-1579783900882-c0d3dad7b119"
local_output = "./download_demo_requests.jpg"
download_image_with_requests(target_url, local_output)
很多初学者会写 with open(path, 'w') as f: f.write(response.text),这是严重的错误。图片本质是由 0 和 1 构成的二进制字节流,必须使用 'wb' 模式写入字节。如果使用 .text,requests 会尝试按文本编码(如 UTF-8)对字节进行解码转换,必然会损坏图像元数据,导致保存出来的图片打不开或损坏。
三、使用 Python 标准库 urllib 设置 User-Agent(零依赖环境)
如果运行环境无法通过 pip install requests 安装第三方包(例如受限的轻量级容器或企业离线服务器),也可以使用 Python 内置的标准库 urllib.request 达成同样的效果。
3.1 标准库的常见踩坑点:不要使用 urlretrieve
部分教程会推荐直接调用快捷方法 urllib.request.urlretrieve(url, filename)。然而,该快捷函数不支持直接传入 headers 参数,默认依然发送 Python-urllib 请求头,因而在开启防爬的站点上依然会遭遇 403 错误。
正确的做法是:使用 urllib.request.Request 显式构造一个包含了 headers 的请求对象,再交由 urllib.request.urlopen() 打开并读取数据。
3.2 urllib 完整可运行代码
import os
import urllib.request
import urllib.error
def download_image_with_urllib(image_url: str, save_path: str) -> bool:
"""
使用 Python 标准库 urllib 配置 User-Agent 下载图片并保存
:param image_url: 图片目标 URL
:param save_path: 本地文件保存路径
:return: 成功返回 True,失败返回 False
"""
headers = {
'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'
}
# 1. 显式封装包含 User-Agent 的 Request 对象
req = urllib.request.Request(url=image_url, headers=headers)
try:
# 2. 发起请求并读取响应
with urllib.request.urlopen(req, timeout=15) as response:
if response.status != 200:
print(f"[urllib 失败] 状态码: {response.status}")
return False
parent_dir = os.path.dirname(save_path)
if parent_dir and not os.path.exists(parent_dir):
os.makedirs(parent_dir, exist_ok=True)
# 3. 循环分块读取并写入本地二进制文件
with open(save_path, 'wb') as f:
while True:
chunk = response.read(8192)
if not chunk:
break
f.write(chunk)
if os.path.exists(save_path) and os.path.getsize(save_path) > 0:
print(f"[urllib 成功] 文件保存至: {save_path}")
return True
return False
except urllib.error.HTTPError as err:
print(f"[urllib HTTP 错误] 状态码: {err.code}, 原因: {err.reason}")
return False
except urllib.error.URLError as err:
print(f"[urllib URL 错误] 无法连接主机: {err.reason}")
return False
# 示例调用
if __name__ == '__main__':
target_url = "https://images.unsplash.com/photo-1579783900882-c0d3dad7b119"
local_output = "./download_demo_urllib.jpg"
download_image_with_urllib(target_url, local_output)
四、认知辨析:设置了 User-Agent 是否就一定能解决 403 错误?
网络上常有一种说法:“只要在请求里加了浏览器 User-Agent,所有的 403 错误都能迎刃而解。”
这种说法在什么条件下成立? 当且仅当目标服务器仅配置了最基础的静态客户端黑名单规则(即仅仅匹配 User-Agent 字符串是否包含 Python 关键字)时,该方案可以 100% 解决问题。对于初学者爬取普通博客、一般内容站的公开图片,这一招通常立竿见影。
哪里需要修正? 在实际复杂的网络环境中,HTTP 403 的实质是服务器拒绝向当前请求授权,而 User-Agent 仅仅声明了“我是什么软件”,并不能证明“我拥有读取该资源的权限”。如果服务端配置了更高级的防护,单纯伪装 User-Agent 依然会持续返回 403。
4.1 排查支线一:图片防盗链机制(Referer 限制)
这是仅次于 User-Agent 的第二大常见 403 原因。各大内容平台(如微博、知乎、微信公众号、小红书等)为了防止外部网站无偿消耗自身图床流量,均配置了 CDN 防盗链策略。
- 鉴权原理:服务器检查请求头中的
Referer字段(表示从哪个网页跳转过来的)。如果Referer为空,或者不在其信任的域名白名单内,直接回传 403。 - 对策:在
headers字典中显式加入该网站的主站地址或图片所属的文章页面地址,例如:headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ...', 'Referer': 'https://www.target-website.com/' # 填入图片来源站点的域名或文章URL }
4.2 排查支线二:时效性签名参数(Token / Signature)失效
很多现代云存储(如阿里云 OSS、腾讯云 COS、AWS S3)生成的原图链接带有动态鉴权参数,如 ?sign=xxx&expires=1710000000。
- 表现:刚从网页上右键复制的 URL 能下载,但几分钟或几小时后再次运行代码就报错 403。
- 对策:确认该签名是否具有时效性。如果是动态签名,爬虫程序必须即时通过页面或 API 重新提取最新生成的临时 URL,立即发起下载,不可将过期 URL 存入数据库长期使用。
4.3 排查支线三:用户登录权限(Cookie / Authorization)
某些高质量图片、高清原图或私密社群资源,必须在用户登录认证后方可查看。未携带有效登录态凭证的请求会被重定向或直接阻断为 403。
- 对策:在浏览器开发者工具(F12)的 Network 面板中,找到对应图片的请求头,将
Cookie字段复制添加到 Python 代码的headers字典中;或者使用requests.Session()先完成模拟登录,再复用会话发起下载。
4.4 排查支线四:高频请求触发了 IP 限额风控
如果单独下载前几张图片都很顺利,而在循环批量下载数百张图片时中途突然密集爆发 403 错误,这通常意味着请求频次触发了服务器的速率限制(Rate Limiting)规则,你的当前出口 IP 被临时加入限流黑名单。
- 对策:在每次下载之间加入适度休眠(如
time.sleep(random.uniform(1.0, 2.5)));控制并发线程数量;或者为requests.get()配置代理 IP 池(proxies={'http': '...', 'https': '...'})。
五、核心实践与防坑总结
将 Python 下载图片的代码从“能跑通”提升到“具备生产级鲁棒性”,需要牢记以下几个关键维度:
| 技术关注点 | 常见错误做法 | 推荐标准规范 |
|---|---|---|
| User-Agent 设置 | 缺失或使用简写(如 'User-Agent': 'test') |
使用标准 Chrome/Safari 完整标识字符串 |
| 防盗链处理 | 仅设置 User-Agent,忽略 Referer | 成对设置 User-Agent 与来源站 Referer |
| 文件落盘模式 | 使用 open(..., 'w') 写入 response.text |
严格使用 'wb' 模式写入二进制 iter_content() |
| 状态码校验 | 不管三七二十一直接落盘,存下 403 网页 HTML | 明确判断 status_code == 200 再开流写入 |
| 网络鲁棒性 | 未指定 timeout,遇到网络丢包永久卡死 | 显式传入 timeout=15 并包裹 try-except 捕获异常 |
通过在代码中标准规范地注入浏览器 User-Agent、结合目标场景按需补充 Referer、并在文件写入前做好状态码断言,即可从容解决绝大多数 Python 下载图片时的 403 报错问题。
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













