用 httpclient 下载图片最稳,因其支持取消、超时控制且默认处理重定向;需设 timeout、调 ensuresuccessstatuscode、用 readasbytearrayasync 读取、校验 contenttype 再保存。

用 HttpClient 下载图片最稳,别碰 WebClient
默认推荐 HttpClient,不是因为它“新”,而是 WebClient 在 .NET Core+ 里已标记为过时,且不支持取消、超时精细控制,容易在图片大或网络差时卡死线程。
常见错误现象:WebClient.DownloadFile 突然卡住几秒甚至十几秒才抛异常;或者下载完文件大小为 0 —— 实际是服务端返回了 302 重定向,WebClient 默认不自动跟随(尤其跨域重定向),而 HttpClient 默认会。
- 务必设置
Timeout,比如new TimeSpan(0, 0, 15)(15 秒) - 加
try/catch捕获HttpRequestException和TaskCanceledException,后者常因超时触发 - URL 必须是完整地址(含
https://),不能是相对路径或带空格未编码的中文
HttpResponseMessage.Content.ReadAsByteArrayAsync() 是安全读取二进制流的关键
直接调 Stream.CopyTo 看似简单,但容易漏掉响应状态检查:如果 URL 返回 404 或 500,HttpClient 默认不会抛异常,Content 仍可读,结果是把错误 HTML 页面存成一张“损坏的图”。
使用场景:你不确定目标 URL 是否稳定(比如爬取第三方图床),或需要统一处理失败逻辑。
- 先调
response.EnsureSuccessStatusCode(),它会在非 2xx 状态码时主动抛异常 - 再用
await response.Content.ReadAsByteArrayAsync(),它内部已处理好流关闭和内存缓冲,比手动ReadAsync更少出错 - 别用
response.Content.ReadAsStringAsync()读图片——那是给 JSON/HTML 准备的,读二进制会乱码
保存前必须检查 Content-Type,否则可能存错扩展名
很多图片 URL 不以 .jpg 或 .png 结尾(比如 https://api.xxx.com/img?id=123),光看后缀会误判。服务端通常会在响应头里写明真实类型,比如 image/jpeg 或 image/webp。
容易踩的坑:直接按 URL 后缀生成本地文件名,结果把 WebP 图片存成 .jpg,Windows 照片查看器打不开;或者没校验,把一个返回 text/html 的 404 页面存成了 .png。
- 从
response.Content.Headers.ContentType?.MediaType取值,例如得到"image/png" - 用字典映射 MediaType 到扩展名:
"image/jpeg" → ".jpg","image/webp" → ".webp" - 如果
ContentType为空或无法识别,宁可拒绝保存,也不要硬猜
异步保存到磁盘时,File.WriteAllBytesAsync 比 FileStream 更省心
有人喜欢手动开 FileStream + WriteAsync,以为更“底层可控”,但实际增加了出错点:忘记 Dispose、没设 FileOptions.Asynchronous 导致假异步、并发写同一文件没加锁……
性能影响:对中小图片(File.WriteAllBytesAsync 内部已优化,和手动流差别不大;对大文件,它默认用缓冲池,比反复 WriteAsync 更稳。
- 路径要用
Path.Combine拼接,别手拼"C:\img\" + name,Windows/Linux 路径分隔符不同 - 确保目标目录存在,提前调
Directory.CreateDirectory(Path.GetDirectoryName(filePath)) - 如果要支持高并发下载,避免多个任务写同一个文件路径——得靠业务层保证文件名唯一,不是靠锁文件
真正麻烦的是重定向跳转、证书验证失败、代理拦截、防盗链 Header 缺失这些网络层问题,它们不会报“下载失败”,而是静默返回空白或错误页。调试时第一个该看的,永远是 response.StatusCode 和 response.RequestMessage.RequestUri——确认你最终请求的是哪个地址。










